~ubuntu-branches/ubuntu/quantal/lptools/quantal

« back to all changes in this revision

Viewing changes to bin/lp-review-notifier

  • Committer: Package Import Robot
  • Author(s): Jelmer Vernooij, Robert Collins, Jelmer Vernooij
  • Date: 2011-09-01 22:47:11 UTC
  • mfrom: (2.1.2 sid)
  • Revision ID: package-import@ubuntu.com-20110901224711-05u7hwne9anexqq9
Tags: 0.0.1~bzr28-1
[ Robert Collins ]
* Control tweaks.

[ Jelmer Vernooij ]
* New upstream snapshot.
 + Imports a few tools from ubuntu-dev-tools. Breaks/Replaces added.
* Add myself to Uploaders.
* Bump standards version to 3.9.2 (no changes).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
#
 
3
# Author: Rodney Dawes <rodney.dawes@canonical.com>
 
4
#
 
5
# Copyright 2009 Canonical Ltd.
 
6
#
 
7
# This program is free software: you can redistribute it and/or modify it
 
8
# under the terms of the GNU General Public License version 3, as published
 
9
# by the Free Software Foundation.
 
10
#
 
11
# This program is distributed in the hope that it will be useful, but
 
12
# WITHOUT ANY WARRANTY; without even the implied warranties of
 
13
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
14
# PURPOSE.  See the GNU General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License along
 
17
# with this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
 
 
19
from __future__ import with_statement
 
20
import os
 
21
import sys
 
22
 
 
23
import gobject
 
24
import gtk
 
25
import pygtk
 
26
import pynotify
 
27
 
 
28
from ConfigParser import ConfigParser
 
29
import subprocess
 
30
 
 
31
from xdg.BaseDirectory import (
 
32
    xdg_config_home,
 
33
    )
 
34
 
 
35
from lptools import config
 
36
 
 
37
pygtk.require('2.0')
 
38
 
 
39
import indicate
 
40
from time import time
 
41
 
 
42
ICON_NAME = "bzr-icon-64"
 
43
 
 
44
class Preferences(object):
 
45
 
 
46
      def __init__(self):
 
47
            self.filename = os.path.join(xdg_config_home, "lptools",
 
48
                                         "lptools.conf")
 
49
            self.config = ConfigParser()
 
50
            self.config.read(self.filename)
 
51
            if not os.path.isdir(os.path.dirname(self.filename)):
 
52
                  os.makedirs(os.path.dirname(self.filename))
 
53
 
 
54
            if not self.config.has_section("lptools"):
 
55
                  self.config.add_section("lptools")
 
56
 
 
57
            if self.config.has_option("lptools", "projects"):
 
58
                  self.projects = self.config.get("lptools",
 
59
                                                  "projects").split(",")
 
60
            else:
 
61
                  self.projects = []
 
62
 
 
63
            # gtk.ListStore for the dialog
 
64
            self.store = None
 
65
            self.dialog = self.__build_dialog()
 
66
 
 
67
      def __build_dialog(self):
 
68
            dialog = gtk.Dialog()
 
69
            dialog.set_title("Pending Reviews Preferences")
 
70
            dialog.set_destroy_with_parent(True)
 
71
            dialog.set_has_separator(False)
 
72
            dialog.set_default_size(240, 320)
 
73
 
 
74
            area = dialog.get_content_area()
 
75
 
 
76
            vbox = gtk.VBox(spacing=6)
 
77
            vbox.set_border_width(12)
 
78
            area.add(vbox)
 
79
            vbox.show()
 
80
 
 
81
            label = gtk.Label("<b>%s</b>" % "_Projects")
 
82
            label.set_use_underline(True)
 
83
            label.set_use_markup(True)
 
84
            label.set_alignment(0.0, 0.5)
 
85
            vbox.pack_start(label, expand=False, fill=False)
 
86
            label.show()
 
87
 
 
88
            hbox = gtk.HBox(spacing=12)
 
89
            vbox.pack_start(hbox, expand=True, fill=True)
 
90
            hbox.show()
 
91
 
 
92
            misc = gtk.Label()
 
93
            hbox.pack_start(misc, expand=False, fill=False)
 
94
            misc.show()
 
95
 
 
96
            scrollwin = gtk.ScrolledWindow()
 
97
            scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
98
            hbox.pack_start(scrollwin, expand=True, fill=True)
 
99
            scrollwin.show()
 
100
 
 
101
            self.store = gtk.ListStore(str)
 
102
 
 
103
            view = gtk.TreeView(self.store)
 
104
            label.set_mnemonic_widget(view)
 
105
            view.set_headers_visible(False)
 
106
            scrollwin.add(view)
 
107
            view.show()
 
108
 
 
109
            cell = gtk.CellRendererText()
 
110
            cell.set_property("editable", True)
 
111
            cell.connect("editing_started", self.__edit_started)
 
112
            cell.connect("edited", self.__edit_finished)
 
113
            col = gtk.TreeViewColumn("Project", cell, markup=0)
 
114
            view.append_column(col)
 
115
 
 
116
            dialog.connect("close", self.__dialog_closed, 0)
 
117
            dialog.connect("response", self.__dialog_closed)
 
118
 
 
119
            return dialog
 
120
 
 
121
      def __edit_started(self, cell, editable, path):
 
122
            return
 
123
 
 
124
      def __edit_finished(self, sell, path, text):
 
125
            if text == "Click here to add a project...":
 
126
                  return
 
127
            treeiter = self.store.get_iter_from_string(path)
 
128
            label = "<i>%s</i>" % "Click here to add a project..."
 
129
            self.store.set(treeiter, 0, label)
 
130
            self.projects.append(text)
 
131
            self.store.append([text,])
 
132
 
 
133
      def __dialog_closed(self, dialog, response):
 
134
            dialog.hide()
 
135
            if len(self.projects) > 0:
 
136
                  self.config.set("lptools", "projects",
 
137
                                  ",".join(self.projects))
 
138
                  with open(self.filename, "w+b") as f:
 
139
                        self.config.write(f)
 
140
 
 
141
      def show_dialog(self, parent):
 
142
            if not self.dialog.get_transient_for():
 
143
                  self.dialog.set_transient_for(parent)
 
144
            self.store.clear()
 
145
            text = "<i>%s</i>" % "Click here to add a project..."
 
146
            self.store.append([text,])
 
147
            if len(self.projects) != 0:
 
148
                  for project in self.projects:
 
149
                        self.store.append([project,])
 
150
            self.dialog.run()
 
151
 
 
152
def server_display (server):
 
153
    ret = subprocess.call(["./review-list"])
 
154
    if ret != 0:
 
155
        sys.stderr.write("Failed to run './review-list'\n")
 
156
 
 
157
def indicator_display (indicator):
 
158
    name = indicator.get_property("name")
 
159
    url = "http://code.launchpad.net/" + name + "/+activereviews"
 
160
    ret = subprocess.call(["xdg-open", url])
 
161
    if ret != 0:
 
162
        sys.stderr.write("Failed to run 'xdg-open %s'\n" % url)
 
163
 
 
164
class Main(object):
 
165
 
 
166
    def __init__(self):
 
167
        self.id = 0
 
168
        self.cached_candidates = {}
 
169
 
 
170
        self.indicators = { }
 
171
        server = indicate.indicate_server_ref_default()
 
172
        server.set_type("message.instant")
 
173
        server.set_desktop_file(os.path.join(os.getcwd(), "review-tools.desktop"))
 
174
        server.connect("server-display", server_display)
 
175
        server.show()
 
176
 
 
177
        self.launchpad = config.get_launchpad("review-notifier")
 
178
        self.me = self.launchpad.me
 
179
 
 
180
        self.project_idle_ids = {}
 
181
 
 
182
        self.config = Preferences()
 
183
 
 
184
        if len(self.config.projects) == 0:
 
185
            print "No Projects specified"
 
186
            sys.exit(1)
 
187
 
 
188
        for project in self.config.projects:
 
189
            ind = indicate.Indicator()
 
190
            ind.set_property("name", project)
 
191
            ind.set_property("count", "%d" % 0)
 
192
            ind.connect("user-display", indicator_display)
 
193
            ind.hide()
 
194
            self.indicators[project] = ind
 
195
 
 
196
        pynotify.init("Review Notifier")
 
197
        self.timeout()
 
198
 
 
199
    def timeout(self):
 
200
        for project in self.config.projects:
 
201
            self.project_idle_ids[project] = gobject.idle_add(self.project_idle, project)
 
202
 
 
203
        return True
 
204
 
 
205
    def project_idle (self, project):
 
206
        lp_project = None
 
207
        lp_focus = None
 
208
        try:
 
209
            lp_project = self.launchpad.projects[project]
 
210
            focus = lp_project.development_focus.branch
 
211
        except AttributeError:
 
212
            print "Project %s has no development focus." % project
 
213
            return False
 
214
        except KeyError:
 
215
            print "Project %s not found." % project
 
216
            return False
 
217
 
 
218
        if not focus:
 
219
            print "Project %s has no development focus." % project
 
220
            return False
 
221
 
 
222
        trunk = focus
 
223
 
 
224
        if trunk.landing_candidates:
 
225
            self.indicators[project].show()
 
226
            for c in trunk.landing_candidates:
 
227
                gobject.idle_add(self.landing_idle, project, c)
 
228
        else:
 
229
            self.indicators[project].hide()
 
230
 
 
231
        return False
 
232
 
 
233
    def landing_idle (self, project, c):
 
234
        c_name = c.source_branch.unique_name
 
235
        status = None
 
236
        try:
 
237
            status = self.cached_candidates[c_name]
 
238
        except KeyError:
 
239
            status = None
 
240
 
 
241
        # If the proposal hasn't changed, get on with it
 
242
        if status and status == c.queue_status:
 
243
            return False
 
244
 
 
245
        self.cached_candidates[c_name] = c.queue_status
 
246
 
 
247
        n = pynotify.Notification("Review Notification")
 
248
        updated = False
 
249
 
 
250
        # Source and target branch URIs
 
251
        source = c.source_branch.display_name
 
252
        target = c.target_branch.display_name
 
253
 
 
254
        if c.queue_status == "Needs review":
 
255
            # First time we see the branch
 
256
            n.update("Branch Proposal",
 
257
                     "%s has proposed merging %s into %s." % (
 
258
                    c.registrant.display_name, source, target),
 
259
                     ICON_NAME)
 
260
            updated = True
 
261
        elif c.queue_status == "Approved":
 
262
            # Branch was approved
 
263
            n.update("Branch Approval",
 
264
                     "%s was approved for merging into %s." % (
 
265
                    source, target),
 
266
                     ICON_NAME)
 
267
            udpated = True
 
268
        elif c.queue_status == "Rejected":
 
269
            # Branch was rejected
 
270
            n.update("Branch Rejected",
 
271
                     """The proposal to merge %s into %s has been rejected.""" % (
 
272
                    source, target),
 
273
                     ICON_NAME)
 
274
            updated = True
 
275
        elif c.queue_status == "Merged":
 
276
            # Code has landed in the target branch
 
277
            n.update("Branch Merged",
 
278
                     "%s has been merged into %s." % (source,
 
279
                                                      target),
 
280
                     ICON_NAME)
 
281
            updated = True
 
282
        else:
 
283
            print "%s status is %s." % (source, c.queue_status)
 
284
 
 
285
        if updated:
 
286
            n.set_urgency(pynotify.URGENCY_LOW)
 
287
            n.set_hint("x-canonical-append", "allow")
 
288
            n.show()
 
289
            self.indicators[project].set_property_time("time", time())
 
290
        else:
 
291
            n.close()
 
292
 
 
293
    def run(self):
 
294
        self.id = gobject.timeout_add_seconds(5 * 60, self.timeout)
 
295
        gtk.main()
 
296
 
 
297
 
 
298
if __name__ == "__main__":
 
299
      gobject.threads_init()
 
300
      gtk.gdk.threads_init()
 
301
      try:
 
302
            foo = Main()
 
303
            gtk.gdk.threads_enter()
 
304
            foo.run()
 
305
            gtk.gdk.threads_leave()
 
306
      except KeyboardInterrupt:
 
307
            gtk.main_quit()