1 # Copyright (C) 2009, Thomas Leonard
2 # See http://0install.net/0compile.html
4 from __future__
import print_function
11 from zeroinstall
import _
12 from zeroinstall
import support
14 def report_bug(driver
, 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()
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
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
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
49 chosen_iface
= driver
.config
.iface_cache
.get_interface(chosen_iface_uri
)
50 impls
= driver
.solver
.details
.get(chosen_iface
, None)
52 best
, reason
= impls
[0]
53 note
= 'best was %s, but: %s' % (best
, reason
)
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
)
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
)
71 class BugReporter(dialog
.Dialog
):
72 def __init__(self
, driver
, iface
, env
):
73 dialog
.Dialog
.__init
__(self
)
75 self
.set_title(_('Report a Bug'))
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):
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)
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
)
106 tv
.set_wrap_mode(gtk
.WRAP_WORD
)
109 tv
.get_buffer().insert_at_cursor(text
)
112 tv
.modify_font(pango
.FontDescription('mono'))
114 tv
.set_accepts_tab(False)
116 return swin
, tv
.get_buffer()
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'):
149 # url_box = gtk.LinkButton(browse_url, label = browse_url)
150 # url_box.connect('clicked', lambda button: browser.open_in_browser(browse_url))
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
)
162 if r
== gtk
.RESPONSE_OK
:
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())
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
)
174 dialog
.alert(None, _("Success: {msg}").format(msg
= message
),
175 type = gtk
.MESSAGE_INFO
)
179 self
.connect('response', resp
)
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()))
192 uncached
= self
.driver
.get_uncached_implementations()
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
]))
198 sels
= self
.driver
.solver
.selections
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
214 stdout
.write(('Length:%8x\n' % len(payload
)).encode('utf-8') + payload
)
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
223 decoder
= codecs
.getdecoder('utf-8')
224 data
= decoder(test_output
, 'replace')[0]
226 buffer.insert_at_cursor(data
)
230 def report_bug(self
, iface
, text
):
232 if sys
.version_info
[0] > 2:
233 from urllib
.request
import urlopen
234 from urllib
.parse
import urlencode
236 from urllib2
import urlopen
237 from urllib
import urlencode
241 'body': text
}).encode('utf-8')
242 stream
= urlopen('http://0install.net/api/report-bug/', data
= data
)
243 reply
= stream
.read().decode('utf-8')
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
)