GTG

Merge lp:~qcxhome/gtg/bugzilla-plugin-refactor into lp:~gtg/gtg/old-trunk

Proposed by Chenxiong Qi
Status: Merged
Merged at revision: 1300
Proposed branch: lp:~qcxhome/gtg/bugzilla-plugin-refactor
Merge into: lp:~gtg/gtg/old-trunk
Diff against target: 516 lines (+296/-164)
5 files modified
GTG/plugins/bugzilla/bug.py (+60/-35)
GTG/plugins/bugzilla/bugzilla.py (+60/-59)
GTG/plugins/bugzilla/notification.py (+59/-0)
GTG/plugins/bugzilla/server.py (+0/-70)
GTG/plugins/bugzilla/services.py (+117/-0)
To merge this branch: bzr merge lp:~qcxhome/gtg/bugzilla-plugin-refactor
Reviewer Review Type Date Requested Status
Izidor Matušov Approve
Review via email: mp+165611@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Izidor Matušov (izidor) wrote :

Hi,

thanks for your patch. Please fix the following (style) errors:

$ make pep8
GTG/plugins/bugzilla/notification.py:46:1: E302 expected 2 blank lines, found 1
GTG/plugins/bugzilla/services.py:57:1: E302 expected 2 blank lines, found 1
GTG/plugins/bugzilla/services.py:62:1: E302 expected 2 blank lines, found 1
GTG/plugins/bugzilla/services.py:67:1: E302 expected 2 blank lines, found 1

$ make pyflakes
GTG/plugins/bugzilla/notification.py:27: local variable 'proc' is assigned to but never used

review: Needs Fixing
1300. By Chenxiong Qi

FIX: code style errors

Revision history for this message
Chenxiong Qi (qcxhome) wrote :

Hi izidor,

I have pushed a commit to fix the code style errors. Do I need make another
merge proposal for this change in that branch page? Thanks!

Regards,
Chenxiong Qi
在 2013-6-2 下午4:06,"Izidor Matušov" <email address hidden>写道:

> Review: Needs Fixing
>
> Hi,
>
> thanks for your patch. Please fix the following (style) errors:
>
> $ make pep8
> GTG/plugins/bugzilla/notification.py:46:1: E302 expected 2 blank lines,
> found 1
> GTG/plugins/bugzilla/services.py:57:1: E302 expected 2 blank lines, found 1
> GTG/plugins/bugzilla/services.py:62:1: E302 expected 2 blank lines, found 1
> GTG/plugins/bugzilla/services.py:67:1: E302 expected 2 blank lines, found 1
>
> $ make pyflakes
> GTG/plugins/bugzilla/notification.py:27: local variable 'proc' is assigned
> to but never used
> --
>
> https://code.launchpad.net/~qcxhome/gtg/bugzilla-plugin-refactor/+merge/165611
> You are the owner of lp:~qcxhome/gtg/bugzilla-plugin-refactor.
>

Revision history for this message
Izidor Matušov (izidor) wrote :

Thank you for your patch :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'GTG/plugins/bugzilla/bug.py'
2--- GTG/plugins/bugzilla/bug.py 2013-02-25 08:12:02 +0000
3+++ GTG/plugins/bugzilla/bug.py 2013-06-04 14:16:49 +0000
4@@ -14,44 +14,69 @@
5 # You should have received a copy of the GNU General Public License along with
6 # this program. If not, see <http://www.gnu.org/licenses/>.
7
8-# this handles old versions of pybugz as well as new ones
9-try:
10- from bugz import bugzilla
11- assert bugzilla
12-except:
13- import bugz as bugzilla
14-
15-# changed the default action to skip auth
16-
17-
18-class Bug:
19-
20- def __init__(self, base, nb):
21- # this also handles old versions of pybugz
22- try:
23- bugs = bugzilla.BugzillaProxy(
24- base, skip_auth=True).Bug.get({'ids': [nb, ], })
25- except:
26- bugs = bugzilla.BugzillaProxy(base).Bug.get({'ids': [nb, ], })
27- self.bug = bugs['bugs'][0]
28-
29- def get_title(self):
30+__all__ = ('BugFactory',)
31+
32+
33+class Bug(object):
34+
35+ def __init__(self, bug):
36+ ''' Initialize Bug object using bug object retrieved via Bugzilla
37+ service XMLRPC
38+ '''
39+ self.bug = bug
40+
41+ @property
42+ def summary(self):
43 return self.bug['summary']
44
45- def get_product(self):
46+ @property
47+ def product(self):
48 return self.bug['product']
49
50- def get_component(self):
51+ @property
52+ def description(self):
53+ return self.bug['summary']
54+
55+ @property
56+ def component(self):
57 return self.bug['component']
58
59- def get_description(self):
60- return self.bug['summary']
61-
62-if __name__ == '__main__':
63- for bug in [Bug('https://bugzilla.gnome.org', '598354'),
64- Bug('https://bugs.freedesktop.org', '24120')]:
65- print "title:", bug.get_title()
66- print "product:", bug.get_product()
67- print "component:", bug.get_component()
68- print "description:", bug.get_description()
69- print ""
70+
71+class GnomeBug(Bug):
72+ pass
73+
74+
75+class FreedesktopBug(Bug):
76+ pass
77+
78+
79+class GentooBug(Bug):
80+ pass
81+
82+
83+class MozillaBug(Bug):
84+ pass
85+
86+
87+class SambaBug(Bug):
88+ pass
89+
90+
91+class RedHatBug(Bug):
92+ pass
93+
94+
95+bugs = {
96+ 'bugzilla.gnome.org': GnomeBug,
97+ 'bugs.freedesktop.org': FreedesktopBug,
98+ 'bugzilla.mozilla.org': MozillaBug,
99+ 'bugzilla.samba.org': SambaBug,
100+ 'bugs.gentoo.org': GentooBug,
101+ 'bugzilla.redhat.com': RedHatBug,
102+}
103+
104+
105+class BugFactory(object):
106+ @staticmethod
107+ def create(serviceDomain, bug):
108+ return bugs[serviceDomain](bug)
109
110=== modified file 'GTG/plugins/bugzilla/bugzilla.py'
111--- GTG/plugins/bugzilla/bugzilla.py 2013-02-25 07:35:07 +0000
112+++ GTG/plugins/bugzilla/bugzilla.py 2013-06-04 14:16:49 +0000
113@@ -15,19 +15,71 @@
114 # this program. If not, see <http://www.gnu.org/licenses/>.
115
116 import gobject
117+import re
118 import threading
119 import xmlrpclib
120 from urlparse import urlparse
121
122-from GTG.plugins.bugzilla.server import ServersStore
123-from GTG.plugins.bugzilla.bug import Bug
124+from services import BugzillaServiceFactory
125+from notification import send_notification
126+
127+__all__ = ('pluginBugzilla', )
128+
129+bugIdPattern = re.compile('^\d+$')
130+
131+
132+class GetBugInformationTask(threading.Thread):
133+
134+ def __init__(self, task, **kwargs):
135+ ''' Initialize task data, where task is the GTG task object. '''
136+ self.task = task
137+ super(GetBugInformationTask, self).__init__(**kwargs)
138+
139+ def parseBugUrl(self, url):
140+ r = urlparse(url)
141+ queries = dict([item.split('=') for item in r.query.split('&')])
142+ return r.scheme, r.hostname, queries
143+
144+ def run(self):
145+ bug_url = self.task.get_title()
146+ scheme, hostname, queries = self.parseBugUrl(bug_url)
147+
148+ bug_id = queries.get('id', None)
149+ if bugIdPattern.match(bug_id) is None:
150+ # FIXME: make some sensable action instead of returning silently.
151+ return
152+
153+ try:
154+ bugzillaService = BugzillaServiceFactory.create(scheme, hostname)
155+ bug = bugzillaService.getBug(bug_id)
156+ except xmlrpclib.Fault, err:
157+ code = err.faultCode
158+ if code == 100: # invalid bug ID
159+ title = 'Invalid bug ID #%s' % bug_id
160+ elif code == 101: # bug ID not exist
161+ title = 'Bug #%s does not exist.' % bug_id
162+ elif code == 102: # Access denied
163+ title = 'Access denied to bug %s' % bug_url
164+ else: # unrecoganized error code currently
165+ title = err.faultString
166+
167+ send_notification(bugzillaService.name, title)
168+ except Exception, err:
169+ send_notification(bugzillaService.name, err.message)
170+ else:
171+ title = '#%s: %s' % (bug_id, bug.summary)
172+ gobject.idle_add(self.task.set_title, title)
173+ text = "%s\n\n%s" % (bug_url, bug.description)
174+ gobject.idle_add(self.task.set_text, text)
175+
176+ tags = bugzillaService.getTags(bug)
177+ if tags is not None and tags:
178+ for tag in tags:
179+ gobject.idle_add(self.task.add_tag, '@%s' % tag)
180
181
182 class pluginBugzilla:
183
184- def __init__(self):
185- self.servers = ServersStore()
186-
187 def activate(self, plugin_api):
188 self.plugin_api = plugin_api
189 self.connect_id = plugin_api.get_ui().connect(
190@@ -37,62 +89,11 @@
191 # this is a gobject callback that will block the Browser.
192 # decoupling with a thread. All interaction with task and tags objects
193 #(anything in a Tree) must be done with gobject.idle_add (invernizzi)
194- thread = threading.Thread(target=self.__analyze_task,
195- args=(task_id, ))
196- thread.setDaemon(True)
197- thread.start()
198
199- def __analyze_task(self, task_id):
200 task = self.plugin_api.get_requester().get_task(task_id)
201- url = task.get_title()
202- r = urlparse(url)
203- if r.hostname is None:
204- return
205-
206- server = self.servers.get(r.hostname)
207- if server is None:
208- return
209-
210- base = '%s://%s/xmlrpc.cgi' % (r.scheme, server.name)
211-
212- # get the number of the bug
213- try:
214- nb = r.query.split('id=')[1]
215- except IndexError:
216- return
217-
218- try:
219- bug = Bug(base, nb)
220- except xmlrpclib.Fault, err:
221- code = err.faultCode
222- if code == 100: # invalid bug ID
223- title = 'Invalid bug ID #%s' % nb
224- elif code == 101: # bug ID not exist
225- title = 'Bug #%s does not exist.' % nb
226- elif code == 102: # Access denied
227- title = 'Access denied to bug #%s' % nb
228- else: # unrecoganized error code currently
229- title = err.faultString
230- old_title = task.get_title()
231- gobject.idle_add(task.set_title, title)
232- gobject.idle_add(task.set_text, old_title)
233- return
234- except:
235- return
236-
237- title = bug.get_title()
238- if title is None:
239- # can't find the title of the bug
240- return
241-
242- gobject.idle_add(task.set_title, '#%s: %s' % (nb, title))
243-
244- text = "%s\n\n%s" % (url, bug.get_description())
245- gobject.idle_add(task.set_text, text)
246-
247- tag = server.get_tag(bug)
248- if tag is not None:
249- gobject.idle_add(task.add_tag, '@%s' % tag)
250+ bugTask = GetBugInformationTask(task)
251+ bugTask.setDaemon(True)
252+ bugTask.start()
253
254 def deactivate(self, plugin_api):
255 plugin_api.get_ui().disconnect(self.connect_id)
256
257=== added file 'GTG/plugins/bugzilla/notification.py'
258--- GTG/plugins/bugzilla/notification.py 1970-01-01 00:00:00 +0000
259+++ GTG/plugins/bugzilla/notification.py 2013-06-04 14:16:49 +0000
260@@ -0,0 +1,59 @@
261+# -*- coding: utf-8 -*-
262+
263+'''
264+Notification is used to show messages to GTG users.
265+'''
266+
267+import atexit
268+import subprocess
269+
270+__all__ = ("send_notification", )
271+
272+APP_NAME = "GTG"
273+# How many millisecond the notification area lasts
274+TIMEOUT = 3000
275+
276+
277+def _notify_via_pynotify(title, message):
278+ pynotify.init(APP_NAME)
279+ nt = pynotify.Notification(title, message)
280+ nt.set_timeout(TIMEOUT)
281+ nt.show()
282+
283+
284+def _notify_via_notify_send(title, message):
285+ cmd = "notify-send --app-name=%s --expire-time=%d \"%s\" \"%s\"" % (
286+ APP_NAME, TIMEOUT, title, message)
287+ subprocess.Popen(cmd, shell=True)
288+
289+
290+# A reference to the concrete handler that sends notification.
291+# By default, this reference is set to None in case all candidates are not
292+# available to keep silient when unexpected things happen.
293+_notify_handler = None
294+try:
295+ # Primarily, pynotify is used to send notification. However, it might not
296+ # appear in user's machine. So, we'll try another alternative.
297+ import pynotify
298+ _notify_handler = _notify_via_pynotify
299+except ImportError:
300+ # The alternative is notify-send, which is a command line utility provided
301+ # by libnotify package.
302+ proc = subprocess.Popen("which notify-send", shell=True)
303+ if proc.wait() == 0:
304+ _notify_handler = _notify_via_notify_send
305+
306+
307+def send_notification(title, message):
308+ ''' A proxy to send notification
309+
310+ When no notification utility is available, just keep silent.
311+ '''
312+
313+ if _notify_handler is not None:
314+ _notify_handler(title, message)
315+
316+
317+@atexit.register
318+def uinit_pynotify():
319+ pynotify.uninit()
320
321=== removed file 'GTG/plugins/bugzilla/server.py'
322--- GTG/plugins/bugzilla/server.py 2012-07-13 17:24:28 +0000
323+++ GTG/plugins/bugzilla/server.py 1970-01-01 00:00:00 +0000
324@@ -1,70 +0,0 @@
325-# -*- coding: utf-8 -*-
326-# Copyright (c) 2009 - Guillaume Desmottes <gdesmott@gnome.org>
327-#
328-# This program is free software: you can redistribute it and/or modify it under
329-# the terms of the GNU General Public License as published by the Free Software
330-# Foundation, either version 3 of the License, or (at your option) any later
331-# version.
332-#
333-# This program is distributed in the hope that it will be useful, but WITHOUT
334-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
335-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
336-# details.
337-#
338-# You should have received a copy of the GNU General Public License along with
339-# this program. If not, see <http://www.gnu.org/licenses/>.
340-
341-SERVER_TAG_PRODUCT = 1
342-SERVER_TAG_COMPONENT = 2
343-
344-
345-class ServersStore:
346-
347- def __init__(self):
348- self.servers = {}
349-
350- # GNOME
351- server = Server('bugzilla.gnome.org')
352- server.tag = SERVER_TAG_PRODUCT
353- self.add(server)
354-
355- # freedesktop.org
356- server = Server('bugs.freedesktop.org')
357- server.tag = SERVER_TAG_COMPONENT
358- self.add(server)
359-
360- # Mozilla
361- server = Server('bugzilla.mozilla.org')
362- server.tag = SERVER_TAG_COMPONENT
363- self.add(server)
364-
365- # Samba
366- server = Server('bugzilla.samba.org')
367- server.tag = SERVER_TAG_COMPONENT
368- self.add(server)
369-
370- # GENTOO
371- server = Server('bugs.gentoo.org')
372- server.tag = SERVER_TAG_COMPONENT
373- self.add(server)
374-
375- def add(self, server):
376- self.servers[server.name] = server
377-
378- def get(self, name):
379- return self.servers.get(name)
380-
381-
382-class Server:
383-
384- def __init__(self, name):
385- self.name = name
386- self.tag = None
387-
388- def get_tag(self, bug):
389- if self.tag is None:
390- return None
391- elif self.tag == SERVER_TAG_PRODUCT:
392- return bug.get_product()
393- elif self.tag == SERVER_TAG_COMPONENT:
394- return bug.get_component()
395
396=== added file 'GTG/plugins/bugzilla/services.py'
397--- GTG/plugins/bugzilla/services.py 1970-01-01 00:00:00 +0000
398+++ GTG/plugins/bugzilla/services.py 2013-06-04 14:16:49 +0000
399@@ -0,0 +1,117 @@
400+# -*- coding: utf-8 -*-
401+
402+# Remove dependence of bugz due to that plugin just needs get action and
403+# it is done by Python xmlrpclib simply enough.
404+from xmlrpclib import ServerProxy
405+
406+from bug import BugFactory
407+
408+__all__ = ('BugzillaServiceFactory',)
409+
410+
411+class BugzillaService(object):
412+ name = 'Bugzilla Service'
413+ enabled = True
414+ tag_from = 'component'
415+
416+ def __init__(self, scheme, domain):
417+ self.scheme = scheme
418+ self.domain = domain
419+
420+ def buildXmlRpcServerUrl(self):
421+ return '%(scheme)s://%(domain)s/xmlrpc.cgi' % {
422+ 'scheme': self.scheme, 'domain': self.domain,
423+ }
424+
425+ def getProxy(self, server_url):
426+ return ServerProxy(server_url)
427+
428+ def getBug(self, bug_id):
429+ server_url = self.buildXmlRpcServerUrl()
430+ proxy = self.getProxy(server_url)
431+ bugs = proxy.Bug.get({'ids': [bug_id, ]})
432+ return BugFactory.create(self.domain, bugs['bugs'][0])
433+
434+ def getTags(self, bug):
435+ ''' Get a list of tags due to some bug attribute contains list rather
436+ than a string in some bugzilla service.
437+ '''
438+ tag_names = getattr(bug, self.tag_from, None)
439+ if tag_names is None:
440+ return []
441+ if not isinstance(tag_names, list):
442+ return [tag_names]
443+ return tag_names
444+
445+
446+class GnomeBugzilla(BugzillaService):
447+ name = 'GNOME Bugzilla Service'
448+ tag_from = 'product'
449+
450+
451+class FreedesktopBugzilla(BugzillaService):
452+ ''' Bugzilla service of Freedesktop projects '''
453+
454+ name = 'Freedesktop Bugzilla Service'
455+
456+
457+class GentooBugzilla(BugzillaService):
458+ ''' Bugzilla service of Gentoo project '''
459+
460+ name = 'Gentoo Bugzilla Service'
461+
462+
463+class MozillaBugzilla(BugzillaService):
464+ ''' Bugzilla service of Mozilla products '''
465+
466+ name = 'Mozilla Bugzilla Service'
467+
468+
469+class SambaBugzilla(BugzillaService):
470+ ''' Bugzilla service of Samba project '''
471+
472+ enabled = False
473+ name = 'Samba Bugzilla Service'
474+
475+
476+class RedHatBugzilla(BugzillaService):
477+ ''' Bugzilla service provided by Red Hat '''
478+
479+ name = 'Red Hat Bugzilla Service'
480+
481+# Register bugzilla services manually, however store them in someplace and load
482+# them at once is better.
483+services = {
484+ 'bugzilla.gnome.org': GnomeBugzilla,
485+ 'bugs.freedesktop.org': FreedesktopBugzilla,
486+ 'bugzilla.mozilla.org': MozillaBugzilla,
487+ 'bugzilla.samba.org': SambaBugzilla,
488+ 'bugs.gentoo.org': GentooBugzilla,
489+ 'bugzilla.redhat.com': RedHatBugzilla,
490+}
491+
492+
493+class BugzillaServiceNotExist(Exception):
494+ pass
495+
496+
497+class BugzillaServiceDisabled(Exception):
498+ ''' Bugzilla service is disabled by user. '''
499+
500+ def __init__(self, domain, *args, **kwargs):
501+ self.message = '%s is disabled.' % domain
502+ super(BugzillaServiceDisabled, self).__init__(*args, **kwargs)
503+
504+
505+class BugzillaServiceFactory(object):
506+ ''' Create a Bugzilla service using scheme and domain '''
507+
508+ @staticmethod
509+ def create(scheme, domain):
510+ if domain in services:
511+ service = services[domain]
512+ if not service.enabled:
513+ raise BugzillaServiceDisabled(domain)
514+ return services[domain](scheme, domain)
515+ else:
516+ raise BugzillaServiceNotExist(domain)

Subscribers

People subscribed via source and target branches

to status/vote changes: