Switched to new bug reporting URL
[zeroinstall/solver.git] / zeroinstall / 0launch-gui / bugs.py
blobc3290fdda37e5b032e2b9981ccde95bf4466bd39
1 # Copyright (C) 2009, Thomas Leonard
2 # See http://0install.net/0compile.html
4 from __future__ import print_function
6 import sys, os
7 import gtk, pango
8 import dialog
10 import zeroinstall
11 from zeroinstall import _
12 from zeroinstall import support
14 def report_bug(driver, iface):
15 assert iface
17 # TODO: Check the interface to decide where to send bug reports
19 issue_file = '/etc/issue'
20 if os.path.exists(issue_file):
21 with open(issue_file, 'rt') as stream:
22 issue = stream.read().strip()
23 else:
24 issue = "(file '%s' not found)" % issue_file
26 requirements = driver.requirements
28 text = 'Problem with %s\n' % iface.uri
29 if iface.uri != requirements.interface_uri:
30 text = ' (while attempting to run %s)\n' % requirements.interface_uri
31 text += '\n'
33 text += 'Zero Install: Version %s, with Python %s\n' % (zeroinstall.version, sys.version)
35 text += '\nChosen implementations:\n'
37 if not driver.solver.ready:
38 text += ' Failed to select all required implementations\n'
40 for chosen_iface_uri, impl in driver.solver.selections.selections.items():
41 text += '\n Interface: %s\n' % chosen_iface_uri
42 if impl:
43 text += ' Version: %s\n' % impl.version
44 feed_url = impl.attrs['from-feed']
45 if feed_url != chosen_iface_uri:
46 text += ' From feed: %s\n' % feed_url
47 text += ' ID: %s\n' % impl.id
48 else:
49 chosen_iface = driver.config.iface_cache.get_interface(chosen_iface_uri)
50 impls = driver.solver.details.get(chosen_iface, None)
51 if impls:
52 best, reason = impls[0]
53 note = 'best was %s, but: %s' % (best, reason)
54 else:
55 note = 'not considered; %d available' % len(chosen_iface.implementations)
57 text += ' No implementation selected (%s)\n' % note
59 if hasattr(os, 'uname'):
60 text += '\nSystem:\n %s\n\nIssue:\n %s\n' % ('\n '.join(os.uname()), issue)
61 else:
62 text += '\nSystem without uname()\n'
64 if driver.solver.ready:
65 sels = driver.solver.selections
66 text += "\n" + sels.toDOM().toprettyxml()
68 reporter = BugReporter(driver, iface, text)
69 reporter.show()
71 class BugReporter(dialog.Dialog):
72 def __init__(self, driver, iface, env):
73 dialog.Dialog.__init__(self)
75 self.set_title(_('Report a Bug'))
76 self.driver = driver
77 self.frames = []
79 vbox = gtk.VBox(False, 4)
80 vbox.set_border_width(10)
81 self.vbox.pack_start(vbox, True, True, 0)
83 self.set_default_size(gtk.gdk.screen_width() / 2, -1)
85 def frame(title, contents, buffer):
86 fr = gtk.Frame()
87 label = gtk.Label('')
88 label.set_markup('<b>%s</b>' % title)
89 fr.set_label_widget(label)
90 fr.set_shadow_type(gtk.SHADOW_NONE)
91 vbox.pack_start(fr, True, True, 0)
93 align = gtk.Alignment(0, 0, 1, 1)
94 align.set_padding(0, 0, 16, 0)
95 fr.add(align)
96 align.add(contents)
98 self.frames.append((title, buffer))
100 def text_area(text = None, mono = False):
101 swin = gtk.ScrolledWindow()
102 swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
103 swin.set_shadow_type(gtk.SHADOW_IN)
105 tv = gtk.TextView()
106 tv.set_wrap_mode(gtk.WRAP_WORD)
107 swin.add(tv)
108 if text:
109 tv.get_buffer().insert_at_cursor(text)
111 if mono:
112 tv.modify_font(pango.FontDescription('mono'))
114 tv.set_accepts_tab(False)
116 return swin, tv.get_buffer()
118 actual = text_area()
119 frame(_("What doesn't work?"), *actual)
121 expected = text_area()
122 frame(_('What did you expect to happen?'), *expected)
124 errors_box = gtk.VBox(False, 0)
125 errors_swin, errors_buffer = text_area(mono = True)
126 errors_box.pack_start(errors_swin, True, True, 0)
127 buttons = gtk.HButtonBox()
128 buttons.set_layout(gtk.BUTTONBOX_START)
129 errors_box.pack_start(buttons, False, True, 4)
130 get_errors = gtk.Button(_('Run it now and record the output'))
131 get_errors.connect('clicked', lambda button: self.collect_output(errors_buffer))
132 buttons.add(get_errors)
134 frame(_('Are any errors or warnings displayed?'), errors_box, errors_buffer)
136 if dialog.last_error:
137 errors_buffer.insert_at_cursor(str(dialog.last_error))
139 environ = text_area(env, mono = True)
140 frame(_('Information about your setup'), *environ)
142 # (not working since sf.net update)
144 # browse_url = 'http://sourceforge.net/tracker/?group_id=%d&atid=%d' % (self.sf_group_id, self.sf_artifact_id)
145 # location_hbox = gtk.HBox(False, 4)
146 # location_hbox.pack_start(gtk.Label(_('Bugs reports will be sent to:')), False, True, 0)
147 # if hasattr(gtk, 'LinkButton'):
148 # import browser
149 # url_box = gtk.LinkButton(browse_url, label = browse_url)
150 # url_box.connect('clicked', lambda button: browser.open_in_browser(browse_url))
151 # else:
152 # url_box = gtk.Label(browse_url)
153 # url_box.set_selectable(True)
154 # location_hbox.pack_start(url_box, False, True, 0)
155 # vbox.pack_start(location_hbox, False, True, 0)
157 self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
158 self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
159 self.set_default_response(gtk.RESPONSE_OK)
161 def resp(box, r):
162 if r == gtk.RESPONSE_OK:
163 text = ''
164 for title, buffer in self.frames:
165 start = buffer.get_start_iter()
166 end = buffer.get_end_iter()
167 text += '%s\n\n%s\n\n' % (title, buffer.get_text(start, end, include_hidden_chars = False).strip())
168 try:
169 message = self.report_bug(iface, text)
170 except Exception as ex:
171 dialog.alert(None, _("Error sending bug report: {ex}".format(ex = ex)),
172 type = gtk.MESSAGE_ERROR)
173 else:
174 dialog.alert(None, _("Success: {msg}").format(msg = message),
175 type = gtk.MESSAGE_INFO)
176 self.destroy()
177 else:
178 self.destroy()
179 self.connect('response', resp)
181 self.show_all()
183 def collect_output(self, buffer):
184 iter = buffer.get_end_iter()
185 buffer.place_cursor(iter)
187 if not self.driver.solver.ready:
188 sels = self.driver.solver.selections
189 buffer.insert_at_cursor("Can't run:\n{reason}".format(
190 reason = self.driver.solver.get_failure_reason()))
191 return
192 uncached = self.driver.get_uncached_implementations()
193 if uncached:
194 buffer.insert_at_cursor("Can't run: the chosen versions have not been downloaded yet. I need:\n\n- " +
195 "\n\n- " . join(['%s version %s\n (%s)' %(x[0].uri, x[1].get_version(), x[1].id) for x in uncached]))
196 return
198 sels = self.driver.solver.selections
199 doc = sels.toDOM()
201 self.hide()
202 try:
203 gtk.gdk.flush()
204 iter = buffer.get_end_iter()
205 buffer.place_cursor(iter)
207 # Tell 0launch to run the program
208 doc.documentElement.setAttribute('run-test', 'true')
209 payload = doc.toxml('utf-8')
210 if sys.version_info[0] > 2:
211 stdout = sys.stdout.buffer
212 else:
213 stdout = sys.stdout
214 stdout.write(('Length:%8x\n' % len(payload)).encode('utf-8') + payload)
215 stdout.flush()
217 reply = support.read_bytes(0, len('Length:') + 9)
218 assert reply.startswith(b'Length:')
219 test_output = support.read_bytes(0, int(reply.split(b':', 1)[1], 16))
221 # Cope with invalid UTF-8
222 import codecs
223 decoder = codecs.getdecoder('utf-8')
224 data = decoder(test_output, 'replace')[0]
226 buffer.insert_at_cursor(data)
227 finally:
228 self.show()
230 def report_bug(self, iface, text):
231 try:
232 if sys.version_info[0] > 2:
233 from urllib.request import urlopen
234 from urllib.parse import urlencode
235 else:
236 from urllib2 import urlopen
237 from urllib import urlencode
239 data = urlencode({
240 'uri': iface.uri,
241 'body': text}).encode('utf-8')
242 stream = urlopen('http://0install.net/api/report-bug/', data = data)
243 reply = stream.read().decode('utf-8')
244 stream.close()
245 return reply
246 except:
247 # Write to stderr in the hope that it doesn't get lost
248 print("Error sending bug report: %s\n\n%s" % (iface, text), file=sys.stderr)
249 raise