Merge lp:~mate-ob/gm-notify/multi-account into lp:gm-notify

Proposed by Mateusz Balbus on 2015-12-05
Status: Merged
Approved by: Mateusz Balbus on 2016-01-19
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
Reviewer Review Type Date Requested Status
gm-notify Maintainers 2015-12-05 Pending
Review via email: mp+279670@code.launchpad.net

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">&lt;b&gt;Autostart:&lt;/b&gt;</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">&lt;b&gt;Autostart:&lt;/b&gt;</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']),

Subscribers

People subscribed via source and target branches

to status/vote changes: