gtg team mailing list archive
-
gtg team
-
Mailing list archive
-
Message #03829
[Merge] lp:~qcxhome/gtg/bugzilla-plugin-refactor into lp:gtg
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 <gdesmott@xxxxxxxxx>
-#
-# 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)