More updates to support Python 3
[zeroinstall/solver.git] / zeroinstall / 0launch-gui / bugs.py
blob65a7ddd7f6a49e4b8203320f482b0b7a27a4c124
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 issue = open(issue_file).read().strip()
22 else:
23 issue = "(file '%s' not found)" % issue_file
25 requirements = driver.requirements
27 text = 'Problem with %s\n' % iface.uri
28 if iface.uri != requirements.interface_uri:
29 text = ' (while attempting to run %s)\n' % requirements.interface_uri
30 text += '\n'
32 text += 'Zero Install: Version %s, with Python %s\n' % (zeroinstall.version, sys.version)
34 text += '\nChosen implementations:\n'
36 if not driver.solver.ready:
37 text += ' Failed to select all required implementations\n'
39 for chosen_iface_uri, impl in driver.solver.selections.selections.items():
40 text += '\n Interface: %s\n' % chosen_iface_uri
41 if impl:
42 text += ' Version: %s\n' % impl.version
43 feed_url = impl.attrs['from-feed']
44 if feed_url != chosen_iface_uri:
45 text += ' From feed: %s\n' % feed_url
46 text += ' ID: %s\n' % impl.id
47 else:
48 chosen_iface = driver.config.iface_cache.get_interface(chosen_iface_uri)
49 impls = driver.solver.details.get(chosen_iface, None)
50 if impls:
51 best, reason = impls[0]
52 note = 'best was %s, but: %s' % (best, reason)
53 else:
54 note = 'not considered; %d available' % len(chosen_iface.implementations)
56 text += ' No implementation selected (%s)\n' % note
58 if hasattr(os, 'uname'):
59 text += '\nSystem:\n %s\n\nIssue:\n %s\n' % ('\n '.join(os.uname()), issue)
60 else:
61 text += '\nSystem without uname()\n'
63 if driver.solver.ready:
64 sels = driver.solver.selections
65 text += "\n" + sels.toDOM().toprettyxml(encoding = 'utf-8')
67 reporter = BugReporter(driver, iface, text)
68 reporter.show()
70 class BugReporter(dialog.Dialog):
71 def __init__(self, driver, iface, env):
72 dialog.Dialog.__init__(self)
74 self.sf_group_id = 76468
75 self.sf_artifact_id = 929902
77 self.set_title(_('Report a Bug'))
78 self.set_modal(True)
79 self.set_has_separator(False)
80 self.driver = driver
81 self.frames = []
83 vbox = gtk.VBox(False, 4)
84 vbox.set_border_width(10)
85 self.vbox.pack_start(vbox, True, True, 0)
87 self.set_default_size(gtk.gdk.screen_width() / 2, -1)
89 def frame(title, contents, buffer):
90 fr = gtk.Frame()
91 label = gtk.Label('')
92 label.set_markup('<b>%s</b>' % title)
93 fr.set_label_widget(label)
94 fr.set_shadow_type(gtk.SHADOW_NONE)
95 vbox.pack_start(fr, True, True, 0)
97 align = gtk.Alignment(0, 0, 1, 1)
98 align.set_padding(0, 0, 16, 0)
99 fr.add(align)
100 align.add(contents)
102 self.frames.append((title, buffer))
104 def text_area(text = None, mono = False):
105 swin = gtk.ScrolledWindow()
106 swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
107 swin.set_shadow_type(gtk.SHADOW_IN)
109 tv = gtk.TextView()
110 tv.set_wrap_mode(gtk.WRAP_WORD)
111 swin.add(tv)
112 if text:
113 tv.get_buffer().insert_at_cursor(text)
115 if mono:
116 tv.modify_font(pango.FontDescription('mono'))
118 tv.set_accepts_tab(False)
120 return swin, tv.get_buffer()
122 actual = text_area()
123 frame(_("What doesn't work?"), *actual)
125 expected = text_area()
126 frame(_('What did you expect to happen?'), *expected)
128 errors_box = gtk.VBox(False, 0)
129 errors_swin, errors_buffer = text_area(mono = True)
130 errors_box.pack_start(errors_swin, True, True, 0)
131 buttons = gtk.HButtonBox()
132 buttons.set_layout(gtk.BUTTONBOX_START)
133 errors_box.pack_start(buttons, False, True, 4)
134 get_errors = gtk.Button(_('Run it now and record the output'))
135 get_errors.connect('clicked', lambda button: self.collect_output(errors_buffer))
136 buttons.add(get_errors)
138 frame(_('Are any errors or warnings displayed?'), errors_box, errors_buffer)
140 if dialog.last_error:
141 errors_buffer.insert_at_cursor(str(dialog.last_error))
143 environ = text_area(env, mono = True)
144 frame(_('Information about your setup'), *environ)
146 browse_url = 'http://sourceforge.net/tracker/?group_id=%d&atid=%d' % (self.sf_group_id, self.sf_artifact_id)
147 location_hbox = gtk.HBox(False, 4)
148 location_hbox.pack_start(gtk.Label(_('Bugs reports will be sent to:')), False, True, 0)
149 if hasattr(gtk, 'LinkButton'):
150 import browser
151 url_box = gtk.LinkButton(browse_url, label = browse_url)
152 url_box.connect('clicked', lambda button: browser.open_in_browser(browse_url))
153 else:
154 url_box = gtk.Label(browse_url)
155 url_box.set_selectable(True)
156 location_hbox.pack_start(url_box, False, True, 0)
157 vbox.pack_start(location_hbox, False, True, 0)
159 self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
160 self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
161 self.set_default_response(gtk.RESPONSE_OK)
163 def resp(box, r):
164 if r == gtk.RESPONSE_OK:
165 text = ''
166 for title, buffer in self.frames:
167 start = buffer.get_start_iter()
168 end = buffer.get_end_iter()
169 text += '%s\n\n%s\n\n' % (title, buffer.get_text(start, end).strip())
170 title = _('Bug for %s') % iface.get_name()
171 self.report_bug(title, text)
172 dialog.alert(None, _("Your bug report has been sent. Thank you."),
173 type = gtk.MESSAGE_INFO)
174 self.destroy()
175 else:
176 self.destroy()
177 self.connect('response', resp)
179 self.show_all()
181 def collect_output(self, buffer):
182 iter = buffer.get_end_iter()
183 buffer.place_cursor(iter)
185 if not self.driver.solver.ready:
186 sels = self.driver.solver.selections
187 buffer.insert_at_cursor("Can't run:\n{reason}".format(
188 reason = self.driver.solver.get_failure_reason()))
189 return
190 uncached = self.driver.get_uncached_implementations()
191 if uncached:
192 buffer.insert_at_cursor("Can't run: the chosen versions have not been downloaded yet. I need:\n\n- " +
193 "\n\n- " . join(['%s version %s\n (%s)' %(x[0].uri, x[1].get_version(), x[1].id) for x in uncached]))
194 return
196 sels = self.driver.solver.selections
197 doc = sels.toDOM()
199 self.hide()
200 try:
201 gtk.gdk.flush()
202 iter = buffer.get_end_iter()
203 buffer.place_cursor(iter)
205 # Tell 0launch to run the program
206 doc.documentElement.setAttribute('run-test', 'true')
207 payload = doc.toxml('utf-8')
208 sys.stdout.write(('Length:%8x\n' % len(payload)) + payload)
209 sys.stdout.flush()
211 reply = support.read_bytes(0, len('Length:') + 9)
212 assert reply.startswith('Length:')
213 test_output = support.read_bytes(0, int(reply.split(':', 1)[1], 16))
215 # Cope with invalid UTF-8
216 import codecs
217 decoder = codecs.getdecoder('utf-8')
218 data = decoder(test_output, 'replace')[0]
220 buffer.insert_at_cursor(data)
221 finally:
222 self.show()
224 def report_bug(self, title, text):
225 try:
226 import urllib
227 from urllib2 import urlopen
229 stream = urlopen('http://sourceforge.net/tracker/index.php',
230 urllib.urlencode({
231 'group_id': str(self.sf_group_id),
232 'atid': str(self.sf_artifact_id),
233 'func': 'postadd',
234 'is_private': '0',
235 'summary': title,
236 'details': text}))
237 stream.read()
238 stream.close()
239 except:
240 # Write to stderr in the hope that it doesn't get lost
241 print("Error sending bug report: %s\n\n%s" % (title, text), file=sys.stderr)
242 raise