Update year to 2009 in various places
[zeroinstall/zeroinstall-rsl.git] / zeroinstall / 0launch-gui / bugs.py
blob2b34e05fd1b6f49fdae43496c038d525d0bd67cb
1 # Copyright (C) 2009, Thomas Leonard
2 # See http://0install.net/0compile.html
4 import sys, os
5 import gtk, pango
6 import dialog
8 import zeroinstall
9 from zeroinstall import support
11 def report_bug(policy, iface):
12 assert iface
14 # TODO: Check the interface to decide where to send bug reports
16 issue_file = '/etc/issue'
17 if os.path.exists(issue_file):
18 issue = file(issue_file).read().strip()
19 else:
20 issue = "(file '%s' not found)" % issue
22 text = 'Problem with %s\n' % iface.uri
23 if iface.uri != policy.root:
24 text = ' (while attempting to run %s)\n' % policy.root
25 text += '\n'
27 text += 'Zero Install: Version %s, with Python %s\n' % (zeroinstall.version, sys.version)
29 text += '\nChosen implementations:\n'
31 if not policy.ready:
32 text += ' Failed to select all required implementations\n'
34 for chosen_iface in policy.implementation:
35 text += '\n Interface: %s\n' % chosen_iface.uri
36 impl = policy.implementation[chosen_iface]
37 if impl:
38 text += ' Version: %s\n' % impl.get_version()
39 if impl.feed.url != chosen_iface.uri:
40 text += ' From feed: %s\n' % impl.feed.url
41 text += ' ID: %s\n' % impl.id
42 else:
43 impls = policy.solver.details.get(chosen_iface, None)
44 if impls:
45 best, reason = impls[0]
46 note = 'best was %s, but: %s' % (best, reason)
47 else:
48 note = 'not considered; %d available' % len(chosen_iface.implementations)
50 text += ' No implementation selected (%s)\n' % note
52 if hasattr(os, 'uname'):
53 text += '\nSystem:\n %s\n\nIssue:\n %s\n' % ('\n '.join(os.uname()), issue)
54 else:
55 text += '\nSystem without uname()\n'
57 reporter = BugReporter(policy, iface, text)
58 reporter.show()
60 class BugReporter(dialog.Dialog):
61 def __init__(self, policy, iface, env):
62 dialog.Dialog.__init__(self)
64 self.sf_group_id = 76468
65 self.sf_artifact_id = 929902
67 self.set_title('Report a Bug')
68 self.set_modal(True)
69 self.set_has_separator(False)
70 self.policy = policy
71 self.frames = []
73 vbox = gtk.VBox(False, 4)
74 vbox.set_border_width(10)
75 self.vbox.pack_start(vbox, True, True, 0)
77 self.set_default_size(gtk.gdk.screen_width() / 2, -1)
79 def frame(title, contents, buffer):
80 fr = gtk.Frame()
81 label = gtk.Label('')
82 label.set_markup('<b>%s</b>' % title)
83 fr.set_label_widget(label)
84 fr.set_shadow_type(gtk.SHADOW_NONE)
85 vbox.pack_start(fr, True, True, 0)
87 align = gtk.Alignment(0, 0, 1, 1)
88 align.set_padding(0, 0, 16, 0)
89 fr.add(align)
90 align.add(contents)
92 self.frames.append((title, buffer))
94 def text_area(text = None, mono = False):
95 swin = gtk.ScrolledWindow()
96 swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
97 swin.set_shadow_type(gtk.SHADOW_IN)
99 tv = gtk.TextView()
100 tv.set_wrap_mode(gtk.WRAP_WORD)
101 swin.add(tv)
102 if text:
103 tv.get_buffer().insert_at_cursor(text)
105 if mono:
106 tv.modify_font(pango.FontDescription('mono'))
108 tv.set_accepts_tab(False)
110 return swin, tv.get_buffer()
112 actual = text_area()
113 frame("What doesn't work?", *actual)
115 expected = text_area()
116 frame('What did you expect to happen?', *expected)
118 errors_box = gtk.VBox(False, 0)
119 errors_swin, errors_buffer = text_area(mono = True)
120 errors_box.pack_start(errors_swin, True, True, 0)
121 buttons = gtk.HButtonBox()
122 buttons.set_layout(gtk.BUTTONBOX_START)
123 errors_box.pack_start(buttons, False, True, 4)
124 get_errors = gtk.Button('Run it now and record the output')
125 get_errors.connect('clicked', lambda button: self.collect_output(errors_buffer))
126 buttons.add(get_errors)
128 frame('Are any errors or warnings displayed?', errors_box, errors_buffer)
130 if dialog.last_error:
131 errors_buffer.insert_at_cursor(str(dialog.last_error))
133 environ = text_area(env, mono = True)
134 frame('Information about your setup', *environ)
136 browse_url = 'http://sourceforge.net/tracker/?group_id=%d&atid=%d' % (self.sf_group_id, self.sf_artifact_id)
137 location_hbox = gtk.HBox(False, 4)
138 location_hbox.pack_start(gtk.Label(_('Bugs reports will be sent to:')), False, True, 0)
139 if hasattr(gtk, 'LinkButton'):
140 import browser
141 url_box = gtk.LinkButton(browse_url)
142 url_box.connect('clicked', lambda button: browser.open_in_browser(browse_url))
143 else:
144 url_box = gtk.Label(browse_url)
145 url_box.set_selectable(True)
146 location_hbox.pack_start(url_box, False, True, 0)
147 vbox.pack_start(location_hbox, False, True, 0)
149 self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
150 self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
151 self.set_default_response(gtk.RESPONSE_OK)
153 def resp(box, r):
154 if r == gtk.RESPONSE_OK:
155 text = ''
156 for title, buffer in self.frames:
157 start = buffer.get_start_iter()
158 end = buffer.get_end_iter()
159 text += '%s\n\n%s\n\n' % (title, buffer.get_text(start, end).strip())
160 title = 'Bug for %s' % iface.get_name()
161 self.report_bug(title, text)
162 self.destroy()
163 dialog.alert(self, "Your bug report has been sent. Thank you.",
164 type = gtk.MESSAGE_INFO)
165 else:
166 self.destroy()
167 self.connect('response', resp)
169 self.show_all()
171 def collect_output(self, buffer):
172 iter = buffer.get_end_iter()
173 buffer.place_cursor(iter)
175 if not self.policy.ready:
176 missing = [iface.uri for iface in self.policy.implementation if self.policy.implementation[iface] is None]
177 buffer.insert_at_cursor("Can't run: no version has been selected for:\n- " +
178 "\n- ".join(missing))
179 return
180 uncached = self.policy.get_uncached_implementations()
181 if uncached:
182 buffer.insert_at_cursor("Can't run: the chosen versions have not been downloaded yet. I need:\n\n- " +
183 "\n\n- " . join(['%s version %s\n (%s)' %(x[0].uri, x[1].get_version(), x[1].id) for x in uncached]))
184 return
186 from zeroinstall.injector import selections
187 sels = selections.Selections(self.policy)
188 doc = sels.toDOM()
190 self.hide()
191 try:
192 gtk.gdk.flush()
193 iter = buffer.get_end_iter()
194 buffer.place_cursor(iter)
196 # Tell 0launch to run the program
197 doc.documentElement.setAttribute('run-test', 'true')
198 payload = doc.toxml('utf-8')
199 sys.stdout.write(('Length:%8x\n' % len(payload)) + payload)
200 sys.stdout.flush()
202 reply = support.read_bytes(0, len('Length:') + 9)
203 assert reply.startswith('Length:')
204 test_output = support.read_bytes(0, int(reply.split(':', 1)[1], 16))
206 # Cope with invalid UTF-8
207 import codecs
208 decoder = codecs.getdecoder('utf-8')
209 data = decoder(test_output, 'replace')[0]
211 buffer.insert_at_cursor(data)
212 finally:
213 self.show()
215 def report_bug(self, title, text):
216 try:
217 import urllib
218 from urllib2 import urlopen
220 stream = urlopen('http://sourceforge.net/tracker/index.php',
221 urllib.urlencode({
222 'group_id': str(self.sf_group_id),
223 'atid': str(self.sf_artifact_id),
224 'func': 'postadd',
225 'is_private': '0',
226 'summary': title,
227 'details': text}))
228 stream.read()
229 stream.close()
230 except:
231 # Write to stderr in the hope that it doesn't get lost
232 print >>sys.stderr, "Error sending bug report: %s\n\n%s" % (title, text)
233 raise