3
# Author: Rodney Dawes <rodney.dawes@canonical.com>
5
# Copyright 2009 Canonical Ltd.
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.
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.
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/>.
19
from __future__ import with_statement
28
from ConfigParser import ConfigParser
31
from xdg.BaseDirectory import (
35
from lptools import config
42
ICON_NAME = "bzr-icon-64"
44
class Preferences(object):
47
self.filename = os.path.join(xdg_config_home, "lptools",
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))
54
if not self.config.has_section("lptools"):
55
self.config.add_section("lptools")
57
if self.config.has_option("lptools", "projects"):
58
self.projects = self.config.get("lptools",
59
"projects").split(",")
63
# gtk.ListStore for the dialog
65
self.dialog = self.__build_dialog()
67
def __build_dialog(self):
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)
74
area = dialog.get_content_area()
76
vbox = gtk.VBox(spacing=6)
77
vbox.set_border_width(12)
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)
88
hbox = gtk.HBox(spacing=12)
89
vbox.pack_start(hbox, expand=True, fill=True)
93
hbox.pack_start(misc, expand=False, fill=False)
96
scrollwin = gtk.ScrolledWindow()
97
scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
98
hbox.pack_start(scrollwin, expand=True, fill=True)
101
self.store = gtk.ListStore(str)
103
view = gtk.TreeView(self.store)
104
label.set_mnemonic_widget(view)
105
view.set_headers_visible(False)
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)
116
dialog.connect("close", self.__dialog_closed, 0)
117
dialog.connect("response", self.__dialog_closed)
121
def __edit_started(self, cell, editable, path):
124
def __edit_finished(self, sell, path, text):
125
if text == "Click here to add a project...":
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,])
133
def __dialog_closed(self, dialog, response):
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:
141
def show_dialog(self, parent):
142
if not self.dialog.get_transient_for():
143
self.dialog.set_transient_for(parent)
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,])
152
def server_display (server):
153
ret = subprocess.call(["./review-list"])
155
sys.stderr.write("Failed to run './review-list'\n")
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])
162
sys.stderr.write("Failed to run 'xdg-open %s'\n" % url)
168
self.cached_candidates = {}
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)
177
self.launchpad = config.get_launchpad("review-notifier")
178
self.me = self.launchpad.me
180
self.project_idle_ids = {}
182
self.config = Preferences()
184
if len(self.config.projects) == 0:
185
print "No Projects specified"
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)
194
self.indicators[project] = ind
196
pynotify.init("Review Notifier")
200
for project in self.config.projects:
201
self.project_idle_ids[project] = gobject.idle_add(self.project_idle, project)
205
def project_idle (self, project):
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
215
print "Project %s not found." % project
219
print "Project %s has no development focus." % project
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)
229
self.indicators[project].hide()
233
def landing_idle (self, project, c):
234
c_name = c.source_branch.unique_name
237
status = self.cached_candidates[c_name]
241
# If the proposal hasn't changed, get on with it
242
if status and status == c.queue_status:
245
self.cached_candidates[c_name] = c.queue_status
247
n = pynotify.Notification("Review Notification")
250
# Source and target branch URIs
251
source = c.source_branch.display_name
252
target = c.target_branch.display_name
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),
261
elif c.queue_status == "Approved":
262
# Branch was approved
263
n.update("Branch Approval",
264
"%s was approved for merging into %s." % (
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.""" % (
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,
283
print "%s status is %s." % (source, c.queue_status)
286
n.set_urgency(pynotify.URGENCY_LOW)
287
n.set_hint("x-canonical-append", "allow")
289
self.indicators[project].set_property_time("time", time())
294
self.id = gobject.timeout_add_seconds(5 * 60, self.timeout)
298
if __name__ == "__main__":
299
gobject.threads_init()
300
gtk.gdk.threads_init()
303
gtk.gdk.threads_enter()
305
gtk.gdk.threads_leave()
306
except KeyboardInterrupt: