Merge lp:~mate-ob/gm-notify/multi-account into lp:gm-notify
- multi-account
- Merge into trunk
Proposed by
Mateusz Balbus
Status: | Merged |
---|---|
Approved by: | Mateusz Balbus |
Approved revision: | 117 |
Merged at revision: | 81 |
Proposed branch: | lp:~mate-ob/gm-notify/multi-account |
Merge into: | lp:gm-notify |
Prerequisite: | lp:~jconti/gm-notify/messaging-menu |
Diff against target: |
2127 lines (+1146/-382) 17 files modified
README (+23/-10) account_config.py (+197/-0) account_settings_provider.py (+128/-0) data/net.launchpad.gm-notify.gschema.xml (+34/-22) debian/changelog (+44/-0) debian/compat (+1/-1) debian/control (+23/-10) debian/copyright (+34/-19) debian/rules (+4/-12) debian/source/format (+1/-0) gm-config.ui (+3/-54) gm-list.ui (+224/-0) gm-notify (+188/-91) gm-notify-config (+76/-127) gm_notify_keyring.py (+48/-5) gtalk.py (+113/-27) setup.py (+5/-4) |
To merge this branch: | bzr merge lp:~mate-ob/gm-notify/multi-account |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
gm-notify Maintainers | Pending | ||
Review via email: mp+279670@code.launchpad.net |
Commit message
Description of the change
Changes:
- Added multi-account support
- Added support for multiple ports (default port 443)
- Added error messages
- Refactored the code
- Made app a single instance
- Added support for MP3 sound files
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'README' |
2 | --- README 2010-09-18 13:06:25 +0000 |
3 | +++ README 2015-12-05 00:25:32 +0000 |
4 | @@ -1,4 +1,5 @@ |
5 | -gm-notify 0.10.3 README |
6 | +gm-notify 1.0 README |
7 | + |
8 | ------------------------------------ |
9 | |
10 | QUICK INSTALLATION: |
11 | @@ -9,56 +10,66 @@ |
12 | The Configuration-Dialog is placed unter System-Settings-GMail Notifier Configuration. |
13 | |
14 | DEPENDENCIES: |
15 | + |
16 | - python-indicate |
17 | - python-notify |
18 | -- python-gst0.10 |
19 | +- gir1.2-gstreamer-1.0 |
20 | +- python-glade2 |
21 | - python-gtk2 |
22 | - python-gconf |
23 | - python-twisted-words |
24 | - python-gnomekeyring |
25 | |
26 | install via |
27 | -sudo apt-get install python-indicate python-notify python-gst0.10 python-gtk2 python-gconf python-twisted-words python-gnomekeyring |
28 | +sudo apt-get install python-indicate python-notify gir1.2-gstreamer-1.0 python-glade2 python-gtk2 python-gconf python-twisted-words python-gnomekeyring |
29 | |
30 | ------------------------------------ |
31 | |
32 | RELEASE NOTES: |
33 | |
34 | -v0.10.3: |
35 | -- Fixed CPU Usage Bug occuring on Maverick |
36 | -- Included Bulgarian Translation |
37 | -- Correct Priority Inbox usage |
38 | -- Mutt Label support |
39 | +v1.0: |
40 | + |
41 | +- Added multi-account support |
42 | +- Added support for multiple ports (default port 443) |
43 | +- Added error messages |
44 | +- Refactored the code |
45 | +- Made app a single instance |
46 | |
47 | v0.10.2: |
48 | + |
49 | - Added Checkbox to ignore the inbox |
50 | - fixed autorun |
51 | - fixed some other small bugs |
52 | |
53 | v0.10.1: |
54 | + |
55 | - Configurable autorun |
56 | - Usage of Ubuntu's default mail icon |
57 | - Fix of a displaying bug |
58 | - Ignoring the inbox is possible (see http://bleedingpaper.com/gm-notify) |
59 | |
60 | v0.10: |
61 | + |
62 | - Written for Lucid 10.04 (no warranty that older versions will be supported) |
63 | - Finally switched to Google Talk backend for instant mail notification (hopefully this time it'll last longer ;-) ) |
64 | - Labels with a count of 0 will be hidden (except Inbox) |
65 | - Sound properties work again |
66 | |
67 | v0.9: |
68 | + |
69 | - Switched to IMAP backend (twisted), now allowing to check different labels |
70 | - allows to choose if it should start webinterface or native client |
71 | - Fixed many bugs/crashes |
72 | |
73 | v0.8: |
74 | + |
75 | - included Danish translation |
76 | - integrated with GNOME Sound Framework. |
77 | - Fixed Bug #367242 |
78 | - warn when using small check intervals |
79 | |
80 | v0.7: |
81 | + |
82 | - included (old) Catalan translation |
83 | - German and English is completely translated (I hope my English weren't to bad ;-) ) |
84 | - added a graphical configuration interface |
85 | @@ -76,9 +87,11 @@ |
86 | Concept and code taken from cgmail |
87 | implemented by Sassur <sassur@gmail.com> |
88 | |
89 | -all the rest is written 2009-2010 by Alexander Hungenberg |
90 | +all the rest developed and maintained by: |
91 | +2009-2010 by Alexander Hungenberg |
92 | +2015 Mateusz Balbus |
93 | |
94 | ------------------------------------------------------------------------- |
95 | +------------------------------------ |
96 | |
97 | LICENSE: |
98 | |
99 | |
100 | === added file 'account_config.py' |
101 | --- account_config.py 1970-01-01 00:00:00 +0000 |
102 | +++ account_config.py 2015-12-05 00:25:32 +0000 |
103 | @@ -0,0 +1,197 @@ |
104 | +#!/usr/bin/env python |
105 | +# -*- coding: utf-8 -*- |
106 | +# account_config.py v0.10.3 |
107 | +# Provides settins to GMail notify |
108 | +# |
109 | +# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com> |
110 | +# |
111 | +# This program is free software: you can redistribute it and/or modify |
112 | +# it under the terms of the GNU General Public License as published by |
113 | +# the Free Software Foundation, either version 3 of the License, or |
114 | +# (at your option) any later version. |
115 | +# |
116 | +# This program is distributed in the hope that it will be useful, |
117 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
118 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
119 | +# GNU General Public License for more details. |
120 | +# |
121 | +# You should have received a copy of the GNU General Public License |
122 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
123 | +from __future__ import print_function |
124 | + |
125 | +import os |
126 | +import gettext |
127 | + |
128 | +from gi.repository import Gio, Gtk |
129 | +from twisted.words.protocols.jabber import jid |
130 | + |
131 | +from gtalk import MailChecker |
132 | +import account_settings_provider |
133 | + |
134 | +_ = gettext.translation('gm-notify', fallback=True).ugettext |
135 | + |
136 | +class AccountConfig: |
137 | + def __init__(self, keys, creds): |
138 | + self.keys = keys |
139 | + self.creds = creds |
140 | + |
141 | + def init_window(self, parent): |
142 | + if os.path.exists("gm-config.ui"): |
143 | + builder_file = "gm-config.ui" |
144 | + elif os.path.exists("/usr/local/share/gm-notify/gm-config.ui"): |
145 | + builder_file = "/usr/local/share/gm-notify/gm-config.ui" |
146 | + elif os.path.exists("/usr/share/gm-notify/gm-config.ui"): |
147 | + builder_file = "/usr/share/gm-notify/gm-config.ui" |
148 | + |
149 | + self.wTree = Gtk.Builder.new() |
150 | + self.wTree.add_from_file(builder_file) |
151 | + self.wTree.set_translation_domain("gm-notify") |
152 | + self.window = self.wTree.get_object("gmnotify_add_account") |
153 | + |
154 | + self.window.set_transient_for(parent) |
155 | + self.window.set_modal(True) |
156 | + self.window.set_destroy_with_parent(True) |
157 | + |
158 | + self.window.show_all() |
159 | + |
160 | + self.wTree.get_object("notebook_main").set_current_page(0) |
161 | + |
162 | + self.input_user = self.wTree.get_object("input_user") |
163 | + self.input_password = self.wTree.get_object("input_password") |
164 | + self.image_credentials = self.wTree.get_object("image_credentials") |
165 | + self.label_credentials = self.wTree.get_object("label_credentials") |
166 | + self.button_apply = self.wTree.get_object("button_apply") |
167 | + |
168 | + self.window.connect("delete_event", self.close) |
169 | + self.wTree.get_object("button_close").connect("clicked", self.close) |
170 | + self.button_apply.connect("clicked", self.apply) |
171 | + self.input_password.connect("focus-out-event", self.check_credentials) |
172 | + self.input_user.connect("focus-out-event", self.check_user) |
173 | + self.wTree.get_object("checkbutton_sound").connect("toggled", self.on_checkbutton_sound_toggled) |
174 | + |
175 | + ##### |
176 | + # Init with stored values |
177 | + ##### |
178 | + |
179 | + # Credentials |
180 | + |
181 | + settings_provider = account_settings_provider.create_settings_provider(self.creds.username) |
182 | + |
183 | + if self.creds.username: |
184 | + self.input_user.set_text(self.creds.username) |
185 | + self.input_user.set_sensitive(False) |
186 | + |
187 | + self.input_password.set_text(self.creds.password) |
188 | + |
189 | + self.api = MailChecker("", "", settings_provider) |
190 | + self.api.setOnAuthSucceeded(self.credentials_valid) |
191 | + self.api.setOnAuthFailed(self.credentials_invalid) |
192 | + self.api.setOnConnectionErrorCB(self.connection_error) |
193 | + |
194 | + self.check_credentials(None, None) |
195 | + |
196 | + # Sound |
197 | + self.wTree.get_object("checkbutton_sound").set_active(settings_provider.retrieve_sound_enabled()) |
198 | + sound_file = settings_provider.retrieve_sound_file() |
199 | + if sound_file: |
200 | + self.wTree.get_object("fcbutton_sound").set_filename(sound_file) |
201 | + self.on_checkbutton_sound_toggled(self.wTree.get_object("checkbutton_sound")) |
202 | + |
203 | + # ClickAction |
204 | + if settings_provider.retrieve_use_mail_client(): |
205 | + self.wTree.get_object("radiobutton_openclient").set_active(True) |
206 | + else: |
207 | + self.wTree.get_object("radiobutton_openweb").set_active(True) |
208 | + |
209 | + # Mailboxes |
210 | + self.wTree.get_object("checkbutton_inbox").set_active(settings_provider.retrieve_ignore_inbox()) |
211 | + labels = settings_provider.retrieve_labels() |
212 | + self.wTree.get_object("entry_labels").set_text(", ".join(labels)) |
213 | + |
214 | + return self.window |
215 | + |
216 | + def close(self, widget = None, event = None): |
217 | + if self.api.is_running(): |
218 | + self.api.die() |
219 | + self.window.close() |
220 | + |
221 | + def apply(self, widget): |
222 | + self.save() |
223 | + self.close() |
224 | + |
225 | + def save(self): |
226 | + '''saves the entered data and closes the app''' |
227 | + # Credentials |
228 | + self.keys.delete_credentials(self.creds.username) |
229 | + |
230 | + user = self.input_user.get_text() |
231 | + self.keys.set_credentials(user, |
232 | + self.input_password.get_text()) |
233 | + |
234 | + settings_provider = account_settings_provider.create_settings_provider(user) |
235 | + # Mailboxes |
236 | + labels = [] |
237 | + for label in self.wTree.get_object("entry_labels").get_text().split(","): |
238 | + labels.append(label.strip()) |
239 | + settings_provider.save_labels(labels) |
240 | + settings_provider.save_ignore_inbox(self.wTree.get_object("checkbutton_inbox").get_active()) |
241 | + |
242 | + # ClickAction |
243 | + settings_provider.save_use_mail_client(self.wTree.get_object("radiobutton_openclient").get_active()) |
244 | + |
245 | + # Port |
246 | + settings_provider.save_preferred_port(settings_provider.retrieve_preferred_port()) |
247 | + |
248 | + # Soundfile |
249 | + if self.wTree.get_object("checkbutton_sound").get_active() and self.wTree.get_object("fcbutton_sound").get_filename(): |
250 | + settings_provider.save_sound_enabled(True) |
251 | + settings_provider.save_sound_file(str(self.wTree.get_object("fcbutton_sound").get_filename())) |
252 | + else: |
253 | + settings_provider.save_sound_enabled(False) |
254 | + |
255 | + def on_checkbutton_sound_toggled(self, widget): |
256 | + self.wTree.get_object("fcbutton_sound").set_sensitive(self.wTree.get_object("checkbutton_sound").get_active()) |
257 | + |
258 | + def check_user(self, widget, event): |
259 | + user = self.input_user.get_text() |
260 | + if not self.has_mail_postfix(user): |
261 | + self.input_user.set_text(user + "@gmail.com") |
262 | + |
263 | + def has_mail_postfix(self, user): |
264 | + return len(user) == 0 or "@" in user |
265 | + |
266 | + def check_credentials(self, widget, event, data=None): |
267 | + '''check if the given credentials are valid''' |
268 | + |
269 | + self.button_apply.set_sensitive(False) |
270 | + |
271 | + # Change status text and disable input fields |
272 | + if self.input_user.get_text() and self.input_password.get_text(): |
273 | + self.image_credentials.set_from_file("/usr/share/gm-notify/checking.gif") |
274 | + self.label_credentials.set_text(_("checking...")) |
275 | + self.input_user.set_sensitive(False) |
276 | + self.input_password.set_sensitive(False) |
277 | + |
278 | + self.api.jid = jid.JID(self.input_user.get_text()) |
279 | + self.api.password = self.input_password.get_text() |
280 | + self.api.connect() |
281 | + return False |
282 | + |
283 | + def credentials_valid(self, username): |
284 | + self.on_credentials_checked("gtk-yes", "Valid credentials", True) |
285 | + |
286 | + def credentials_invalid(self, username, reason): |
287 | + self.on_credentials_checked("gtk-stop", "Invalid credentials") |
288 | + |
289 | + def connection_error(self, username, reason): |
290 | + self.on_credentials_checked("gtk-stop", "Connection error") |
291 | + |
292 | + def on_credentials_checked(self, icon_name, text, valid = False): |
293 | + self.image_credentials.set_from_icon_name(icon_name, Gtk.IconSize.MENU) |
294 | + self.label_credentials.set_text(_(text)) |
295 | + self.input_user.set_sensitive(not bool(self.creds.username)) |
296 | + self.input_password.set_sensitive(True) |
297 | + self.button_apply.set_sensitive(valid) |
298 | + |
299 | + self.api.die() |
300 | + |
301 | \ No newline at end of file |
302 | |
303 | === added file 'account_settings_provider.py' |
304 | --- account_settings_provider.py 1970-01-01 00:00:00 +0000 |
305 | +++ account_settings_provider.py 2015-12-05 00:25:32 +0000 |
306 | @@ -0,0 +1,128 @@ |
307 | +#!/usr/bin/env python |
308 | +# -*- coding: utf-8 -*- |
309 | +# settings_provider.py v0.10.3 |
310 | +# Provides settins to GMail notify |
311 | +# |
312 | +# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com> |
313 | +# |
314 | +# This program is free software: you can redistribute it and/or modify |
315 | +# it under the terms of the GNU General Public License as published by |
316 | +# the Free Software Foundation, either version 3 of the License, or |
317 | +# (at your option) any later version. |
318 | +# |
319 | +# This program is distributed in the hope that it will be useful, |
320 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
321 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
322 | +# GNU General Public License for more details. |
323 | +# |
324 | +# You should have received a copy of the GNU General Public License |
325 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
326 | +# |
327 | +from __future__ import print_function |
328 | + |
329 | +from gi.repository import Gio |
330 | + |
331 | +def create_settings_provider(username): |
332 | + if username: |
333 | + return AccountSettingsProvider(username) |
334 | + else: |
335 | + return DefaultSettingsProvider() |
336 | + |
337 | +class AccountSettingsProvider: |
338 | + def __init__(self, username): |
339 | + self.client = Gio.Settings("net.launchpad.gm-notify.account", "/net/launchpad/gm-notify/" + username + "/") |
340 | + |
341 | + def retrieve_sound_file(self, default_file = None): |
342 | + soundfile = self.client.get_string("soundfile") |
343 | + if soundfile == '': |
344 | + soundfile = default_file |
345 | + return soundfile |
346 | + |
347 | + def retrieve_sound_enabled(self): |
348 | + return self.client.get_boolean("play-sound") |
349 | + |
350 | + def retrieve_preferred_port(self): |
351 | + return self.client.get_int("preferred-port") |
352 | + |
353 | + def retrieve_ignore_inbox(self): |
354 | + return self.client.get_boolean("ignore-inbox") |
355 | + |
356 | + |
357 | + def retrieve_labels(self): |
358 | + return self.client.get_strv("labels") |
359 | + |
360 | + |
361 | + def retrieve_use_mail_client(self): |
362 | + return self.client.get_boolean("openclient") |
363 | + |
364 | + def save_labels(self, labels): |
365 | + self.client.set_strv("labels", labels) |
366 | + |
367 | + |
368 | + def save_ignore_inbox(self, ignore_inbox): |
369 | + self.client.set_boolean("ignore-inbox", ignore_inbox) |
370 | + |
371 | + |
372 | + def save_use_mail_client(self, use_mail_client): |
373 | + self.client.set_boolean("openclient", use_mail_client) |
374 | + |
375 | + |
376 | + def save_sound_enabled(self, enabled): |
377 | + self.client.set_boolean("play-sound", enabled) |
378 | + |
379 | + |
380 | + def save_sound_file(self, sound_file): |
381 | + self.client.set_string("soundfile", sound_file) |
382 | + |
383 | + |
384 | + def save_preferred_port(self, preferred_port): |
385 | + self.client.set_int("preferred-port", preferred_port) |
386 | + |
387 | + def remove_all_settings(self): |
388 | + self.client.reset("") |
389 | + |
390 | +class DefaultSettingsProvider: |
391 | + def retrieve_sound_file(self, default_file = None): |
392 | + return default_file |
393 | + |
394 | + def retrieve_sound_enabled(self): |
395 | + return False |
396 | + |
397 | + def retrieve_preferred_port(self): |
398 | + return 443 |
399 | + |
400 | + def retrieve_ignore_inbox(self): |
401 | + return False |
402 | + |
403 | + def retrieve_labels(self): |
404 | + return '' |
405 | + |
406 | + def retrieve_use_mail_client(self): |
407 | + return False |
408 | + |
409 | + def save_labels(self, mailboxes): |
410 | + raise NotImplementedError("Saving not supported") |
411 | + |
412 | + |
413 | + def save_ignore_inbox(self, ignore_inbox): |
414 | + raise NotImplementedError("Saving not supported") |
415 | + |
416 | + |
417 | + def save_use_mail_client(self, use_mail_client): |
418 | + raise NotImplementedError("Saving not supported") |
419 | + |
420 | + |
421 | + def save_sound_enabled(self, enabled): |
422 | + raise NotImplementedError("Saving not supported") |
423 | + |
424 | + |
425 | + def save_sound_file(self, sound_file): |
426 | + raise NotImplementedError("Saving not supported") |
427 | + |
428 | + |
429 | + def save_preferred_port(self, preferred_port): |
430 | + print("Ignoring save_preferred_port") |
431 | + |
432 | + def remove_all_settings(self): |
433 | + raise NotImplementedError("Removing not supported") |
434 | + |
435 | |
436 | === modified file 'data/net.launchpad.gm-notify.gschema.xml' |
437 | --- data/net.launchpad.gm-notify.gschema.xml 2015-12-05 00:25:32 +0000 |
438 | +++ data/net.launchpad.gm-notify.gschema.xml 2015-12-05 00:25:32 +0000 |
439 | @@ -1,25 +1,37 @@ |
440 | <?xml version="1.0" encoding="UTF-8"?> |
441 | <schemalist> |
442 | - <schema path="/net/launchpad/gm-notify/" id="net.launchpad.gm-notify" gettext-domain="gm-notify"> |
443 | - <key type="b" name="play-sound"> |
444 | - <default>false</default> |
445 | - <summary>Shall we play a sound on new mail?</summary> |
446 | - </key> |
447 | - <key type="b" name="ignore-inbox"> |
448 | - <default>false</default> |
449 | - <summary>If true, there won't be notifications regarding just your inbox.</summary> |
450 | - </key> |
451 | - <key type="s" name="soundfile"> |
452 | - <default>''</default> |
453 | - <summary>Path to the soundfile which should be played when a new mail arrives</summary> |
454 | - </key> |
455 | - <key type="as" name="mailboxes"> |
456 | - <default>[]</default> |
457 | - <summary>List containing mailboxes to check</summary> |
458 | - </key> |
459 | - <key type="b" name="openclient"> |
460 | - <default>false</default> |
461 | - <summary>Shall we open the default mail client or webinterface?</summary> |
462 | - </key> |
463 | - </schema> |
464 | + <schema id="net.launchpad.gm-notify.account" gettext-domain="gm-notify"> |
465 | + <key type="b" name="play-sound"> |
466 | + <default>false</default> |
467 | + <summary>Shall we play a sound on new mail?</summary> |
468 | + </key> |
469 | + <key type="b" name="ignore-inbox"> |
470 | + <default>false</default> |
471 | + <summary>If true, there won't be notifications regarding |
472 | + just your |
473 | + inbox. |
474 | + </summary> |
475 | + </key> |
476 | + <key type="s" name="soundfile"> |
477 | + <default>''</default> |
478 | + <summary>Path to the soundfile which should be played when a |
479 | + new mail |
480 | + arrives |
481 | + </summary> |
482 | + </key> |
483 | + <key type="as" name="labels"> |
484 | + <default>[]</default> |
485 | + <summary>List containing labels to check</summary> |
486 | + </key> |
487 | + <key type="b" name="openclient"> |
488 | + <default>false</default> |
489 | + <summary>Shall we open the default mail client or |
490 | + webinterface? |
491 | + </summary> |
492 | + </key> |
493 | + <key type="i" name="preferred-port"> |
494 | + <default>443</default> |
495 | + <summary>Port used to connect to Google services</summary> |
496 | + </key> |
497 | + </schema> |
498 | </schemalist> |
499 | |
500 | === modified file 'debian/changelog' |
501 | --- debian/changelog 2010-05-05 19:51:23 +0000 |
502 | +++ debian/changelog 2015-12-05 00:25:32 +0000 |
503 | @@ -1,3 +1,47 @@ |
504 | +gm-notify (1.0.0-0ubuntu1) vivid; urgency=medium |
505 | + |
506 | + * Added multi-account support |
507 | + * Added support for multiple ports (default port 443) |
508 | + * Added error messages |
509 | + * Refactored the code |
510 | + * Made app a single instance |
511 | + |
512 | + -- Mateusz Balbus <balbusm@gmail.com> Thu, 15 Oct 2015 15:43:32 +0200 |
513 | + |
514 | +gm-notify (0.10.3-0ubuntu3) trusty; urgency=low |
515 | + |
516 | + * Convert to dh_python2. |
517 | + * Wrap and sort. |
518 | + * Bump Standards-Version to 3.9.4. |
519 | + * Update copyright fields. |
520 | + |
521 | + -- Logan Rosen <logan@ubuntu.com> Tue, 19 Nov 2013 23:47:50 -0500 |
522 | + |
523 | +gm-notify (0.10.3-0ubuntu2) trusty; urgency=low |
524 | + |
525 | + * debian/control: Add missing dependency on |
526 | + python-glade2 (LP: # 863855). |
527 | + * debian/copyright: Tweak to bring up to latest |
528 | + DEP-5 spec. |
529 | + |
530 | + -- Andrew Starr-Bochicchio <a.starr.b@gmail.com> Sat, 01 Oct 2011 03:03:20 -0400 |
531 | + |
532 | +gm-notify (0.10.3-0ubuntu1) maverick; urgency=low |
533 | + |
534 | + * New upstream bug fix release. |
535 | + - Fix CPU usage issue with subprocess.call. (LP: #616781) |
536 | + * debian/patches/scripts-with-extensions-in-PATH: |
537 | + - Drop, included upstream. |
538 | + * debian/control: Bump Standards-Version to 3.9.1. |
539 | + |
540 | + -- Andrew Starr-Bochicchio <a.starr.b@gmail.com> Sat, 18 Sep 2010 15:41:02 -0400 |
541 | + |
542 | +gm-notify (0.10.2-0ubuntu1) maverick; urgency=low |
543 | + |
544 | + * Initial release (LP: #596313). |
545 | + |
546 | + -- Andrew Starr-Bochicchio <a.starr.b@gmail.com> Thu, 01 Jul 2010 11:28:14 -0400 |
547 | + |
548 | gm-notify (0.10.2-1~ppa1) lucid; urgency=low |
549 | |
550 | * Release 0.10.2 / see http://bleedingpaper.com/gm-notify |
551 | |
552 | === modified file 'debian/compat' |
553 | --- debian/compat 2009-05-07 06:24:15 +0000 |
554 | +++ debian/compat 2015-12-05 00:25:32 +0000 |
555 | @@ -1,1 +1,1 @@ |
556 | -7 |
557 | +9 |
558 | |
559 | === modified file 'debian/control' |
560 | --- debian/control 2010-04-21 11:56:58 +0000 |
561 | +++ debian/control 2015-12-05 00:25:32 +0000 |
562 | @@ -1,18 +1,31 @@ |
563 | Source: gm-notify |
564 | Section: mail |
565 | Priority: extra |
566 | -Maintainer: Ken VanDine <ken.vandine@canonical.com> |
567 | -XS-Python-Version: >= 2.6 |
568 | -Build-Depends: cdbs, debhelper (>= 7), python-central (>= 0.6), python |
569 | -Standards-Version: 3.8.0 |
570 | +Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
571 | +XSBC-Original-Maintainer: Andrew Starr-Bochicchio <a.starr.b@gmail.com> |
572 | +X-Python-Version: >= 2.6 |
573 | +Build-Depends: debhelper (>= 9), |
574 | + dh-python, |
575 | + python (>= 2.6.6-3~), |
576 | + python-setuptools |
577 | +Standards-Version: 3.9.4 |
578 | Homepage: https://launchpad.net/gm-notify |
579 | |
580 | Package: gm-notify |
581 | Architecture: all |
582 | -XB-Python-Version: ${python:Versions} |
583 | -Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, python-indicate, python-notify, python-gst0.10, python-gtk2, python-gconf, python-twisted-words, python-gnomekeyring |
584 | Provides: ${python:Provides} |
585 | -Description: GMail Notifier |
586 | - A simple and lightweight highly Ubuntu 10.04 integrated GMail Notifier which |
587 | - takes advantages of the new and nice notify-osd and indicator-applet. |
588 | - Because of this it will not run with older Ubuntu versions. |
589 | +Depends: python-gconf, |
590 | + python-glade2, |
591 | + python-gnomekeyring, |
592 | + python-gtk2, |
593 | + python-indicate, |
594 | + python-notify, |
595 | + python-twisted-words, |
596 | + gir1.2-gstreamer-1.0, |
597 | + ${misc:Depends}, |
598 | + ${python:Depends}, |
599 | + ${shlibs:Depends} |
600 | +Description: Highly Ubuntu integrated GMail notifier |
601 | + gm-notify is a simple, lightweight, and highly Ubuntu integrated |
602 | + GMail notifier which takes advantages of features like notify-osd |
603 | + and indicator-applet. |
604 | |
605 | === modified file 'debian/copyright' |
606 | --- debian/copyright 2009-05-07 06:24:15 +0000 |
607 | +++ debian/copyright 2015-12-05 00:25:32 +0000 |
608 | @@ -1,19 +1,34 @@ |
609 | -This package was debianized by Ken VanDine <ken.vandine@canonical.com> on |
610 | -Thu, 07 May 2009 01:39:52 -0400. |
611 | - |
612 | -It was downloaded from https://launchpad.net/gm-notify |
613 | - |
614 | -Upstream Author: |
615 | - |
616 | - Alexander Hungenberg <alexander.hungenberg@gmail.com> |
617 | - |
618 | -Copyright: |
619 | - |
620 | - Copyright (c) 2009, Alexander Hungenberg <alexander.hungenberg@gmail.com> |
621 | - |
622 | -License: |
623 | - |
624 | - GPL-3 |
625 | - |
626 | -The Debian packaging is copyright 2009, Ken VanDine <ken.vandine@canonical.com> and |
627 | -is licensed under the GPL, see `/usr/share/common-licenses/GPL'. |
628 | +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ |
629 | +Upstream-Name: gm-notify |
630 | +Source: https://launchpad.net/gm-notify |
631 | +Upstream-Contact: Alexander Hungenberg <alexander.hungenberg@gmail.com> |
632 | + |
633 | +Files: * |
634 | +Copyright: 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com> |
635 | +License: GPL-3+ |
636 | + |
637 | +Files: po/* |
638 | +Copyright: 2009, Rosetta Contributors and Canonical Ltd. |
639 | +License: GPL-3+ |
640 | + |
641 | +Files: debian/* |
642 | +Copyright: 2009, Ken VanDine <ken.vandine@canonical.com>, |
643 | + 2010, Andrew Starr-Bochicchio <a.starr.b@gmail.com> |
644 | +License: GPL-3+ |
645 | + |
646 | +License: GPL-3+ |
647 | + This package is free software; you can redistribute it and/or modify |
648 | + it under the terms of the GNU General Public License as published by |
649 | + the Free Software Foundation; either version 3 of the License, or |
650 | + (at your option) any later version. |
651 | + . |
652 | + This package is distributed in the hope that it will be useful, |
653 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
654 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
655 | + GNU General Public License for more details. |
656 | + . |
657 | + You should have received a copy of the GNU General Public License |
658 | + along with this package; if not, write to the Free Software |
659 | + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
660 | + . |
661 | + On Debian systems, see `/usr/share/common-licenses/GPL-3'. |
662 | |
663 | === modified file 'debian/rules' |
664 | --- debian/rules 2009-05-07 13:09:38 +0000 |
665 | +++ debian/rules 2015-12-05 00:25:32 +0000 |
666 | @@ -1,14 +1,6 @@ |
667 | #!/usr/bin/make -f |
668 | |
669 | -DEB_PYTHON_SYSTEM := pycentral |
670 | -export DH_PYCENTRAL=include-links |
671 | - |
672 | -include /usr/share/cdbs/1/rules/debhelper.mk |
673 | -include /usr/share/cdbs/1/class/python-distutils.mk |
674 | - |
675 | -DEB_DH_INSTALL_SOURCEDIR := debian/tmp |
676 | -DEB_PYTHON_INSTALL_ARGS_ALL := --no-compile --install-layout=deb |
677 | - |
678 | -# Add here any variable or target overrides you need. |
679 | -binary-post-install/gm-notify:: |
680 | - dh_gconf -p$(cdbs_curpkg) $(DEB_DH_GCONF_ARGS) |
681 | +export PYBUILD_NAME=gm-notify |
682 | +%: |
683 | + dh $@ --with python2 --buildsystem=pybuild |
684 | + |
685 | |
686 | === added directory 'debian/source' |
687 | === added file 'debian/source/format' |
688 | --- debian/source/format 1970-01-01 00:00:00 +0000 |
689 | +++ debian/source/format 2015-12-05 00:25:32 +0000 |
690 | @@ -0,0 +1,1 @@ |
691 | +3.0 (quilt) |
692 | |
693 | === modified file 'gm-config.ui' |
694 | --- gm-config.ui 2015-12-05 00:25:32 +0000 |
695 | +++ gm-config.ui 2015-12-05 00:25:32 +0000 |
696 | @@ -1,7 +1,8 @@ |
697 | <?xml version="1.0" encoding="UTF-8"?> |
698 | +<!-- Generated with glade 3.18.3 --> |
699 | <interface> |
700 | - <!-- interface-requires gtk+ 3.0 --> |
701 | - <object class="GtkWindow" id="gmnotify_config_main"> |
702 | + <requires lib="gtk+" version="3.10"/> |
703 | + <object class="GtkWindow" id="gmnotify_add_account"> |
704 | <property name="can_focus">False</property> |
705 | <property name="border_width">10</property> |
706 | <property name="title" translatable="yes">GMail Notifier</property> |
707 | @@ -36,8 +37,6 @@ |
708 | <packing> |
709 | <property name="left_attach">0</property> |
710 | <property name="top_attach">0</property> |
711 | - <property name="width">1</property> |
712 | - <property name="height">1</property> |
713 | </packing> |
714 | </child> |
715 | <child> |
716 | @@ -49,8 +48,6 @@ |
717 | <packing> |
718 | <property name="left_attach">0</property> |
719 | <property name="top_attach">1</property> |
720 | - <property name="width">1</property> |
721 | - <property name="height">1</property> |
722 | </packing> |
723 | </child> |
724 | <child> |
725 | @@ -58,13 +55,10 @@ |
726 | <property name="visible">True</property> |
727 | <property name="can_focus">True</property> |
728 | <property name="invisible_char">â</property> |
729 | - <property name="invisible_char_set">True</property> |
730 | </object> |
731 | <packing> |
732 | <property name="left_attach">1</property> |
733 | <property name="top_attach">0</property> |
734 | - <property name="width">1</property> |
735 | - <property name="height">1</property> |
736 | </packing> |
737 | </child> |
738 | <child> |
739 | @@ -73,14 +67,11 @@ |
740 | <property name="can_focus">True</property> |
741 | <property name="visibility">False</property> |
742 | <property name="invisible_char">â</property> |
743 | - <property name="invisible_char_set">True</property> |
744 | <signal name="focus-out-event" handler="on_input_password_focus_out_event" swapped="no"/> |
745 | </object> |
746 | <packing> |
747 | <property name="left_attach">1</property> |
748 | <property name="top_attach">1</property> |
749 | - <property name="width">1</property> |
750 | - <property name="height">1</property> |
751 | </packing> |
752 | </child> |
753 | <child> |
754 | @@ -92,8 +83,6 @@ |
755 | <packing> |
756 | <property name="left_attach">0</property> |
757 | <property name="top_attach">2</property> |
758 | - <property name="width">1</property> |
759 | - <property name="height">1</property> |
760 | </packing> |
761 | </child> |
762 | <child> |
763 | @@ -105,8 +94,6 @@ |
764 | <packing> |
765 | <property name="left_attach">1</property> |
766 | <property name="top_attach">2</property> |
767 | - <property name="width">1</property> |
768 | - <property name="height">1</property> |
769 | </packing> |
770 | </child> |
771 | </object> |
772 | @@ -329,44 +316,6 @@ |
773 | <property name="position">2</property> |
774 | </packing> |
775 | </child> |
776 | - <child> |
777 | - <object class="GtkFrame" id="frame_autostart"> |
778 | - <property name="visible">True</property> |
779 | - <property name="can_focus">False</property> |
780 | - <property name="label_xalign">0</property> |
781 | - <property name="shadow_type">none</property> |
782 | - <child> |
783 | - <object class="GtkAlignment" id="alignment4"> |
784 | - <property name="visible">True</property> |
785 | - <property name="can_focus">False</property> |
786 | - <property name="left_padding">12</property> |
787 | - <child> |
788 | - <object class="GtkCheckButton" id="checkbutton_autostart"> |
789 | - <property name="label" translatable="yes">Start automatically on logon</property> |
790 | - <property name="visible">True</property> |
791 | - <property name="can_focus">True</property> |
792 | - <property name="receives_default">False</property> |
793 | - <property name="xalign">0.5</property> |
794 | - <property name="draw_indicator">True</property> |
795 | - </object> |
796 | - </child> |
797 | - </object> |
798 | - </child> |
799 | - <child type="label"> |
800 | - <object class="GtkLabel" id="label1"> |
801 | - <property name="visible">True</property> |
802 | - <property name="can_focus">False</property> |
803 | - <property name="label" translatable="yes"><b>Autostart:</b></property> |
804 | - <property name="use_markup">True</property> |
805 | - </object> |
806 | - </child> |
807 | - </object> |
808 | - <packing> |
809 | - <property name="expand">False</property> |
810 | - <property name="fill">True</property> |
811 | - <property name="position">3</property> |
812 | - </packing> |
813 | - </child> |
814 | </object> |
815 | <packing> |
816 | <property name="position">1</property> |
817 | |
818 | === added file 'gm-list.ui' |
819 | --- gm-list.ui 1970-01-01 00:00:00 +0000 |
820 | +++ gm-list.ui 2015-12-05 00:25:32 +0000 |
821 | @@ -0,0 +1,224 @@ |
822 | +<?xml version="1.0" encoding="UTF-8"?> |
823 | +<!-- Generated with glade 3.18.3 --> |
824 | +<interface> |
825 | + <requires lib="gtk+" version="3.10"/> |
826 | + <object class="GtkListStore" id="accounts"> |
827 | + <columns> |
828 | + <!-- column-name mail --> |
829 | + <column type="gchararray"/> |
830 | + </columns> |
831 | + </object> |
832 | + <object class="GtkWindow" id="gmnotify_config_main"> |
833 | + <property name="width_request">400</property> |
834 | + <property name="height_request">250</property> |
835 | + <property name="can_focus">False</property> |
836 | + <property name="border_width">10</property> |
837 | + <property name="title" translatable="yes">GMail Notifier</property> |
838 | + <property name="resizable">False</property> |
839 | + <property name="window_position">center</property> |
840 | + <property name="default_width">512</property> |
841 | + <property name="default_height">512</property> |
842 | + <property name="icon_name">evolution</property> |
843 | + <property name="gravity">center</property> |
844 | + <child> |
845 | + <object class="GtkBox" id="box1"> |
846 | + <property name="visible">True</property> |
847 | + <property name="can_focus">False</property> |
848 | + <property name="opacity">0.97999999999999998</property> |
849 | + <property name="orientation">vertical</property> |
850 | + <child> |
851 | + <object class="GtkNotebook" id="notebook_main"> |
852 | + <property name="visible">True</property> |
853 | + <property name="can_focus">True</property> |
854 | + <child> |
855 | + <object class="GtkBox" id="box2"> |
856 | + <property name="visible">True</property> |
857 | + <property name="can_focus">False</property> |
858 | + <property name="margin_left">1</property> |
859 | + <property name="margin_right">1</property> |
860 | + <property name="margin_top">1</property> |
861 | + <property name="margin_bottom">1</property> |
862 | + <property name="spacing">5</property> |
863 | + <child> |
864 | + <object class="GtkToolbar" id="add_remove_toolbar"> |
865 | + <property name="visible">True</property> |
866 | + <property name="can_focus">False</property> |
867 | + <property name="orientation">vertical</property> |
868 | + <property name="toolbar_style">both</property> |
869 | + <child> |
870 | + <object class="GtkToolButton" id="add_button"> |
871 | + <property name="use_action_appearance">False</property> |
872 | + <property name="visible">True</property> |
873 | + <property name="can_focus">False</property> |
874 | + <property name="has_tooltip">True</property> |
875 | + <property name="tooltip_markup" translatable="yes">Add...</property> |
876 | + <property name="tooltip_text" translatable="yes">Add...</property> |
877 | + <property name="use_underline">True</property> |
878 | + <property name="stock_id">gtk-add</property> |
879 | + </object> |
880 | + <packing> |
881 | + <property name="expand">False</property> |
882 | + <property name="homogeneous">True</property> |
883 | + </packing> |
884 | + </child> |
885 | + <child> |
886 | + <object class="GtkToolButton" id="remove_button"> |
887 | + <property name="use_action_appearance">False</property> |
888 | + <property name="visible">True</property> |
889 | + <property name="can_focus">False</property> |
890 | + <property name="has_tooltip">True</property> |
891 | + <property name="tooltip_markup" translatable="yes">Remove...</property> |
892 | + <property name="tooltip_text" translatable="yes">Remove...</property> |
893 | + <property name="use_underline">True</property> |
894 | + <property name="stock_id">gtk-remove</property> |
895 | + </object> |
896 | + <packing> |
897 | + <property name="expand">False</property> |
898 | + <property name="homogeneous">True</property> |
899 | + </packing> |
900 | + </child> |
901 | + </object> |
902 | + <packing> |
903 | + <property name="expand">False</property> |
904 | + <property name="fill">True</property> |
905 | + <property name="position">0</property> |
906 | + </packing> |
907 | + </child> |
908 | + <child> |
909 | + <object class="GtkTreeView" id="accounts_treeview"> |
910 | + <property name="visible">True</property> |
911 | + <property name="can_focus">True</property> |
912 | + <property name="margin_left">1</property> |
913 | + <property name="margin_right">1</property> |
914 | + <property name="margin_top">1</property> |
915 | + <property name="margin_bottom">1</property> |
916 | + <property name="hscroll_policy">natural</property> |
917 | + <property name="vscroll_policy">natural</property> |
918 | + <property name="model">accounts</property> |
919 | + <property name="search_column">1</property> |
920 | + <property name="show_expanders">False</property> |
921 | + <property name="enable_grid_lines">horizontal</property> |
922 | + <child internal-child="selection"> |
923 | + <object class="GtkTreeSelection" id="treeview-selection"> |
924 | + <property name="mode">browse</property> |
925 | + </object> |
926 | + </child> |
927 | + <child> |
928 | + <object class="GtkTreeViewColumn" id="treeviewcolumn_name"> |
929 | + <property name="sizing">fixed</property> |
930 | + <property name="title" translatable="yes">Accounts List</property> |
931 | + <child> |
932 | + <object class="GtkCellRendererText" id="cellrenderertext_name"/> |
933 | + <attributes> |
934 | + <attribute name="text">0</attribute> |
935 | + </attributes> |
936 | + </child> |
937 | + </object> |
938 | + </child> |
939 | + </object> |
940 | + <packing> |
941 | + <property name="expand">True</property> |
942 | + <property name="fill">True</property> |
943 | + <property name="position">1</property> |
944 | + </packing> |
945 | + </child> |
946 | + </object> |
947 | + </child> |
948 | + <child type="tab"> |
949 | + <object class="GtkLabel" id="label_accounts"> |
950 | + <property name="visible">True</property> |
951 | + <property name="can_focus">False</property> |
952 | + <property name="label" translatable="yes">Accounts</property> |
953 | + </object> |
954 | + <packing> |
955 | + <property name="tab_fill">False</property> |
956 | + </packing> |
957 | + </child> |
958 | + <child> |
959 | + <object class="GtkFrame" id="frame_autostart"> |
960 | + <property name="visible">True</property> |
961 | + <property name="can_focus">False</property> |
962 | + <property name="label_xalign">0</property> |
963 | + <property name="shadow_type">none</property> |
964 | + <child> |
965 | + <object class="GtkAlignment" id="alignment4"> |
966 | + <property name="visible">True</property> |
967 | + <property name="can_focus">False</property> |
968 | + <property name="left_padding">12</property> |
969 | + <child> |
970 | + <object class="GtkCheckButton" id="checkbutton_autostart"> |
971 | + <property name="label" translatable="yes">Start automatically on logon</property> |
972 | + <property name="visible">True</property> |
973 | + <property name="can_focus">True</property> |
974 | + <property name="receives_default">False</property> |
975 | + <property name="valign">start</property> |
976 | + <property name="xalign">0</property> |
977 | + <property name="yalign">0</property> |
978 | + <property name="draw_indicator">True</property> |
979 | + </object> |
980 | + </child> |
981 | + </object> |
982 | + </child> |
983 | + <child type="label"> |
984 | + <object class="GtkLabel" id="label_autostart"> |
985 | + <property name="visible">True</property> |
986 | + <property name="can_focus">False</property> |
987 | + <property name="label" translatable="yes"><b>Autostart:</b></property> |
988 | + <property name="use_markup">True</property> |
989 | + </object> |
990 | + </child> |
991 | + </object> |
992 | + <packing> |
993 | + <property name="position">1</property> |
994 | + </packing> |
995 | + </child> |
996 | + <child type="tab"> |
997 | + <object class="GtkLabel" id="label_preferences"> |
998 | + <property name="visible">True</property> |
999 | + <property name="can_focus">False</property> |
1000 | + <property name="label" translatable="yes">Preferences</property> |
1001 | + </object> |
1002 | + <packing> |
1003 | + <property name="position">1</property> |
1004 | + <property name="tab_fill">False</property> |
1005 | + </packing> |
1006 | + </child> |
1007 | + <child> |
1008 | + <placeholder/> |
1009 | + </child> |
1010 | + <child type="tab"> |
1011 | + <placeholder/> |
1012 | + </child> |
1013 | + </object> |
1014 | + <packing> |
1015 | + <property name="expand">True</property> |
1016 | + <property name="fill">True</property> |
1017 | + <property name="position">0</property> |
1018 | + </packing> |
1019 | + </child> |
1020 | + <child> |
1021 | + <object class="GtkButton" id="button_ok"> |
1022 | + <property name="label">gtk-ok</property> |
1023 | + <property name="width_request">100</property> |
1024 | + <property name="visible">True</property> |
1025 | + <property name="can_focus">True</property> |
1026 | + <property name="receives_default">True</property> |
1027 | + <property name="halign">end</property> |
1028 | + <property name="valign">center</property> |
1029 | + <property name="margin_left">5</property> |
1030 | + <property name="margin_right">5</property> |
1031 | + <property name="margin_top">5</property> |
1032 | + <property name="margin_bottom">5</property> |
1033 | + <property name="use_stock">True</property> |
1034 | + <property name="always_show_image">True</property> |
1035 | + </object> |
1036 | + <packing> |
1037 | + <property name="expand">False</property> |
1038 | + <property name="fill">True</property> |
1039 | + <property name="position">1</property> |
1040 | + </packing> |
1041 | + </child> |
1042 | + </object> |
1043 | + </child> |
1044 | + </object> |
1045 | +</interface> |
1046 | |
1047 | === modified file 'gm-notify' |
1048 | --- gm-notify 2015-12-05 00:25:32 +0000 |
1049 | +++ gm-notify 2015-12-05 00:25:32 +0000 |
1050 | @@ -1,10 +1,11 @@ |
1051 | #!/usr/bin/python |
1052 | # -*- coding: utf-8 -*- |
1053 | |
1054 | -# gm-notify v0.10.3 |
1055 | +# gm-notify v1.0 |
1056 | # a simple and lightweight GMail-Notifier for ubuntu and notify-osd |
1057 | # |
1058 | # Copyright (c) 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com> |
1059 | +# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com> |
1060 | # |
1061 | # This program is free software: you can redistribute it and/or modify |
1062 | # it under the terms of the GNU General Public License as published by |
1063 | @@ -27,7 +28,9 @@ |
1064 | import gettext |
1065 | import webbrowser |
1066 | |
1067 | -from gi.repository import Gio, GLib, MessagingMenu, Notify |
1068 | +import gi |
1069 | +gi.require_version('Gst', '1.0') |
1070 | +from gi.repository import Gio, GLib, Gst, MessagingMenu, Notify |
1071 | |
1072 | from twisted.internet import gireactor |
1073 | gireactor.install() |
1074 | @@ -35,6 +38,7 @@ |
1075 | from twisted.words.protocols.jabber import jid |
1076 | |
1077 | from gtalk import MailChecker |
1078 | +import account_settings_provider |
1079 | import gm_notify_keyring as keyring |
1080 | |
1081 | _ = gettext.translation('gm-notify', fallback=True).ugettext |
1082 | @@ -43,6 +47,10 @@ |
1083 | |
1084 | MAILBOXES_URLS = { "inbox": "" } |
1085 | |
1086 | +GMAIL_DOMAINS = ['gmail.com','googlemail.com'] |
1087 | + |
1088 | +EMAIL_RETRIEVAL_ERROR = "--retrieval-error" |
1089 | + |
1090 | class PathNotFound(Exception): pass |
1091 | |
1092 | def get_executable_path(name): |
1093 | @@ -54,28 +62,15 @@ |
1094 | if os.path.exists(path) and os.access(path, os.X_OK): return path |
1095 | raise PathNotFound("%s not found" % name) |
1096 | |
1097 | -def play_sound(name): |
1098 | - '''Spawns a canberra-gtk-play process to play the sound''' |
1099 | - if name is None: |
1100 | - return |
1101 | - player_path = "/usr/bin/canberra-gtk-play" |
1102 | - # Not installed? |
1103 | - if not os.path.exists(player_path): |
1104 | - return |
1105 | - command = [player_path] |
1106 | - # File exists, so use the file flag |
1107 | - if os.path.exists(name): |
1108 | - command.extend(["-f", name]) |
1109 | - # Assume it is a sound id |
1110 | - else: |
1111 | - command.extend(["-i", name]) |
1112 | - try: |
1113 | - result = GLib.spawn_async(command) |
1114 | - except: |
1115 | - return |
1116 | - # Does nothing but the documentation says to do it anyway |
1117 | - if len(result) > 0: |
1118 | - GLib.spawn_close_pid(result[0]) |
1119 | +class Account: |
1120 | + def __init__(self): |
1121 | + self.username = None |
1122 | + self.client = None |
1123 | + self.soundfile = None |
1124 | + self.ignore_inbox = None |
1125 | + self.domain = None |
1126 | + self.labels = None |
1127 | + self.use_mail_client = None |
1128 | |
1129 | class CheckMail(Gio.Application): |
1130 | def __init__(self): |
1131 | @@ -84,14 +79,36 @@ |
1132 | super(CheckMail, self).__init__(application_id="net.launchpad.gm-notify", |
1133 | flags=Gio.ApplicationFlags.FLAGS_NONE) |
1134 | |
1135 | + self.player = None |
1136 | self._has_activated = False |
1137 | self._counts = {} |
1138 | self.connect("activate", self.on_activate) |
1139 | |
1140 | + def play_sound(self, name): |
1141 | + '''Uses GSound to play the music''' |
1142 | + if name is None: |
1143 | + return |
1144 | + |
1145 | + if not self.player: |
1146 | + Gst.init() |
1147 | + self.player = Gst.ElementFactory.make("playbin", "player") |
1148 | + |
1149 | + self.player.set_property("uri", "file://" + name) |
1150 | + result = self.player.set_state(Gst.State.PLAYING) |
1151 | + if result == Gst.StateChangeReturn.FAILURE: |
1152 | + print("Unable to play " + name) |
1153 | + |
1154 | def on_remote_quit(self, action, args): |
1155 | '''Stops the application when the "remote-quit" action is activated''' |
1156 | reactor.stop() |
1157 | |
1158 | + def run(self, args): |
1159 | + self.register(); |
1160 | + if self.get_is_remote(): |
1161 | + self.activate() |
1162 | + else: |
1163 | + super(CheckMail, self).run(args) |
1164 | + |
1165 | def on_activate(self, app): |
1166 | '''When first receiving the activate signal, initialize the primary |
1167 | instance. On subsequent activate signals, we are being activated from a |
1168 | @@ -113,8 +130,8 @@ |
1169 | sys.exit(-1) |
1170 | |
1171 | keys = keyring.Keyring("GMail", "mail.google.com", "http") |
1172 | - if keys.has_credentials(): |
1173 | - self.creds = keys.get_credentials() |
1174 | + if keys.has_any_credentials(): |
1175 | + creds = keys.get_all_credentials() |
1176 | else: |
1177 | print("Failed to get credentials") |
1178 | # Start gm-notify-config if no credentials are found |
1179 | @@ -122,110 +139,140 @@ |
1180 | subprocess.call(get_executable_path("gm-notify-config")) |
1181 | except PathNotFound: |
1182 | print(_("gm-notify-config utility was not found")) |
1183 | + |
1184 | sys.exit(-1) |
1185 | |
1186 | - # check if we use Google Apps to start the correct webinterface |
1187 | - gmail_domains = ['gmail.com','googlemail.com'] |
1188 | - self.jid = jid.JID(self.creds[0]) |
1189 | - if self.jid.host in gmail_domains: |
1190 | - self.domain = None |
1191 | - else: |
1192 | - self.domain = self.jid.host |
1193 | - |
1194 | - self.client = Gio.Settings("net.launchpad.gm-notify") |
1195 | - |
1196 | - # Set up the sound file |
1197 | - self._soundfile = self.client.get_string("soundfile") |
1198 | - if self._soundfile == '': |
1199 | - self._soundfile = "message-new-instant" |
1200 | - if not self.client.get_boolean("play-sound"): |
1201 | - self._soundfile = None |
1202 | - |
1203 | # Messaging Menu integration |
1204 | self._m_menu = MessagingMenu.App.new("gm-notify.desktop") |
1205 | self._m_menu.register() |
1206 | self._m_menu.connect("activate-source", self.source_clicked) |
1207 | |
1208 | - # Read ignore-inbox value. If true you will only receive notifications |
1209 | - # about configured labels |
1210 | - self.ignore_inbox = self.client.get_boolean("ignore-inbox") |
1211 | - |
1212 | - # Retrieve the mailbox we're gonna check |
1213 | - self.mailboxes = self.client.get_strv("mailboxes") |
1214 | - self.mailboxes.insert(0, "inbox") |
1215 | - self.checker = MailChecker(self.jid, self.creds[1], self.mailboxes[1:], self.new_mail, self.update_count) |
1216 | - self.checker.connect() |
1217 | - |
1218 | + self.accounts = self.init_accounts(creds) |
1219 | + |
1220 | + |
1221 | + def init_accounts(self, creds): |
1222 | + accounts = {} |
1223 | + for credentails in creds: |
1224 | + account = Account() |
1225 | + account.username = credentails.username |
1226 | + local_jid = jid.JID(account.username) |
1227 | + # check if we use Google Apps to start the correct webinterface |
1228 | + if local_jid.host not in GMAIL_DOMAINS: |
1229 | + account.domain = local_jid.host |
1230 | + |
1231 | + client = account_settings_provider.create_settings_provider(account.username) |
1232 | + account.client = client |
1233 | + |
1234 | + account.use_mail_client = client.retrieve_use_mail_client() |
1235 | + |
1236 | + # Set up the sound file |
1237 | + if client.retrieve_sound_enabled(): |
1238 | + account.soundfile = client.retrieve_sound_file("message-new-instant") |
1239 | + # Read ignore-inbox value. If true you will only receive notifications |
1240 | + # about configured labels |
1241 | + account.ignore_inbox = client.retrieve_ignore_inbox() |
1242 | + |
1243 | + # Retrieve the mailbox we're gonna check |
1244 | + account.labels = client.retrieve_labels() |
1245 | + account.labels.insert(0, "inbox") |
1246 | + account.checker = MailChecker(local_jid, credentails.password, client, account.labels[1:], self.new_mail, self.update_count) |
1247 | + account.checker.setOnConnectionErrorCB(self.checker_connection_error) |
1248 | + account.checker.setOnAuthFailed(self.checker_auth_failed) |
1249 | + account.checker.setOnAuthSucceeded(self.checker_auth_succeeded) |
1250 | + account.checker.connect() |
1251 | + |
1252 | + accounts[credentails.username] = account |
1253 | + return accounts |
1254 | + |
1255 | def indicator_clicked(self): |
1256 | '''called when "Google Mail" is clicked in indicator-messages and |
1257 | performs a Mail Check''' |
1258 | - for label in self.mailboxes: |
1259 | - self.remove_attention(label) |
1260 | - |
1261 | - self.checker.queryInbox() |
1262 | + for username, account in self.accounts.items(): |
1263 | + for label in account.labels: |
1264 | + self.remove_attention(self.compose_id(username, label)) |
1265 | + |
1266 | + for username, account in self.accounts.items(): |
1267 | + account.checker.queryInbox() |
1268 | + |
1269 | + def compose_label(self, username, label): |
1270 | + return "%s (%s)" % (username, label) |
1271 | + |
1272 | + def compose_id(self, username, label): |
1273 | + return "%s&%s" % (username, label) |
1274 | + |
1275 | + def decompose_id(self, id): |
1276 | + decomposed = id.split("&") |
1277 | + return (decomposed[0], decomposed[1]) |
1278 | |
1279 | def remove_attention(self, label): |
1280 | '''Removes attention from the label source if it exists''' |
1281 | if self._m_menu.has_source(label): |
1282 | self._m_menu.remove_attention(label) |
1283 | |
1284 | - def has_source(self, label): |
1285 | + def has_source(self, username, label): |
1286 | '''Returns true if we have this label, or if we don't and it is in our |
1287 | mailboxes list, create it''' |
1288 | - if label == "inbox" and self.ignore_inbox: |
1289 | + account = self.accounts[username] |
1290 | + if label == "inbox" and account.ignore_inbox: |
1291 | return False |
1292 | - elif label in self.mailboxes: |
1293 | - if not self._m_menu.has_source(label): |
1294 | + elif label in account.labels: |
1295 | + mail_label = self.compose_id(username, label) |
1296 | + if not self._m_menu.has_source(mail_label): |
1297 | if label in MAILBOXES_NAMES: |
1298 | name = MAILBOXES_NAMES[label] |
1299 | else: |
1300 | name = label |
1301 | + name_label = self.compose_label(username, name) |
1302 | if label == "inbox": |
1303 | - self._m_menu.insert_source_with_string(0, label, None, name, _("empty")) |
1304 | + self._m_menu.insert_source_with_string(0, mail_label, None, name_label, _("empty")) |
1305 | else: |
1306 | - self._m_menu.append_source_with_string(label, None, name, _("empty")) |
1307 | - if label in self._counts: |
1308 | - self._m_menu.set_source_count(label, self._counts[label]) |
1309 | + self._m_menu.append_source_with_string(mail_label, None, name_label, _("empty")) |
1310 | + if mail_label in self._counts: |
1311 | + self._m_menu.set_source_count(mail_label, self._counts[mail_label]) |
1312 | return True |
1313 | else: |
1314 | return False |
1315 | |
1316 | - def update_count(self, count): |
1317 | + def update_count(self, username, count): |
1318 | '''Updates the count for all the mailboxes''' |
1319 | + account = self.accounts[username] |
1320 | for mailbox in count.iteritems(): |
1321 | - if mailbox[0] == "inbox" and self.ignore_inbox: |
1322 | + if mailbox[0] == "inbox" and account.ignore_inbox: |
1323 | continue |
1324 | |
1325 | - if self.has_source(mailbox[0]): |
1326 | + if self.has_source(username, mailbox[0]): |
1327 | # Get the last count |
1328 | last_count = 0 |
1329 | - if mailbox[0] in self._counts: |
1330 | - last_count = self._counts[mailbox[0]] |
1331 | + mail_label = self.compose_id(username, mailbox[0]) |
1332 | + if mail_label in self._counts: |
1333 | + last_count = self._counts[mail_label] |
1334 | current_count = int(mailbox[1]) |
1335 | |
1336 | # Remove attention if the count has decreased |
1337 | if last_count > current_count: |
1338 | - self._m_menu.remove_attention(mailbox[0]) |
1339 | + self._m_menu.remove_attention(mail_label) |
1340 | if current_count > 0: |
1341 | - self._m_menu.set_source_count(mailbox[0], current_count) |
1342 | + self._m_menu.set_source_count(mail_label, current_count) |
1343 | # Remove the source if 0 messages, to save space |
1344 | else: |
1345 | - self._m_menu.remove_source(mailbox[0]) |
1346 | - self._counts[mailbox[0]] = current_count |
1347 | + self._m_menu.remove_source(mail_label) |
1348 | + self._counts[mail_label] = current_count |
1349 | |
1350 | - def new_mail(self, mails): |
1351 | + def new_mail(self, username, mails): |
1352 | '''Takes mailbox name and titles of mails, to display notification and add indicators''' |
1353 | + account = self.accounts[username] |
1354 | text = "" |
1355 | # aggregate the titles of the messages... cut the string if longer than 30 chars |
1356 | for mail in mails: |
1357 | got_label = False |
1358 | for label in mail['labels']: |
1359 | if label == u"^i": label = "inbox" |
1360 | - if label == "inbox" and self.ignore_inbox: |
1361 | + if label == "inbox" and account.ignore_inbox: |
1362 | continue |
1363 | - if self.has_source(label): |
1364 | + if self.has_source(username, label): |
1365 | got_label = True |
1366 | - self._m_menu.draw_attention(label) |
1367 | + mail_label = self.compose_id(username, label) |
1368 | + self._m_menu.draw_attention(mail_label) |
1369 | if not got_label: continue |
1370 | |
1371 | if "sender_name" in mail: text += mail['sender_name'] + ":\n" |
1372 | @@ -242,32 +289,75 @@ |
1373 | text += "- " + title + "\n" |
1374 | |
1375 | if text: |
1376 | - self.showNotification(_("Incoming message"), text.strip("\n")) |
1377 | - play_sound(self._soundfile) |
1378 | + self.show_notification("{0}".format(username), text.strip("\n")) |
1379 | + self.play_sound(self.accounts[username].soundfile) |
1380 | |
1381 | def source_clicked(self, app, source_id): |
1382 | '''called when a label is clicked in the indicator-applet and opens the corresponding gmail page''' |
1383 | - if self.domain: |
1384 | - url = "https://mail.google.com/a/"+self.domain+"/" |
1385 | - else: |
1386 | - url = "https://mail.google.com/mail/" |
1387 | - |
1388 | - try: |
1389 | - url += "#%s" % MAILBOXES_URLS[source_id] |
1390 | - except KeyError: |
1391 | - url += "#label/%s" % source_id |
1392 | + # TODO: missing username |
1393 | + username, label = self.decompose_id(source_id) |
1394 | + if label == EMAIL_RETRIEVAL_ERROR: |
1395 | + return |
1396 | + account = self.accounts[username] |
1397 | |
1398 | # Open mail client |
1399 | - if self.client.get_boolean("openclient"): |
1400 | + if account.use_mail_client: |
1401 | try: |
1402 | info = Gio.AppInfo.get_default_for_type("x-scheme-handler/mailto", False) |
1403 | info.launch(None, None) |
1404 | except: |
1405 | pass |
1406 | else: |
1407 | + url = self.prep_url(account, label) |
1408 | webbrowser.open(url) |
1409 | |
1410 | - def showNotification(self, title, message): |
1411 | + def prep_url(self, account, label): |
1412 | + url_domain = self.prep_url_domain(account.domain) |
1413 | + url_label = self.prep_url_label(label) |
1414 | + |
1415 | + return ("https://accounts.google.com/AccountChooser?" |
1416 | + "Email={0}" |
1417 | + "&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F{1}" |
1418 | + "&service=mail" |
1419 | + "&hd={2}").format(account.username, url_label, url_domain) |
1420 | + |
1421 | + def prep_url_label(self, label): |
1422 | + if label in MAILBOXES_URLS: |
1423 | + return "%23{0}".format(MAILBOXES_URLS[label]) |
1424 | + else: |
1425 | + return "%23label%2F{0}".format(label) |
1426 | + |
1427 | + def prep_url_domain(self, domain): |
1428 | + if domain: |
1429 | + return domain |
1430 | + else: |
1431 | + return "default" |
1432 | + |
1433 | + def checker_auth_succeeded(self, username): |
1434 | + error_label = self.compose_id(username, EMAIL_RETRIEVAL_ERROR) |
1435 | + self._m_menu.remove_source(error_label) |
1436 | + |
1437 | + def checker_connection_error(self, username, error): |
1438 | + self.check_failed(username, _("Cannot retrieve emails")) |
1439 | + |
1440 | + def checker_auth_failed(self, username, error): |
1441 | + self.check_failed(username, _("Authentication failed")) |
1442 | + |
1443 | + def check_failed(self, username, feed): |
1444 | + |
1445 | + for id in self._counts: |
1446 | + local_username, local_label = self.decompose_id(id) |
1447 | + if local_username == username: |
1448 | + self._m_menu.remove_source(id) |
1449 | + |
1450 | + error_id = self.compose_id(username, EMAIL_RETRIEVAL_ERROR) |
1451 | + if self._m_menu.has_source(error_id): |
1452 | + return |
1453 | + self._m_menu.append_source(error_id, None, username + _(" - Cannot retrieve emails")) |
1454 | + n = Notify.Notification.new(_("Error for ") + username, feed, "messagebox_critical") |
1455 | + n.show() |
1456 | + |
1457 | + def show_notification(self, title, message): |
1458 | '''takes a title and a message to display the email notification. Returns the |
1459 | created notification object''' |
1460 | |
1461 | @@ -276,6 +366,13 @@ |
1462 | |
1463 | return n |
1464 | |
1465 | + def shutdown(self): |
1466 | + if self.accounts: |
1467 | + for username, account in self.accounts.items(): |
1468 | + if account.checker : account.checker.die() |
1469 | + |
1470 | cm = CheckMail() |
1471 | reactor.registerGApplication(cm) |
1472 | +reactor.addSystemEventTrigger('before', 'shutdown', cm.shutdown) |
1473 | reactor.run() |
1474 | + |
1475 | |
1476 | === modified file 'gm-notify-config' |
1477 | --- gm-notify-config 2015-12-05 00:25:32 +0000 |
1478 | +++ gm-notify-config 2015-12-05 00:25:32 +0000 |
1479 | @@ -1,10 +1,11 @@ |
1480 | #!/usr/bin/env python |
1481 | # -*- coding: utf-8 -*- |
1482 | |
1483 | -# gm-notify-config v0.10.3 |
1484 | +# gm-notify-config v1.0 |
1485 | # GMail Notifier Configuration Utility |
1486 | # |
1487 | # Copyright (c) 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com> |
1488 | +# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com> |
1489 | # |
1490 | # This program is free software: you can redistribute it and/or modify |
1491 | # it under the terms of the GNU General Public License as published by |
1492 | @@ -27,15 +28,17 @@ |
1493 | import subprocess |
1494 | import shutil |
1495 | |
1496 | -from gi.repository import Gio, Gtk |
1497 | +from gi.repository import Gio, Gtk, Gdk |
1498 | |
1499 | from twisted.internet import gtk3reactor |
1500 | gtk3reactor.install() |
1501 | from twisted.internet import reactor |
1502 | -from twisted.words.protocols.jabber import jid |
1503 | |
1504 | import gm_notify_keyring as keyring |
1505 | -from gtalk import MailChecker |
1506 | +from gm_notify_keyring import Credentials |
1507 | +from account_config import AccountConfig |
1508 | +import account_settings_provider |
1509 | + |
1510 | |
1511 | _ = gettext.translation('gm-notify', fallback=True).ugettext |
1512 | |
1513 | @@ -57,6 +60,13 @@ |
1514 | self.window = None |
1515 | self.connect("activate", self.on_activate) |
1516 | |
1517 | + def run(self, args): |
1518 | + self.register(); |
1519 | + if self.get_is_remote(): |
1520 | + self.activate() |
1521 | + else: |
1522 | + super(Window, self).run(args) |
1523 | + |
1524 | def on_activate(self, app): |
1525 | '''Setup the application''' |
1526 | if self.window is not None: |
1527 | @@ -66,59 +76,39 @@ |
1528 | # GUI initialization |
1529 | ##### |
1530 | self.keys = keyring.Keyring("GMail", "mail.google.com", "http") |
1531 | - self.client = Gio.Settings("net.launchpad.gm-notify") |
1532 | |
1533 | - if os.path.exists("gm-config.ui"): |
1534 | - builder_file = "gm-config.ui" |
1535 | - elif os.path.exists("/usr/local/share/gm-notify/gm-config.ui"): |
1536 | - builder_file = "/usr/local/share/gm-notify/gm-config.ui" |
1537 | - elif os.path.exists("/usr/share/gm-notify/gm-config.ui"): |
1538 | - builder_file = "/usr/share/gm-notify/gm-config.ui" |
1539 | + if os.path.exists("gm-list.ui"): |
1540 | + builder_file = "gm-list.ui" |
1541 | + elif os.path.exists("/usr/local/share/gm-notify/gm-list.ui"): |
1542 | + builder_file = "/usr/local/share/gm-notify/gm-list.ui" |
1543 | + elif os.path.exists("/usr/share/gm-notify/gm-list.ui"): |
1544 | + builder_file = "/usr/share/gm-notify/gm-list.ui" |
1545 | + |
1546 | |
1547 | self.wTree = Gtk.Builder.new() |
1548 | self.wTree.add_from_file(builder_file) |
1549 | self.wTree.set_translation_domain("gm-notify") |
1550 | self.window = self.wTree.get_object("gmnotify_config_main") |
1551 | self.window.show_all() |
1552 | + self.window.connect("delete_event", self.terminate) |
1553 | + self.window.connect("window-state-event", self.on_update_accounts) |
1554 | self.add_window(self.window) |
1555 | + |
1556 | + self.accounts = self.wTree.get_object("accounts") |
1557 | |
1558 | + self.accounts_treeview = self.wTree.get_object("accounts_treeview") |
1559 | + self.accounts_treeview.connect("row-activated", self.on_modify_account); |
1560 | + |
1561 | self.wTree.get_object("notebook_main").set_current_page(0) |
1562 | |
1563 | - ##### |
1564 | - # Init with stored values |
1565 | - ##### |
1566 | - |
1567 | - # Credentials |
1568 | - if self.keys.has_credentials(): |
1569 | - self.creds = self.keys.get_credentials() |
1570 | - else: |
1571 | - self.creds = ("", "") |
1572 | - self.wTree.get_object("input_user").set_text(self.creds[0]) |
1573 | - self.wTree.get_object("input_password").set_text(self.creds[1]) |
1574 | - |
1575 | - self.api = MailChecker("", "") |
1576 | - self.api.cb_auth_successful = self.credentials_valid |
1577 | - self.api.cb_auth_failed = self.credentials_invalid |
1578 | - |
1579 | - self.check_credentials(None, None) |
1580 | - |
1581 | - # Sound |
1582 | - self.wTree.get_object("checkbutton_sound").set_active(self.client.get_boolean("play-sound")) |
1583 | - if self.client.get_string("soundfile"): |
1584 | - self.wTree.get_object("fcbutton_sound").set_filename(self.client.get_string("soundfile")) |
1585 | - self.on_checkbutton_sound_toggled(self.wTree.get_object("checkbutton_sound")) |
1586 | - |
1587 | - # ClickAction |
1588 | - if self.client.get_boolean("openclient"): |
1589 | - self.wTree.get_object("radiobutton_openclient").set_active(True) |
1590 | - else: |
1591 | - self.wTree.get_object("radiobutton_openweb").set_active(True) |
1592 | - |
1593 | - # Mailboxes |
1594 | - mailboxes = self.client.get_strv("mailboxes") |
1595 | - self.wTree.get_object("checkbutton_inbox").set_active(self.client.get_boolean("ignore-inbox")) |
1596 | - self.wTree.get_object("entry_labels").set_text(", ".join(mailboxes)) |
1597 | + |
1598 | + self.add_button = self.wTree.get_object("add_button") |
1599 | + self.add_button.connect("clicked", self.on_add_account) |
1600 | |
1601 | + self.remove_button = self.wTree.get_object("remove_button") |
1602 | + self.remove_button.connect("clicked", self.on_remove_account) |
1603 | + |
1604 | + self.wTree.get_object("button_ok").connect("clicked", self.terminate) |
1605 | # Autorun |
1606 | if os.path.exists("data/gm-notify.desktop"): |
1607 | self.gm_notify_autostart_file = "data/gm-notify.desktop" |
1608 | @@ -129,36 +119,48 @@ |
1609 | self.autostart_file = os.path.expanduser("~/.config/autostart/gm-notify.desktop") |
1610 | self.wTree.get_object("checkbutton_autostart").set_active(os.path.exists(self.autostart_file)) |
1611 | |
1612 | - # Connect signals |
1613 | - self.wTree.get_object("button_close").connect("clicked", self.terminate) |
1614 | - self.wTree.get_object("button_apply").connect("clicked", self.save) |
1615 | - self.wTree.get_object("input_password").connect("focus-out-event", self.check_credentials) |
1616 | - self.wTree.get_object("checkbutton_sound").connect("toggled", self.on_checkbutton_sound_toggled) |
1617 | - |
1618 | + def update_accounts(self, accounts, keys): |
1619 | + accounts.clear() |
1620 | + if not self.keys.has_any_users(): |
1621 | + return |
1622 | + for user in keys.get_all_users(): |
1623 | + accounts.append((user,)) |
1624 | + |
1625 | + def on_update_accounts(self, widget, event): |
1626 | + print("Event %s WindowState %s" % (str(event.type), str(event.new_window_state))) |
1627 | + if (event.type == Gdk.EventType.WINDOW_STATE |
1628 | + and event.new_window_state == Gdk.WindowState.FOCUSED): |
1629 | + self.update_accounts(self.accounts, self.keys) |
1630 | + |
1631 | + def on_modify_account(self, widget, path, column): |
1632 | + iter = self.accounts.get_iter(path) |
1633 | + account = self.accounts[iter] |
1634 | + username = account[0] |
1635 | + credentials = self.keys.get_credentials(username) |
1636 | + account_config = AccountConfig(self.keys, credentials) |
1637 | + window = account_config.init_window(self.window) |
1638 | + |
1639 | + def on_add_account(self, widget): |
1640 | + account_config = AccountConfig(self.keys, Credentials()) |
1641 | + window = account_config.init_window(self.window) |
1642 | + |
1643 | + def on_remove_account(self, widget): |
1644 | + tree_selection = self.accounts_treeview.get_selection() |
1645 | + accounts,iter = tree_selection.get_selected() |
1646 | + if iter is None: |
1647 | + return |
1648 | + |
1649 | + account = accounts[iter] |
1650 | + username = account[0] |
1651 | + settings_provider = account_settings_provider.create_settings_provider(username) |
1652 | + settings_provider.remove_all_settings() |
1653 | + self.keys.delete_credentials(username) |
1654 | + |
1655 | + accounts.remove(iter) |
1656 | + |
1657 | def save(self, widget, data=None): |
1658 | '''saves the entered data and closes the app''' |
1659 | - # Credentials |
1660 | - self.keys.delete_credentials() |
1661 | - self.keys.set_credentials(self.wTree.get_object("input_user").get_text(), |
1662 | - self.wTree.get_object("input_password").get_text()) |
1663 | - |
1664 | - # Mailboxes |
1665 | - mailboxes = [] |
1666 | - for label in self.wTree.get_object("entry_labels").get_text().split(","): |
1667 | - mailboxes.append(label.strip()) |
1668 | - self.client.set_strv("mailboxes", mailboxes) |
1669 | - self.client.set_boolean("ignore-inbox", self.wTree.get_object("checkbutton_inbox").get_active()) |
1670 | - |
1671 | - # ClickAction |
1672 | - self.client.set_boolean("openclient", self.wTree.get_object("radiobutton_openclient").get_active()) |
1673 | - |
1674 | - # Soundfile |
1675 | - if self.wTree.get_object("checkbutton_sound").get_active() and self.wTree.get_object("fcbutton_sound").get_filename(): |
1676 | - self.client.set_boolean("play-sound", True) |
1677 | - self.client.set_string("soundfile", str(self.wTree.get_object("fcbutton_sound").get_filename())) |
1678 | - else: |
1679 | - self.client.set_boolean("play-sound", False) |
1680 | - |
1681 | + |
1682 | # Autorun |
1683 | if self.wTree.get_object("checkbutton_autostart").get_active(): |
1684 | if not os.path.exists(self.autostart_file): |
1685 | @@ -182,62 +184,9 @@ |
1686 | # Start gm-notify itself |
1687 | subprocess.Popen(get_executable_path("gm-notify")) |
1688 | |
1689 | - def terminate(self, widget): |
1690 | + def terminate(self, widget, event = None): |
1691 | reactor.stop() |
1692 | |
1693 | - def on_checkbutton_sound_toggled(self, widget): |
1694 | - self.wTree.get_object("fcbutton_sound").set_sensitive(self.wTree.get_object("checkbutton_sound").get_active()) |
1695 | - |
1696 | - def check_credentials(self, widget, event, data=None): |
1697 | - '''check if the given credentials are valid''' |
1698 | - |
1699 | - input_user = self.wTree.get_object("input_user") |
1700 | - input_password = self.wTree.get_object("input_password") |
1701 | - image_credentials = self.wTree.get_object("image_credentials") |
1702 | - label_credentials = self.wTree.get_object("label_credentials") |
1703 | - button_apply = self.wTree.get_object("button_apply") |
1704 | - button_apply.set_sensitive(False) |
1705 | - |
1706 | - # Change status text and disable input fields |
1707 | - if input_user.get_text() and input_password.get_text(): |
1708 | - image_credentials.set_from_file("/usr/share/gm-notify/checking.gif") |
1709 | - label_credentials.set_text(_("checking...")) |
1710 | - input_user.set_sensitive(False) |
1711 | - input_password.set_sensitive(False) |
1712 | - |
1713 | - self.api.jid = jid.JID(input_user.get_text()) |
1714 | - self.api.password = input_password.get_text() |
1715 | - self.api.connect() |
1716 | - return False |
1717 | - |
1718 | - def credentials_valid(self): |
1719 | - input_user = self.wTree.get_object("input_user") |
1720 | - input_password = self.wTree.get_object("input_password") |
1721 | - image_credentials = self.wTree.get_object("image_credentials") |
1722 | - label_credentials = self.wTree.get_object("label_credentials") |
1723 | - button_apply = self.wTree.get_object("button_apply") |
1724 | - |
1725 | - image_credentials.set_from_icon_name("gtk-yes", Gtk.IconSize.MENU) |
1726 | - label_credentials.set_text(_("Valid credentials")) |
1727 | - button_apply.set_sensitive(True) |
1728 | - input_user.set_sensitive(True) |
1729 | - input_password.set_sensitive(True) |
1730 | - |
1731 | - self.api.die() |
1732 | - |
1733 | - def credentials_invalid(self): |
1734 | - input_user = self.wTree.get_object("input_user") |
1735 | - input_password = self.wTree.get_object("input_password") |
1736 | - image_credentials = self.wTree.get_object("image_credentials") |
1737 | - label_credentials = self.wTree.get_object("label_credentials") |
1738 | - |
1739 | - image_credentials.set_from_icon_name("gtk-stop", Gtk.IconSize.MENU) |
1740 | - label_credentials.set_text(_("Invalid credentials")) |
1741 | - input_user.set_sensitive(True) |
1742 | - input_password.set_sensitive(True) |
1743 | - |
1744 | - self.api.die() |
1745 | - |
1746 | t = Window() |
1747 | reactor.registerGApplication(t) |
1748 | reactor.run() |
1749 | |
1750 | === modified file 'gm_notify_keyring.py' |
1751 | --- gm_notify_keyring.py 2015-12-05 00:25:32 +0000 |
1752 | +++ gm_notify_keyring.py 2015-12-05 00:25:32 +0000 |
1753 | @@ -3,6 +3,9 @@ |
1754 | __version__ = "$Revision: 14294 $" |
1755 | |
1756 | from gi.repository import GnomeKeyring, Gtk |
1757 | +from collections import namedtuple |
1758 | +Credentials = namedtuple("Credentials", ["username", "password"]) |
1759 | +Credentials.__new__.__defaults__ = ("", "") |
1760 | |
1761 | def attributes(d): |
1762 | '''Converts a dictionary to a GnomeKeyring.Attribute array''' |
1763 | @@ -28,26 +31,66 @@ |
1764 | self._protocol = protocol |
1765 | result, self._keyring = GnomeKeyring.get_default_keyring_sync() |
1766 | |
1767 | - def has_credentials(self): |
1768 | + def has_any_credentials(self): |
1769 | attrs = attributes({"server": self._server, "protocol": self._protocol}) |
1770 | result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs) |
1771 | if result in (GnomeKeyring.Result.NO_MATCH, GnomeKeyring.Result.DENIED): |
1772 | return False |
1773 | return len(items) > 0 |
1774 | |
1775 | - def get_credentials(self): |
1776 | - attrs = attributes({"server": self._server, "protocol": self._protocol}) |
1777 | + def get_all_credentials(self): |
1778 | + attrs = attributes({"server": self._server, "protocol": self._protocol}) |
1779 | + result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs) |
1780 | + if len(items) == 0: |
1781 | + raise KeyringException("Credentials not found") |
1782 | + credentials_list = [] |
1783 | + for item in items: |
1784 | + d = dict_from_attributes(item.attributes) |
1785 | + credentials_list.append(Credentials(d["user"], item.secret)) |
1786 | + return credentials_list |
1787 | + |
1788 | + def has_any_users(self): |
1789 | + return self.has_any_credentials() |
1790 | + |
1791 | + def get_all_users(self): |
1792 | + attrs = attributes({"server": self._server, "protocol": self._protocol}) |
1793 | + result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs) |
1794 | + if len(items) == 0: |
1795 | + raise KeyringException("Credentials not found") |
1796 | + users_list = [] |
1797 | + for item in items: |
1798 | + d = dict_from_attributes(item.attributes) |
1799 | + users_list.append(d["user"]) |
1800 | + return users_list |
1801 | + |
1802 | + def get_credentials(self, user): |
1803 | + attrs = attributes({ |
1804 | + "user" : user, |
1805 | + "server": self._server, |
1806 | + "protocol": self._protocol |
1807 | + }) |
1808 | result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs) |
1809 | if len(items) == 0: |
1810 | raise KeyringException("Credentials not found") |
1811 | d = dict_from_attributes(items[0].attributes) |
1812 | - return (d["user"], items[0].secret) |
1813 | + return Credentials(d["user"], items[0].secret) |
1814 | |
1815 | - def delete_credentials(self): |
1816 | + def delete_all_credentials(self): |
1817 | attrs = attributes({"server": self._server, "protocol": self._protocol}) |
1818 | result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs) |
1819 | for item in items: |
1820 | GnomeKeyring.item_delete_sync(self._keyring, item.item_id) |
1821 | + |
1822 | + def delete_credentials(self, user): |
1823 | + attrs = attributes({ |
1824 | + "user": user, |
1825 | + "server": self._server, |
1826 | + "protocol": self._protocol |
1827 | + }) |
1828 | + result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs) |
1829 | + for item in items: |
1830 | + GnomeKeyring.item_delete_sync(self._keyring, item.item_id) |
1831 | + |
1832 | |
1833 | def set_credentials(self, user, pw): |
1834 | attrs = attributes({ |
1835 | |
1836 | === modified file 'gtalk.py' |
1837 | --- gtalk.py 2015-12-05 00:25:32 +0000 |
1838 | +++ gtalk.py 2015-12-05 00:25:32 +0000 |
1839 | @@ -1,10 +1,11 @@ |
1840 | #!/usr/bin/env python |
1841 | # -*- coding: utf-8 -*- |
1842 | |
1843 | -# gtalk.py v0.10.3 |
1844 | +# gtalk.py v1.0 |
1845 | # Google Talk mail notification client library |
1846 | # |
1847 | # Copyright (c) 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com> |
1848 | +# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com> |
1849 | # |
1850 | # This program is free software: you can redistribute it and/or modify |
1851 | # it under the terms of the GNU General Public License as published by |
1852 | @@ -21,38 +22,86 @@ |
1853 | # |
1854 | from __future__ import print_function |
1855 | |
1856 | -from threading import Event |
1857 | - |
1858 | from twisted.words.protocols.jabber import xmlstream, client, jid |
1859 | from twisted.words.xish import domish |
1860 | -from twisted.internet import reactor, task, error |
1861 | - |
1862 | -_DEBUG = False |
1863 | +from twisted.internet import reactor, task, error, ssl |
1864 | +from twisted.internet.error import TimeoutError, ConnectionRefusedError |
1865 | + |
1866 | +from datetime import datetime |
1867 | + |
1868 | +GTALK_HOST = "talk.google.com" |
1869 | + |
1870 | +_DEBUG = True |
1871 | COLOR_GREEN = "\033[92m" |
1872 | COLOR_END = "\033[0m" |
1873 | def DEBUG(msg): |
1874 | - if _DEBUG: print(COLOR_GREEN + str(msg) + COLOR_END) |
1875 | + if _DEBUG: |
1876 | + curent_time = datetime.now() |
1877 | + |
1878 | + print(COLOR_GREEN + str(curent_time) + " " +str(msg) + COLOR_END) |
1879 | |
1880 | class GTalkClientFactory(xmlstream.XmlStreamFactory): |
1881 | - def __init__(self, jid, password): |
1882 | + |
1883 | + def __init__(self, jid, password, settings_provider): |
1884 | a = client.XMPPAuthenticator(jid, password) |
1885 | xmlstream.XmlStreamFactory.__init__(self, a) |
1886 | + self.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, self.clientConnected) |
1887 | |
1888 | + self.jid = jid |
1889 | self.reconnect = True |
1890 | + self.connection_failed = False |
1891 | + self.connection_lost = False |
1892 | + self.cb_connection_error = None |
1893 | + self.settings_provider = settings_provider |
1894 | + self.port = self.settings_provider.retrieve_preferred_port() |
1895 | |
1896 | def clientConnectionLost(self, connector, reason): |
1897 | - if self.reconnect: xmlstream.XmlStreamFactory.clientConnectionLost(self, connector, reason) |
1898 | + DEBUG("clientConnectionLost %s" % str(reason)) |
1899 | + self.connection_lost = True |
1900 | + if not self.reconnect: |
1901 | + return |
1902 | + |
1903 | + DEBUG("clientConnectionLost: Reconnecting with the same settings (port %d)" % connector.port) |
1904 | + if self.cb_connection_error : self.cb_connection_error(self.jid.full(), reason) |
1905 | + # reconnect on the same port as it used to work |
1906 | + xmlstream.XmlStreamFactory.clientConnectionLost(self, connector, reason) |
1907 | + |
1908 | + def clientConnectionFailed(self, connector, reason): |
1909 | + DEBUG("clientConnectionFailed %s on port: %d reconnecting: %s" % (str(reason), connector.port, str(self.reconnect))) |
1910 | + self.connection_failed = True |
1911 | + if not self.reconnect: |
1912 | + return |
1913 | + |
1914 | + DEBUG("clientConnectionFailed: Connection failed"); |
1915 | + if self.cb_connection_error : self.cb_connection_error(self.jid.full(), reason) |
1916 | + |
1917 | + def clientConnected(self, xmlstream): |
1918 | + DEBUG("clientConnected") |
1919 | + self.connection_failed = False |
1920 | + self.connection_lost = False |
1921 | + |
1922 | + def getCurrentPort(self): |
1923 | + return self.port |
1924 | + |
1925 | + def hasConnectionFailedOrLost(self): |
1926 | + return self.connection_failed or self.connection_lost |
1927 | + |
1928 | + def setOnConnectionErrorCB(self, cb_connection_error): |
1929 | + self.cb_connection_error = cb_connection_error |
1930 | + |
1931 | |
1932 | class MailChecker(): |
1933 | - def __init__(self, jid, password, labels=[], cb_new=None, cb_count=None): |
1934 | - self.host = "talk.google.com" |
1935 | - self.port = 5222 |
1936 | + def __init__(self, jid, password, settings_provider, labels=[], cb_new=None, cb_count=None): |
1937 | + self.host = GTALK_HOST |
1938 | + self.settings_provider = settings_provider |
1939 | self.jid = jid |
1940 | self.password = password |
1941 | self.cb_new = cb_new |
1942 | self.cb_count = cb_count |
1943 | - self.cb_auth_successful = None |
1944 | + self.cb_auth_succeeded = None |
1945 | self.cb_auth_failed = None |
1946 | + self.cb_connection_error = None |
1947 | + self.cb_connected = None |
1948 | |
1949 | self.last_tids = {} |
1950 | self.labels = labels |
1951 | @@ -66,14 +115,33 @@ |
1952 | self.ready_for_query_state = False |
1953 | self.timeout_call_id = None |
1954 | self.disconnected = True |
1955 | + self.running = False |
1956 | + |
1957 | + def setOnConnectionErrorCB(self, cb_connection_error): |
1958 | + self.cb_connection_error = cb_connection_error |
1959 | + |
1960 | + def setOnConnectedCB(self, cb_connected): |
1961 | + self.cb_connected = cb_connected |
1962 | + |
1963 | + def setOnAuthFailed(self, cb_auth_failed): |
1964 | + self.cb_auth_failed = cb_auth_failed |
1965 | + |
1966 | + def setOnAuthSucceeded(self, cb_auth_succeeded): |
1967 | + self.cb_auth_succeeded = cb_auth_succeeded |
1968 | + |
1969 | + def is_running(self): |
1970 | + return self.running |
1971 | |
1972 | def die(self): |
1973 | + DEBUG("Dying...") |
1974 | self.factory.reconnect = False |
1975 | self.query_task.stop() |
1976 | self.connector.disconnect() |
1977 | + self.running = False |
1978 | |
1979 | def connect(self): |
1980 | - self.factory = GTalkClientFactory(self.jid, self.password) |
1981 | + self.factory = GTalkClientFactory(self.jid, self.password, self.settings_provider) |
1982 | + self.factory.setOnConnectionErrorCB(self.cb_connection_error) |
1983 | self.factory.addBootstrap(xmlstream.STREAM_END_EVENT, self.disconnectCB) |
1984 | self.factory.addBootstrap(xmlstream.STREAM_ERROR_EVENT, self.disconnectCB) |
1985 | self.factory.addBootstrap(xmlstream.INIT_FAILED_EVENT, self.init_failedCB) |
1986 | @@ -84,10 +152,12 @@ |
1987 | |
1988 | self.query_task = task.LoopingCall(self.queryInbox) |
1989 | self.query_task.start(60) |
1990 | + self.running = True |
1991 | |
1992 | - self.connector = reactor.connectTCP(self.host, self.port, self.factory) |
1993 | + self.connector = reactor.connectSSL(self.host, self.factory.getCurrentPort(), self.factory, ssl.ClientContextFactory()) |
1994 | |
1995 | def reply_timeout(self): |
1996 | + DEBUG("reply_timeout") |
1997 | self.connector.disconnect() # Our reconnecting factory will try the reconnecting |
1998 | |
1999 | def send_callback_handler(self, data, callback=None, **kargs): |
2000 | @@ -115,16 +185,19 @@ |
2001 | self.xmlstream.send(data) |
2002 | |
2003 | def disconnectCB(self, xmlstream): |
2004 | - self.ready_for_query_state = False |
2005 | - self.disconnected = True |
2006 | DEBUG("disconnected") |
2007 | - |
2008 | + self.ready_for_query_state = False |
2009 | + self.factory.reconnect = False |
2010 | + self.disconnected = True |
2011 | + |
2012 | def init_failedCB(self, xmlstream): |
2013 | - if self.cb_auth_failed: self.cb_auth_failed() |
2014 | + DEBUG("init_failedCB") |
2015 | + if self.cb_auth_failed: self.cb_auth_failed(self.jid.full(), xmlstream.value) |
2016 | self.disconnectCB(xmlstream) |
2017 | |
2018 | def authenticationCB(self, xmlstream): |
2019 | - if self.cb_auth_successful: self.cb_auth_successful() |
2020 | + DEBUG("authenticationCB") |
2021 | + if self.cb_auth_succeeded: self.cb_auth_succeeded(self.jid.full()) |
2022 | self.factory.resetDelay() |
2023 | |
2024 | # We set the usersetting mail-notification |
2025 | @@ -135,12 +208,19 @@ |
2026 | self.send(iq, "/iq", self.usersettingIQ) |
2027 | |
2028 | def usersettingIQ(self, iq): |
2029 | + DEBUG("usersettingIQ") |
2030 | self.ready_for_query_state = True |
2031 | self.queryInbox() |
2032 | |
2033 | def queryInbox(self): |
2034 | - if not self.ready_for_query_state: return |
2035 | + DEBUG("queryInbox") |
2036 | + if not(self.ready_for_query_state or self.factory.hasConnectionFailedOrLost()): |
2037 | + DEBUG("queryInbox: ready for query: %s connection_failed_or_lost: %s" % (str(self.ready_for_query_state), str(self.factory.hasConnectionFailedOrLost()))) |
2038 | + DEBUG("queryInbox: skipping query request") |
2039 | + return |
2040 | if self.disconnected: |
2041 | + DEBUG("queryInbox: disconnected") |
2042 | + self.factory.reconnect = True |
2043 | self.connector.connect() |
2044 | return |
2045 | self.ready_for_query_state = False |
2046 | @@ -149,21 +229,23 @@ |
2047 | |
2048 | iq = domish.Element((None, "iq"), attribs={"type": "get", "id": "mail-request-1"}) |
2049 | query = iq.addElement(("google:mail:notify", "query")) |
2050 | + DEBUG("queryInbox: requesting for label") |
2051 | self.send(iq, "/iq", self.gotLabel) |
2052 | |
2053 | def queryLabel(self): |
2054 | try: |
2055 | label = self.labels_iter.next() |
2056 | - |
2057 | + DEBUG("queryLabel " + label) |
2058 | iq = domish.Element((None, "iq"), attribs={"type": "get", "id": "mail-request-1"}) |
2059 | query = iq.addElement(("google:mail:notify", "query")) |
2060 | query.attributes['q'] = "label:%s AND is:unread" % label |
2061 | self.send(iq, "/iq", self.gotLabel, label=label) |
2062 | except StopIteration: |
2063 | + DEBUG("queryLabel: end of iteration") |
2064 | self.labels_iter = iter(self.labels) |
2065 | self.xmlstream.addObserver("/iq", self.gotNewMail) |
2066 | - if self.cb_count: self.cb_count(self.count) |
2067 | - if self.mails and self.cb_new: self.cb_new(self.mails) |
2068 | + if self.cb_count: self.cb_count(self.jid.full(), self.count) |
2069 | + if self.mails and self.cb_new: self.cb_new(self.jid.full(), self.mails) |
2070 | self.mails = [] |
2071 | self.ready_for_query_state = True |
2072 | |
2073 | @@ -242,21 +324,25 @@ |
2074 | mail['snippet'] = unicode(child) |
2075 | mails.append(mail) |
2076 | |
2077 | - self.cb_new(mails) |
2078 | + self.cb_new(self.jid.full(), mails) |
2079 | |
2080 | self.ready_for_query_state = True |
2081 | if iq: self.queryInbox() |
2082 | |
2083 | def rawDataIn(self, buf): |
2084 | - print(u"< %s" % unicode(buf, "utf-8")) |
2085 | + print("> %s" % buf) |
2086 | |
2087 | def rawDataOut(self, buf): |
2088 | - print(u"> %s" % unicode(buf, "utf-8")) |
2089 | + print("< %s" % buf) |
2090 | |
2091 | def connectedCB(self, xmlstream): |
2092 | self.xmlstream = xmlstream |
2093 | self.disconnected = False |
2094 | + if self.cb_connected: self.cb_connected(self.jid.full()) |
2095 | |
2096 | if _DEBUG: |
2097 | xmlstream.rawDataInFn = self.rawDataIn |
2098 | xmlstream.rawDataOutFn = self.rawDataOut |
2099 | + |
2100 | + def getCurrentPort(self): |
2101 | + return self.factory.getCurrentPort() |
2102 | |
2103 | === modified file 'setup.py' |
2104 | --- setup.py 2015-12-05 00:25:32 +0000 |
2105 | +++ setup.py 2015-12-05 00:25:32 +0000 |
2106 | @@ -3,16 +3,17 @@ |
2107 | from distutils.core import setup |
2108 | |
2109 | setup( name='gm-notify', |
2110 | - version='0.10.3', |
2111 | + version='1.0.0', |
2112 | description='Highly Ubuntu integrated GMail Notifier', |
2113 | - author='Alexander Hungenberg', |
2114 | - author_email='alexander.hungenberg@gmail.com', |
2115 | - py_modules=['gtalk', 'gm_notify_keyring'], |
2116 | + author='Mateusz Balbus', |
2117 | + author_email='balbusmg@gmail.com', |
2118 | + py_modules=['gtalk', 'gm_notify_keyring', 'account_settings_provider', 'account_config'], |
2119 | scripts=['gm-notify', 'gm-notify-config'], |
2120 | data_files=[('/usr/share/applications', ['data/gm-notify-config.desktop']), |
2121 | ('/usr/share/applications', ['data/gm-notify.desktop']), |
2122 | ('/usr/share/gm-notify', ['data/checking.gif']), |
2123 | ('/usr/share/gm-notify', ['gm-config.ui']), |
2124 | + ('/usr/share/gm-notify', ['gm-list.ui']), |
2125 | ('/usr/share/glib-2.0/schemas', ['data/net.launchpad.gm-notify.gschema.xml']), |
2126 | ('/usr/share/locale/da/LC_MESSAGES', ['po/da/gm-notify.mo']), |
2127 | ('/usr/share/locale/bg/LC_MESSAGES', ['po/bg/gm-notify.mo']), |