Chenxiong Qi has proposed merging lp:~qcxhome/gtg/bugzilla-plugin-refactor into lp:gtg.
Requested reviews: Gtg developers (gtg) Related bugs: Bug #1096360 in Getting Things GNOME!: "Refactor Bugzilla plugin to support the differences of each supported Bugzilla service" https://bugs.launchpad.net/gtg/+bug/1096360 For more details, see: https://code.launchpad.net/~qcxhome/gtg/bugzilla-plugin-refactor/+merge/165611 -- https://code.launchpad.net/~qcxhome/gtg/bugzilla-plugin-refactor/+merge/165611 Your team Gtg developers is requested to review the proposed merge of lp:~qcxhome/gtg/bugzilla-plugin-refactor into lp:gtg.
=== modified file 'GTG/plugins/bugzilla/bug.py' --- GTG/plugins/bugzilla/bug.py 2013-02-25 08:12:02 +0000 +++ GTG/plugins/bugzilla/bug.py 2013-05-24 13:15:50 +0000 @@ -14,44 +14,69 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see <http://www.gnu.org/licenses/>. -# this handles old versions of pybugz as well as new ones -try: - from bugz import bugzilla - assert bugzilla -except: - import bugz as bugzilla - -# changed the default action to skip auth - - -class Bug: - - def __init__(self, base, nb): - # this also handles old versions of pybugz - try: - bugs = bugzilla.BugzillaProxy( - base, skip_auth=True).Bug.get({'ids': [nb, ], }) - except: - bugs = bugzilla.BugzillaProxy(base).Bug.get({'ids': [nb, ], }) - self.bug = bugs['bugs'][0] - - def get_title(self): +__all__ = ('BugFactory',) + + +class Bug(object): + + def __init__(self, bug): + ''' Initialize Bug object using bug object retrieved via Bugzilla + service XMLRPC + ''' + self.bug = bug + + @property + def summary(self): return self.bug['summary'] - def get_product(self): + @property + def product(self): return self.bug['product'] - def get_component(self): + @property + def description(self): + return self.bug['summary'] + + @property + def component(self): return self.bug['component'] - def get_description(self): - return self.bug['summary'] - -if __name__ == '__main__': - for bug in [Bug('https://bugzilla.gnome.org', '598354'), - Bug('https://bugs.freedesktop.org', '24120')]: - print "title:", bug.get_title() - print "product:", bug.get_product() - print "component:", bug.get_component() - print "description:", bug.get_description() - print "" + +class GnomeBug(Bug): + pass + + +class FreedesktopBug(Bug): + pass + + +class GentooBug(Bug): + pass + + +class MozillaBug(Bug): + pass + + +class SambaBug(Bug): + pass + + +class RedHatBug(Bug): + pass + + +bugs = { + 'bugzilla.gnome.org': GnomeBug, + 'bugs.freedesktop.org': FreedesktopBug, + 'bugzilla.mozilla.org': MozillaBug, + 'bugzilla.samba.org': SambaBug, + 'bugs.gentoo.org': GentooBug, + 'bugzilla.redhat.com': RedHatBug, +} + + +class BugFactory(object): + @staticmethod + def create(serviceDomain, bug): + return bugs[serviceDomain](bug) === modified file 'GTG/plugins/bugzilla/bugzilla.py' --- GTG/plugins/bugzilla/bugzilla.py 2013-02-25 07:35:07 +0000 +++ GTG/plugins/bugzilla/bugzilla.py 2013-05-24 13:15:50 +0000 @@ -15,19 +15,71 @@ # this program. If not, see <http://www.gnu.org/licenses/>. import gobject +import re import threading import xmlrpclib from urlparse import urlparse -from GTG.plugins.bugzilla.server import ServersStore -from GTG.plugins.bugzilla.bug import Bug +from services import BugzillaServiceFactory +from notification import send_notification + +__all__ = ('pluginBugzilla', ) + +bugIdPattern = re.compile('^\d+$') + + +class GetBugInformationTask(threading.Thread): + + def __init__(self, task, **kwargs): + ''' Initialize task data, where task is the GTG task object. ''' + self.task = task + super(GetBugInformationTask, self).__init__(**kwargs) + + def parseBugUrl(self, url): + r = urlparse(url) + queries = dict([item.split('=') for item in r.query.split('&')]) + return r.scheme, r.hostname, queries + + def run(self): + bug_url = self.task.get_title() + scheme, hostname, queries = self.parseBugUrl(bug_url) + + bug_id = queries.get('id', None) + if bugIdPattern.match(bug_id) is None: + # FIXME: make some sensable action instead of returning silently. + return + + try: + bugzillaService = BugzillaServiceFactory.create(scheme, hostname) + bug = bugzillaService.getBug(bug_id) + except xmlrpclib.Fault, err: + code = err.faultCode + if code == 100: # invalid bug ID + title = 'Invalid bug ID #%s' % bug_id + elif code == 101: # bug ID not exist + title = 'Bug #%s does not exist.' % bug_id + elif code == 102: # Access denied + title = 'Access denied to bug %s' % bug_url + else: # unrecoganized error code currently + title = err.faultString + + send_notification(bugzillaService.name, title) + except Exception, err: + send_notification(bugzillaService.name, err.message) + else: + title = '#%s: %s' % (bug_id, bug.summary) + gobject.idle_add(self.task.set_title, title) + text = "%s\n\n%s" % (bug_url, bug.description) + gobject.idle_add(self.task.set_text, text) + + tags = bugzillaService.getTags(bug) + if tags is not None and tags: + for tag in tags: + gobject.idle_add(self.task.add_tag, '@%s' % tag) class pluginBugzilla: - def __init__(self): - self.servers = ServersStore() - def activate(self, plugin_api): self.plugin_api = plugin_api self.connect_id = plugin_api.get_ui().connect( @@ -37,62 +89,11 @@ # this is a gobject callback that will block the Browser. # decoupling with a thread. All interaction with task and tags objects #(anything in a Tree) must be done with gobject.idle_add (invernizzi) - thread = threading.Thread(target=self.__analyze_task, - args=(task_id, )) - thread.setDaemon(True) - thread.start() - def __analyze_task(self, task_id): task = self.plugin_api.get_requester().get_task(task_id) - url = task.get_title() - r = urlparse(url) - if r.hostname is None: - return - - server = self.servers.get(r.hostname) - if server is None: - return - - base = '%s://%s/xmlrpc.cgi' % (r.scheme, server.name) - - # get the number of the bug - try: - nb = r.query.split('id=')[1] - except IndexError: - return - - try: - bug = Bug(base, nb) - except xmlrpclib.Fault, err: - code = err.faultCode - if code == 100: # invalid bug ID - title = 'Invalid bug ID #%s' % nb - elif code == 101: # bug ID not exist - title = 'Bug #%s does not exist.' % nb - elif code == 102: # Access denied - title = 'Access denied to bug #%s' % nb - else: # unrecoganized error code currently - title = err.faultString - old_title = task.get_title() - gobject.idle_add(task.set_title, title) - gobject.idle_add(task.set_text, old_title) - return - except: - return - - title = bug.get_title() - if title is None: - # can't find the title of the bug - return - - gobject.idle_add(task.set_title, '#%s: %s' % (nb, title)) - - text = "%s\n\n%s" % (url, bug.get_description()) - gobject.idle_add(task.set_text, text) - - tag = server.get_tag(bug) - if tag is not None: - gobject.idle_add(task.add_tag, '@%s' % tag) + bugTask = GetBugInformationTask(task) + bugTask.setDaemon(True) + bugTask.start() def deactivate(self, plugin_api): plugin_api.get_ui().disconnect(self.connect_id) === added file 'GTG/plugins/bugzilla/notification.py' --- GTG/plugins/bugzilla/notification.py 1970-01-01 00:00:00 +0000 +++ GTG/plugins/bugzilla/notification.py 2013-05-24 13:15:50 +0000 @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +''' +Notification is used to show messages to GTG users. +''' + +import atexit +import subprocess + +__all__ = ("send_notification", ) + +APP_NAME = "GTG" +# How many millisecond the notification area lasts +TIMEOUT = 3000 + + +def _notify_via_pynotify(title, message): + pynotify.init(APP_NAME) + nt = pynotify.Notification(title, message) + nt.set_timeout(TIMEOUT) + nt.show() + + +def _notify_via_notify_send(title, message): + cmd = "notify-send --app-name=%s --expire-time=%d \"%s\" \"%s\"" % ( + APP_NAME, TIMEOUT, title, message) + proc = subprocess.Popen(cmd, shell=True) + + +# A reference to the concrete handler that sends notification. +# By default, this reference is set to None in case all candidates are not +# available to keep silient when unexpected things happen. +_notify_handler = None +try: + # Primarily, pynotify is used to send notification. However, it might not + # appear in user's machine. So, we'll try another alternative. + import pynotify + _notify_handler = _notify_via_pynotify +except ImportError: + # The alternative is notify-send, which is a command line utility provided + # by libnotify package. + proc = subprocess.Popen("which notify-send", shell=True) + if proc.wait() == 0: + _notify_handler = _notify_via_notify_send + +def send_notification(title, message): + ''' A proxy to send notification + + When no notification utility is available, just keep silent. + ''' + + if _notify_handler is not None: + _notify_handler(title, message) + + +@atexit.register +def uinit_pynotify(): + pynotify.uninit() === removed file 'GTG/plugins/bugzilla/server.py' --- GTG/plugins/bugzilla/server.py 2012-07-13 17:24:28 +0000 +++ GTG/plugins/bugzilla/server.py 1970-01-01 00:00:00 +0000 @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2009 - Guillaume Desmottes <gdesm...@gnome.org> -# -# This program is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program. If not, see <http://www.gnu.org/licenses/>. - -SERVER_TAG_PRODUCT = 1 -SERVER_TAG_COMPONENT = 2 - - -class ServersStore: - - def __init__(self): - self.servers = {} - - # GNOME - server = Server('bugzilla.gnome.org') - server.tag = SERVER_TAG_PRODUCT - self.add(server) - - # freedesktop.org - server = Server('bugs.freedesktop.org') - server.tag = SERVER_TAG_COMPONENT - self.add(server) - - # Mozilla - server = Server('bugzilla.mozilla.org') - server.tag = SERVER_TAG_COMPONENT - self.add(server) - - # Samba - server = Server('bugzilla.samba.org') - server.tag = SERVER_TAG_COMPONENT - self.add(server) - - # GENTOO - server = Server('bugs.gentoo.org') - server.tag = SERVER_TAG_COMPONENT - self.add(server) - - def add(self, server): - self.servers[server.name] = server - - def get(self, name): - return self.servers.get(name) - - -class Server: - - def __init__(self, name): - self.name = name - self.tag = None - - def get_tag(self, bug): - if self.tag is None: - return None - elif self.tag == SERVER_TAG_PRODUCT: - return bug.get_product() - elif self.tag == SERVER_TAG_COMPONENT: - return bug.get_component() === added file 'GTG/plugins/bugzilla/services.py' --- GTG/plugins/bugzilla/services.py 1970-01-01 00:00:00 +0000 +++ GTG/plugins/bugzilla/services.py 2013-05-24 13:15:50 +0000 @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +# Remove dependence of bugz due to that plugin just needs get action and +# it is done by Python xmlrpclib simply enough. +from xmlrpclib import ServerProxy + +from bug import BugFactory + +__all__ = ('BugzillaServiceFactory',) + + +class BugzillaService(object): + name = 'Bugzilla Service' + enabled = True + tag_from = 'component' + + def __init__(self, scheme, domain): + self.scheme = scheme + self.domain = domain + + def buildXmlRpcServerUrl(self): + return '%(scheme)s://%(domain)s/xmlrpc.cgi' % { + 'scheme': self.scheme, 'domain': self.domain, + } + + def getProxy(self, server_url): + return ServerProxy(server_url) + + def getBug(self, bug_id): + server_url = self.buildXmlRpcServerUrl() + proxy = self.getProxy(server_url) + bugs = proxy.Bug.get({'ids': [bug_id, ]}) + return BugFactory.create(self.domain, bugs['bugs'][0]) + + def getTags(self, bug): + ''' Get a list of tags due to some bug attribute contains list rather + than a string in some bugzilla service. + ''' + tag_names = getattr(bug, self.tag_from, None) + if tag_names is None: + return [] + if not isinstance(tag_names, list): + return [tag_names] + return tag_names + + +class GnomeBugzilla(BugzillaService): + name = 'GNOME Bugzilla Service' + tag_from = 'product' + + +class FreedesktopBugzilla(BugzillaService): + ''' Bugzilla service of Freedesktop projects ''' + + name = 'Freedesktop Bugzilla Service' + +class GentooBugzilla(BugzillaService): + ''' Bugzilla service of Gentoo project ''' + + name = 'Gentoo Bugzilla Service' + +class MozillaBugzilla(BugzillaService): + ''' Bugzilla service of Mozilla products ''' + + name = 'Mozilla Bugzilla Service' + +class SambaBugzilla(BugzillaService): + ''' Bugzilla service of Samba project ''' + + enabled = False + name = 'Samba Bugzilla Service' + + +class RedHatBugzilla(BugzillaService): + ''' Bugzilla service provided by Red Hat ''' + + name = 'Red Hat Bugzilla Service' + +# Register bugzilla services manually, however store them in someplace and load +# them at once is better. +services = { + 'bugzilla.gnome.org': GnomeBugzilla, + 'bugs.freedesktop.org': FreedesktopBugzilla, + 'bugzilla.mozilla.org': MozillaBugzilla, + 'bugzilla.samba.org': SambaBugzilla, + 'bugs.gentoo.org': GentooBugzilla, + 'bugzilla.redhat.com': RedHatBugzilla, +} + + +class BugzillaServiceNotExist(Exception): + pass + + +class BugzillaServiceDisabled(Exception): + ''' Bugzilla service is disabled by user. ''' + + def __init__(self, domain, *args, **kwargs): + self.message = '%s is disabled.' % domain + super(BugzillaServiceDisabled, self).__init__(*args, **kwargs) + + +class BugzillaServiceFactory(object): + ''' Create a Bugzilla service using scheme and domain ''' + + @staticmethod + def create(scheme, domain): + if domain in services: + service = services[domain] + if not service.enabled: + raise BugzillaServiceDisabled(domain) + return services[domain](scheme, domain) + else: + raise BugzillaServiceNotExist(domain)
_______________________________________________ Mailing list: https://launchpad.net/~gtg Post to : gtg@lists.launchpad.net Unsubscribe : https://launchpad.net/~gtg More help : https://help.launchpad.net/ListHelp