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

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
Reviewer Review Type Date Requested Status
gm-notify Maintainers 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
=== modified file 'README'
--- README 2010-09-18 13:06:25 +0000
+++ README 2015-12-05 00:25:32 +0000
@@ -1,4 +1,5 @@
1gm-notify 0.10.3 README1gm-notify 1.0 README
2
2------------------------------------3------------------------------------
34
4QUICK INSTALLATION:5QUICK INSTALLATION:
@@ -9,56 +10,66 @@
9The Configuration-Dialog is placed unter System-Settings-GMail Notifier Configuration.10The Configuration-Dialog is placed unter System-Settings-GMail Notifier Configuration.
1011
11DEPENDENCIES:12DEPENDENCIES:
13
12- python-indicate14- python-indicate
13- python-notify15- python-notify
14- python-gst0.1016- gir1.2-gstreamer-1.0
17- python-glade2
15- python-gtk218- python-gtk2
16- python-gconf19- python-gconf
17- python-twisted-words20- python-twisted-words
18- python-gnomekeyring21- python-gnomekeyring
1922
20install via23install via
21sudo apt-get install python-indicate python-notify python-gst0.10 python-gtk2 python-gconf python-twisted-words python-gnomekeyring24sudo apt-get install python-indicate python-notify gir1.2-gstreamer-1.0 python-glade2 python-gtk2 python-gconf python-twisted-words python-gnomekeyring
2225
23------------------------------------26------------------------------------
2427
25RELEASE NOTES:28RELEASE NOTES:
2629
27v0.10.3:30v1.0:
28- Fixed CPU Usage Bug occuring on Maverick31
29- Included Bulgarian Translation32- Added multi-account support
30- Correct Priority Inbox usage33- Added support for multiple ports (default port 443)
31- Mutt Label support34- Added error messages
35- Refactored the code
36- Made app a single instance
3237
33v0.10.2:38v0.10.2:
39
34- Added Checkbox to ignore the inbox40- Added Checkbox to ignore the inbox
35- fixed autorun41- fixed autorun
36- fixed some other small bugs42- fixed some other small bugs
3743
38v0.10.1:44v0.10.1:
45
39- Configurable autorun46- Configurable autorun
40- Usage of Ubuntu's default mail icon47- Usage of Ubuntu's default mail icon
41- Fix of a displaying bug48- Fix of a displaying bug
42- Ignoring the inbox is possible (see http://bleedingpaper.com/gm-notify)49- Ignoring the inbox is possible (see http://bleedingpaper.com/gm-notify)
4350
44v0.10:51v0.10:
52
45- Written for Lucid 10.04 (no warranty that older versions will be supported)53- Written for Lucid 10.04 (no warranty that older versions will be supported)
46- Finally switched to Google Talk backend for instant mail notification (hopefully this time it'll last longer ;-) )54- Finally switched to Google Talk backend for instant mail notification (hopefully this time it'll last longer ;-) )
47- Labels with a count of 0 will be hidden (except Inbox)55- Labels with a count of 0 will be hidden (except Inbox)
48- Sound properties work again56- Sound properties work again
4957
50v0.9:58v0.9:
59
51- Switched to IMAP backend (twisted), now allowing to check different labels60- Switched to IMAP backend (twisted), now allowing to check different labels
52- allows to choose if it should start webinterface or native client61- allows to choose if it should start webinterface or native client
53- Fixed many bugs/crashes62- Fixed many bugs/crashes
5463
55v0.8:64v0.8:
65
56- included Danish translation66- included Danish translation
57- integrated with GNOME Sound Framework.67- integrated with GNOME Sound Framework.
58- Fixed Bug #36724268- Fixed Bug #367242
59- warn when using small check intervals69- warn when using small check intervals
6070
61v0.7:71v0.7:
72
62- included (old) Catalan translation73- included (old) Catalan translation
63- German and English is completely translated (I hope my English weren't to bad ;-) )74- German and English is completely translated (I hope my English weren't to bad ;-) )
64- added a graphical configuration interface75- added a graphical configuration interface
@@ -76,9 +87,11 @@
76Concept and code taken from cgmail87Concept and code taken from cgmail
77implemented by Sassur <sassur@gmail.com>88implemented by Sassur <sassur@gmail.com>
7889
79all the rest is written 2009-2010 by Alexander Hungenberg90all the rest developed and maintained by:
912009-2010 by Alexander Hungenberg
922015 Mateusz Balbus
8093
81------------------------------------------------------------------------94------------------------------------
8295
83LICENSE:96LICENSE:
8497
8598
=== added file 'account_config.py'
--- account_config.py 1970-01-01 00:00:00 +0000
+++ account_config.py 2015-12-05 00:25:32 +0000
@@ -0,0 +1,197 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# account_config.py v0.10.3
4# Provides settins to GMail notify
5#
6# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20from __future__ import print_function
21
22import os
23import gettext
24
25from gi.repository import Gio, Gtk
26from twisted.words.protocols.jabber import jid
27
28from gtalk import MailChecker
29import account_settings_provider
30
31_ = gettext.translation('gm-notify', fallback=True).ugettext
32
33class AccountConfig:
34 def __init__(self, keys, creds):
35 self.keys = keys
36 self.creds = creds
37
38 def init_window(self, parent):
39 if os.path.exists("gm-config.ui"):
40 builder_file = "gm-config.ui"
41 elif os.path.exists("/usr/local/share/gm-notify/gm-config.ui"):
42 builder_file = "/usr/local/share/gm-notify/gm-config.ui"
43 elif os.path.exists("/usr/share/gm-notify/gm-config.ui"):
44 builder_file = "/usr/share/gm-notify/gm-config.ui"
45
46 self.wTree = Gtk.Builder.new()
47 self.wTree.add_from_file(builder_file)
48 self.wTree.set_translation_domain("gm-notify")
49 self.window = self.wTree.get_object("gmnotify_add_account")
50
51 self.window.set_transient_for(parent)
52 self.window.set_modal(True)
53 self.window.set_destroy_with_parent(True)
54
55 self.window.show_all()
56
57 self.wTree.get_object("notebook_main").set_current_page(0)
58
59 self.input_user = self.wTree.get_object("input_user")
60 self.input_password = self.wTree.get_object("input_password")
61 self.image_credentials = self.wTree.get_object("image_credentials")
62 self.label_credentials = self.wTree.get_object("label_credentials")
63 self.button_apply = self.wTree.get_object("button_apply")
64
65 self.window.connect("delete_event", self.close)
66 self.wTree.get_object("button_close").connect("clicked", self.close)
67 self.button_apply.connect("clicked", self.apply)
68 self.input_password.connect("focus-out-event", self.check_credentials)
69 self.input_user.connect("focus-out-event", self.check_user)
70 self.wTree.get_object("checkbutton_sound").connect("toggled", self.on_checkbutton_sound_toggled)
71
72 #####
73 # Init with stored values
74 #####
75
76 # Credentials
77
78 settings_provider = account_settings_provider.create_settings_provider(self.creds.username)
79
80 if self.creds.username:
81 self.input_user.set_text(self.creds.username)
82 self.input_user.set_sensitive(False)
83
84 self.input_password.set_text(self.creds.password)
85
86 self.api = MailChecker("", "", settings_provider)
87 self.api.setOnAuthSucceeded(self.credentials_valid)
88 self.api.setOnAuthFailed(self.credentials_invalid)
89 self.api.setOnConnectionErrorCB(self.connection_error)
90
91 self.check_credentials(None, None)
92
93 # Sound
94 self.wTree.get_object("checkbutton_sound").set_active(settings_provider.retrieve_sound_enabled())
95 sound_file = settings_provider.retrieve_sound_file()
96 if sound_file:
97 self.wTree.get_object("fcbutton_sound").set_filename(sound_file)
98 self.on_checkbutton_sound_toggled(self.wTree.get_object("checkbutton_sound"))
99
100 # ClickAction
101 if settings_provider.retrieve_use_mail_client():
102 self.wTree.get_object("radiobutton_openclient").set_active(True)
103 else:
104 self.wTree.get_object("radiobutton_openweb").set_active(True)
105
106 # Mailboxes
107 self.wTree.get_object("checkbutton_inbox").set_active(settings_provider.retrieve_ignore_inbox())
108 labels = settings_provider.retrieve_labels()
109 self.wTree.get_object("entry_labels").set_text(", ".join(labels))
110
111 return self.window
112
113 def close(self, widget = None, event = None):
114 if self.api.is_running():
115 self.api.die()
116 self.window.close()
117
118 def apply(self, widget):
119 self.save()
120 self.close()
121
122 def save(self):
123 '''saves the entered data and closes the app'''
124 # Credentials
125 self.keys.delete_credentials(self.creds.username)
126
127 user = self.input_user.get_text()
128 self.keys.set_credentials(user,
129 self.input_password.get_text())
130
131 settings_provider = account_settings_provider.create_settings_provider(user)
132 # Mailboxes
133 labels = []
134 for label in self.wTree.get_object("entry_labels").get_text().split(","):
135 labels.append(label.strip())
136 settings_provider.save_labels(labels)
137 settings_provider.save_ignore_inbox(self.wTree.get_object("checkbutton_inbox").get_active())
138
139 # ClickAction
140 settings_provider.save_use_mail_client(self.wTree.get_object("radiobutton_openclient").get_active())
141
142 # Port
143 settings_provider.save_preferred_port(settings_provider.retrieve_preferred_port())
144
145 # Soundfile
146 if self.wTree.get_object("checkbutton_sound").get_active() and self.wTree.get_object("fcbutton_sound").get_filename():
147 settings_provider.save_sound_enabled(True)
148 settings_provider.save_sound_file(str(self.wTree.get_object("fcbutton_sound").get_filename()))
149 else:
150 settings_provider.save_sound_enabled(False)
151
152 def on_checkbutton_sound_toggled(self, widget):
153 self.wTree.get_object("fcbutton_sound").set_sensitive(self.wTree.get_object("checkbutton_sound").get_active())
154
155 def check_user(self, widget, event):
156 user = self.input_user.get_text()
157 if not self.has_mail_postfix(user):
158 self.input_user.set_text(user + "@gmail.com")
159
160 def has_mail_postfix(self, user):
161 return len(user) == 0 or "@" in user
162
163 def check_credentials(self, widget, event, data=None):
164 '''check if the given credentials are valid'''
165
166 self.button_apply.set_sensitive(False)
167
168 # Change status text and disable input fields
169 if self.input_user.get_text() and self.input_password.get_text():
170 self.image_credentials.set_from_file("/usr/share/gm-notify/checking.gif")
171 self.label_credentials.set_text(_("checking..."))
172 self.input_user.set_sensitive(False)
173 self.input_password.set_sensitive(False)
174
175 self.api.jid = jid.JID(self.input_user.get_text())
176 self.api.password = self.input_password.get_text()
177 self.api.connect()
178 return False
179
180 def credentials_valid(self, username):
181 self.on_credentials_checked("gtk-yes", "Valid credentials", True)
182
183 def credentials_invalid(self, username, reason):
184 self.on_credentials_checked("gtk-stop", "Invalid credentials")
185
186 def connection_error(self, username, reason):
187 self.on_credentials_checked("gtk-stop", "Connection error")
188
189 def on_credentials_checked(self, icon_name, text, valid = False):
190 self.image_credentials.set_from_icon_name(icon_name, Gtk.IconSize.MENU)
191 self.label_credentials.set_text(_(text))
192 self.input_user.set_sensitive(not bool(self.creds.username))
193 self.input_password.set_sensitive(True)
194 self.button_apply.set_sensitive(valid)
195
196 self.api.die()
197
0\ No newline at end of file198\ No newline at end of file
1199
=== added file 'account_settings_provider.py'
--- account_settings_provider.py 1970-01-01 00:00:00 +0000
+++ account_settings_provider.py 2015-12-05 00:25:32 +0000
@@ -0,0 +1,128 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# settings_provider.py v0.10.3
4# Provides settins to GMail notify
5#
6# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21from __future__ import print_function
22
23from gi.repository import Gio
24
25def create_settings_provider(username):
26 if username:
27 return AccountSettingsProvider(username)
28 else:
29 return DefaultSettingsProvider()
30
31class AccountSettingsProvider:
32 def __init__(self, username):
33 self.client = Gio.Settings("net.launchpad.gm-notify.account", "/net/launchpad/gm-notify/" + username + "/")
34
35 def retrieve_sound_file(self, default_file = None):
36 soundfile = self.client.get_string("soundfile")
37 if soundfile == '':
38 soundfile = default_file
39 return soundfile
40
41 def retrieve_sound_enabled(self):
42 return self.client.get_boolean("play-sound")
43
44 def retrieve_preferred_port(self):
45 return self.client.get_int("preferred-port")
46
47 def retrieve_ignore_inbox(self):
48 return self.client.get_boolean("ignore-inbox")
49
50
51 def retrieve_labels(self):
52 return self.client.get_strv("labels")
53
54
55 def retrieve_use_mail_client(self):
56 return self.client.get_boolean("openclient")
57
58 def save_labels(self, labels):
59 self.client.set_strv("labels", labels)
60
61
62 def save_ignore_inbox(self, ignore_inbox):
63 self.client.set_boolean("ignore-inbox", ignore_inbox)
64
65
66 def save_use_mail_client(self, use_mail_client):
67 self.client.set_boolean("openclient", use_mail_client)
68
69
70 def save_sound_enabled(self, enabled):
71 self.client.set_boolean("play-sound", enabled)
72
73
74 def save_sound_file(self, sound_file):
75 self.client.set_string("soundfile", sound_file)
76
77
78 def save_preferred_port(self, preferred_port):
79 self.client.set_int("preferred-port", preferred_port)
80
81 def remove_all_settings(self):
82 self.client.reset("")
83
84class DefaultSettingsProvider:
85 def retrieve_sound_file(self, default_file = None):
86 return default_file
87
88 def retrieve_sound_enabled(self):
89 return False
90
91 def retrieve_preferred_port(self):
92 return 443
93
94 def retrieve_ignore_inbox(self):
95 return False
96
97 def retrieve_labels(self):
98 return ''
99
100 def retrieve_use_mail_client(self):
101 return False
102
103 def save_labels(self, mailboxes):
104 raise NotImplementedError("Saving not supported")
105
106
107 def save_ignore_inbox(self, ignore_inbox):
108 raise NotImplementedError("Saving not supported")
109
110
111 def save_use_mail_client(self, use_mail_client):
112 raise NotImplementedError("Saving not supported")
113
114
115 def save_sound_enabled(self, enabled):
116 raise NotImplementedError("Saving not supported")
117
118
119 def save_sound_file(self, sound_file):
120 raise NotImplementedError("Saving not supported")
121
122
123 def save_preferred_port(self, preferred_port):
124 print("Ignoring save_preferred_port")
125
126 def remove_all_settings(self):
127 raise NotImplementedError("Removing not supported")
128
0129
=== modified file 'data/net.launchpad.gm-notify.gschema.xml'
--- data/net.launchpad.gm-notify.gschema.xml 2015-12-05 00:25:32 +0000
+++ data/net.launchpad.gm-notify.gschema.xml 2015-12-05 00:25:32 +0000
@@ -1,25 +1,37 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<schemalist>2<schemalist>
3 <schema path="/net/launchpad/gm-notify/" id="net.launchpad.gm-notify" gettext-domain="gm-notify">3 <schema id="net.launchpad.gm-notify.account" gettext-domain="gm-notify">
4 <key type="b" name="play-sound">4 <key type="b" name="play-sound">
5 <default>false</default>5 <default>false</default>
6 <summary>Shall we play a sound on new mail?</summary>6 <summary>Shall we play a sound on new mail?</summary>
7 </key>7 </key>
8 <key type="b" name="ignore-inbox">8 <key type="b" name="ignore-inbox">
9 <default>false</default>9 <default>false</default>
10 <summary>If true, there won't be notifications regarding just your inbox.</summary>10 <summary>If true, there won't be notifications regarding
11 </key>11 just your
12 <key type="s" name="soundfile">12 inbox.
13 <default>''</default>13 </summary>
14 <summary>Path to the soundfile which should be played when a new mail arrives</summary>14 </key>
15 </key>15 <key type="s" name="soundfile">
16 <key type="as" name="mailboxes">16 <default>''</default>
17 <default>[]</default>17 <summary>Path to the soundfile which should be played when a
18 <summary>List containing mailboxes to check</summary>18 new mail
19 </key>19 arrives
20 <key type="b" name="openclient">20 </summary>
21 <default>false</default>21 </key>
22 <summary>Shall we open the default mail client or webinterface?</summary>22 <key type="as" name="labels">
23 </key>23 <default>[]</default>
24 </schema>24 <summary>List containing labels to check</summary>
25 </key>
26 <key type="b" name="openclient">
27 <default>false</default>
28 <summary>Shall we open the default mail client or
29 webinterface?
30 </summary>
31 </key>
32 <key type="i" name="preferred-port">
33 <default>443</default>
34 <summary>Port used to connect to Google services</summary>
35 </key>
36 </schema>
25</schemalist>37</schemalist>
2638
=== modified file 'debian/changelog'
--- debian/changelog 2010-05-05 19:51:23 +0000
+++ debian/changelog 2015-12-05 00:25:32 +0000
@@ -1,3 +1,47 @@
1gm-notify (1.0.0-0ubuntu1) vivid; urgency=medium
2
3 * Added multi-account support
4 * Added support for multiple ports (default port 443)
5 * Added error messages
6 * Refactored the code
7 * Made app a single instance
8
9 -- Mateusz Balbus <balbusm@gmail.com> Thu, 15 Oct 2015 15:43:32 +0200
10
11gm-notify (0.10.3-0ubuntu3) trusty; urgency=low
12
13 * Convert to dh_python2.
14 * Wrap and sort.
15 * Bump Standards-Version to 3.9.4.
16 * Update copyright fields.
17
18 -- Logan Rosen <logan@ubuntu.com> Tue, 19 Nov 2013 23:47:50 -0500
19
20gm-notify (0.10.3-0ubuntu2) trusty; urgency=low
21
22 * debian/control: Add missing dependency on
23 python-glade2 (LP: # 863855).
24 * debian/copyright: Tweak to bring up to latest
25 DEP-5 spec.
26
27 -- Andrew Starr-Bochicchio <a.starr.b@gmail.com> Sat, 01 Oct 2011 03:03:20 -0400
28
29gm-notify (0.10.3-0ubuntu1) maverick; urgency=low
30
31 * New upstream bug fix release.
32 - Fix CPU usage issue with subprocess.call. (LP: #616781)
33 * debian/patches/scripts-with-extensions-in-PATH:
34 - Drop, included upstream.
35 * debian/control: Bump Standards-Version to 3.9.1.
36
37 -- Andrew Starr-Bochicchio <a.starr.b@gmail.com> Sat, 18 Sep 2010 15:41:02 -0400
38
39gm-notify (0.10.2-0ubuntu1) maverick; urgency=low
40
41 * Initial release (LP: #596313).
42
43 -- Andrew Starr-Bochicchio <a.starr.b@gmail.com> Thu, 01 Jul 2010 11:28:14 -0400
44
1gm-notify (0.10.2-1~ppa1) lucid; urgency=low45gm-notify (0.10.2-1~ppa1) lucid; urgency=low
246
3 * Release 0.10.2 / see http://bleedingpaper.com/gm-notify47 * Release 0.10.2 / see http://bleedingpaper.com/gm-notify
448
=== modified file 'debian/compat'
--- debian/compat 2009-05-07 06:24:15 +0000
+++ debian/compat 2015-12-05 00:25:32 +0000
@@ -1,1 +1,1 @@
1719
22
=== modified file 'debian/control'
--- debian/control 2010-04-21 11:56:58 +0000
+++ debian/control 2015-12-05 00:25:32 +0000
@@ -1,18 +1,31 @@
1Source: gm-notify1Source: gm-notify
2Section: mail2Section: mail
3Priority: extra3Priority: extra
4Maintainer: Ken VanDine <ken.vandine@canonical.com>4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
5XS-Python-Version: >= 2.65XSBC-Original-Maintainer: Andrew Starr-Bochicchio <a.starr.b@gmail.com>
6Build-Depends: cdbs, debhelper (>= 7), python-central (>= 0.6), python6X-Python-Version: >= 2.6
7Standards-Version: 3.8.07Build-Depends: debhelper (>= 9),
8 dh-python,
9 python (>= 2.6.6-3~),
10 python-setuptools
11Standards-Version: 3.9.4
8Homepage: https://launchpad.net/gm-notify12Homepage: https://launchpad.net/gm-notify
913
10Package: gm-notify14Package: gm-notify
11Architecture: all15Architecture: all
12XB-Python-Version: ${python:Versions}
13Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, python-indicate, python-notify, python-gst0.10, python-gtk2, python-gconf, python-twisted-words, python-gnomekeyring
14Provides: ${python:Provides}16Provides: ${python:Provides}
15Description: GMail Notifier 17Depends: python-gconf,
16 A simple and lightweight highly Ubuntu 10.04 integrated GMail Notifier which 18 python-glade2,
17 takes advantages of the new and nice notify-osd and indicator-applet. 19 python-gnomekeyring,
18 Because of this it will not run with older Ubuntu versions.20 python-gtk2,
21 python-indicate,
22 python-notify,
23 python-twisted-words,
24 gir1.2-gstreamer-1.0,
25 ${misc:Depends},
26 ${python:Depends},
27 ${shlibs:Depends}
28Description: Highly Ubuntu integrated GMail notifier
29 gm-notify is a simple, lightweight, and highly Ubuntu integrated
30 GMail notifier which takes advantages of features like notify-osd
31 and indicator-applet.
1932
=== modified file 'debian/copyright'
--- debian/copyright 2009-05-07 06:24:15 +0000
+++ debian/copyright 2015-12-05 00:25:32 +0000
@@ -1,19 +1,34 @@
1This package was debianized by Ken VanDine <ken.vandine@canonical.com> on1Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2Thu, 07 May 2009 01:39:52 -0400.2Upstream-Name: gm-notify
33Source: https://launchpad.net/gm-notify
4It was downloaded from https://launchpad.net/gm-notify4Upstream-Contact: Alexander Hungenberg <alexander.hungenberg@gmail.com>
55
6Upstream Author:6Files: *
77Copyright: 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com>
8 Alexander Hungenberg <alexander.hungenberg@gmail.com>8License: GPL-3+
99
10Copyright:10Files: po/*
1111Copyright: 2009, Rosetta Contributors and Canonical Ltd.
12 Copyright (c) 2009, Alexander Hungenberg <alexander.hungenberg@gmail.com>12License: GPL-3+
1313
14License:14Files: debian/*
1515Copyright: 2009, Ken VanDine <ken.vandine@canonical.com>,
16 GPL-316 2010, Andrew Starr-Bochicchio <a.starr.b@gmail.com>
1717License: GPL-3+
18The Debian packaging is copyright 2009, Ken VanDine <ken.vandine@canonical.com> and18
19is licensed under the GPL, see `/usr/share/common-licenses/GPL'.19License: GPL-3+
20 This package is free software; you can redistribute it and/or modify
21 it under the terms of the GNU General Public License as published by
22 the Free Software Foundation; either version 3 of the License, or
23 (at your option) any later version.
24 .
25 This package is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 GNU General Public License for more details.
29 .
30 You should have received a copy of the GNU General Public License
31 along with this package; if not, write to the Free Software
32 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
33 .
34 On Debian systems, see `/usr/share/common-licenses/GPL-3'.
2035
=== modified file 'debian/rules'
--- debian/rules 2009-05-07 13:09:38 +0000
+++ debian/rules 2015-12-05 00:25:32 +0000
@@ -1,14 +1,6 @@
1#!/usr/bin/make -f1#!/usr/bin/make -f
22
3DEB_PYTHON_SYSTEM := pycentral3export PYBUILD_NAME=gm-notify
4export DH_PYCENTRAL=include-links4%:
55 dh $@ --with python2 --buildsystem=pybuild
6include /usr/share/cdbs/1/rules/debhelper.mk6
7include /usr/share/cdbs/1/class/python-distutils.mk
8
9DEB_DH_INSTALL_SOURCEDIR := debian/tmp
10DEB_PYTHON_INSTALL_ARGS_ALL := --no-compile --install-layout=deb
11
12# Add here any variable or target overrides you need.
13binary-post-install/gm-notify::
14 dh_gconf -p$(cdbs_curpkg) $(DEB_DH_GCONF_ARGS)
157
=== added directory 'debian/source'
=== added file 'debian/source/format'
--- debian/source/format 1970-01-01 00:00:00 +0000
+++ debian/source/format 2015-12-05 00:25:32 +0000
@@ -0,0 +1,1 @@
13.0 (quilt)
02
=== modified file 'gm-config.ui'
--- gm-config.ui 2015-12-05 00:25:32 +0000
+++ gm-config.ui 2015-12-05 00:25:32 +0000
@@ -1,7 +1,8 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<!-- Generated with glade 3.18.3 -->
2<interface>3<interface>
3 <!-- interface-requires gtk+ 3.0 -->4 <requires lib="gtk+" version="3.10"/>
4 <object class="GtkWindow" id="gmnotify_config_main">5 <object class="GtkWindow" id="gmnotify_add_account">
5 <property name="can_focus">False</property>6 <property name="can_focus">False</property>
6 <property name="border_width">10</property>7 <property name="border_width">10</property>
7 <property name="title" translatable="yes">GMail Notifier</property>8 <property name="title" translatable="yes">GMail Notifier</property>
@@ -36,8 +37,6 @@
36 <packing>37 <packing>
37 <property name="left_attach">0</property>38 <property name="left_attach">0</property>
38 <property name="top_attach">0</property>39 <property name="top_attach">0</property>
39 <property name="width">1</property>
40 <property name="height">1</property>
41 </packing>40 </packing>
42 </child>41 </child>
43 <child>42 <child>
@@ -49,8 +48,6 @@
49 <packing>48 <packing>
50 <property name="left_attach">0</property>49 <property name="left_attach">0</property>
51 <property name="top_attach">1</property>50 <property name="top_attach">1</property>
52 <property name="width">1</property>
53 <property name="height">1</property>
54 </packing>51 </packing>
55 </child>52 </child>
56 <child>53 <child>
@@ -58,13 +55,10 @@
58 <property name="visible">True</property>55 <property name="visible">True</property>
59 <property name="can_focus">True</property>56 <property name="can_focus">True</property>
60 <property name="invisible_char">●</property>57 <property name="invisible_char">●</property>
61 <property name="invisible_char_set">True</property>
62 </object>58 </object>
63 <packing>59 <packing>
64 <property name="left_attach">1</property>60 <property name="left_attach">1</property>
65 <property name="top_attach">0</property>61 <property name="top_attach">0</property>
66 <property name="width">1</property>
67 <property name="height">1</property>
68 </packing>62 </packing>
69 </child>63 </child>
70 <child>64 <child>
@@ -73,14 +67,11 @@
73 <property name="can_focus">True</property>67 <property name="can_focus">True</property>
74 <property name="visibility">False</property>68 <property name="visibility">False</property>
75 <property name="invisible_char">●</property>69 <property name="invisible_char">●</property>
76 <property name="invisible_char_set">True</property>
77 <signal name="focus-out-event" handler="on_input_password_focus_out_event" swapped="no"/>70 <signal name="focus-out-event" handler="on_input_password_focus_out_event" swapped="no"/>
78 </object>71 </object>
79 <packing>72 <packing>
80 <property name="left_attach">1</property>73 <property name="left_attach">1</property>
81 <property name="top_attach">1</property>74 <property name="top_attach">1</property>
82 <property name="width">1</property>
83 <property name="height">1</property>
84 </packing>75 </packing>
85 </child>76 </child>
86 <child>77 <child>
@@ -92,8 +83,6 @@
92 <packing>83 <packing>
93 <property name="left_attach">0</property>84 <property name="left_attach">0</property>
94 <property name="top_attach">2</property>85 <property name="top_attach">2</property>
95 <property name="width">1</property>
96 <property name="height">1</property>
97 </packing>86 </packing>
98 </child>87 </child>
99 <child>88 <child>
@@ -105,8 +94,6 @@
105 <packing>94 <packing>
106 <property name="left_attach">1</property>95 <property name="left_attach">1</property>
107 <property name="top_attach">2</property>96 <property name="top_attach">2</property>
108 <property name="width">1</property>
109 <property name="height">1</property>
110 </packing>97 </packing>
111 </child>98 </child>
112 </object>99 </object>
@@ -329,44 +316,6 @@
329 <property name="position">2</property>316 <property name="position">2</property>
330 </packing>317 </packing>
331 </child>318 </child>
332 <child>
333 <object class="GtkFrame" id="frame_autostart">
334 <property name="visible">True</property>
335 <property name="can_focus">False</property>
336 <property name="label_xalign">0</property>
337 <property name="shadow_type">none</property>
338 <child>
339 <object class="GtkAlignment" id="alignment4">
340 <property name="visible">True</property>
341 <property name="can_focus">False</property>
342 <property name="left_padding">12</property>
343 <child>
344 <object class="GtkCheckButton" id="checkbutton_autostart">
345 <property name="label" translatable="yes">Start automatically on logon</property>
346 <property name="visible">True</property>
347 <property name="can_focus">True</property>
348 <property name="receives_default">False</property>
349 <property name="xalign">0.5</property>
350 <property name="draw_indicator">True</property>
351 </object>
352 </child>
353 </object>
354 </child>
355 <child type="label">
356 <object class="GtkLabel" id="label1">
357 <property name="visible">True</property>
358 <property name="can_focus">False</property>
359 <property name="label" translatable="yes">&lt;b&gt;Autostart:&lt;/b&gt;</property>
360 <property name="use_markup">True</property>
361 </object>
362 </child>
363 </object>
364 <packing>
365 <property name="expand">False</property>
366 <property name="fill">True</property>
367 <property name="position">3</property>
368 </packing>
369 </child>
370 </object>319 </object>
371 <packing>320 <packing>
372 <property name="position">1</property>321 <property name="position">1</property>
373322
=== added file 'gm-list.ui'
--- gm-list.ui 1970-01-01 00:00:00 +0000
+++ gm-list.ui 2015-12-05 00:25:32 +0000
@@ -0,0 +1,224 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!-- Generated with glade 3.18.3 -->
3<interface>
4 <requires lib="gtk+" version="3.10"/>
5 <object class="GtkListStore" id="accounts">
6 <columns>
7 <!-- column-name mail -->
8 <column type="gchararray"/>
9 </columns>
10 </object>
11 <object class="GtkWindow" id="gmnotify_config_main">
12 <property name="width_request">400</property>
13 <property name="height_request">250</property>
14 <property name="can_focus">False</property>
15 <property name="border_width">10</property>
16 <property name="title" translatable="yes">GMail Notifier</property>
17 <property name="resizable">False</property>
18 <property name="window_position">center</property>
19 <property name="default_width">512</property>
20 <property name="default_height">512</property>
21 <property name="icon_name">evolution</property>
22 <property name="gravity">center</property>
23 <child>
24 <object class="GtkBox" id="box1">
25 <property name="visible">True</property>
26 <property name="can_focus">False</property>
27 <property name="opacity">0.97999999999999998</property>
28 <property name="orientation">vertical</property>
29 <child>
30 <object class="GtkNotebook" id="notebook_main">
31 <property name="visible">True</property>
32 <property name="can_focus">True</property>
33 <child>
34 <object class="GtkBox" id="box2">
35 <property name="visible">True</property>
36 <property name="can_focus">False</property>
37 <property name="margin_left">1</property>
38 <property name="margin_right">1</property>
39 <property name="margin_top">1</property>
40 <property name="margin_bottom">1</property>
41 <property name="spacing">5</property>
42 <child>
43 <object class="GtkToolbar" id="add_remove_toolbar">
44 <property name="visible">True</property>
45 <property name="can_focus">False</property>
46 <property name="orientation">vertical</property>
47 <property name="toolbar_style">both</property>
48 <child>
49 <object class="GtkToolButton" id="add_button">
50 <property name="use_action_appearance">False</property>
51 <property name="visible">True</property>
52 <property name="can_focus">False</property>
53 <property name="has_tooltip">True</property>
54 <property name="tooltip_markup" translatable="yes">Add...</property>
55 <property name="tooltip_text" translatable="yes">Add...</property>
56 <property name="use_underline">True</property>
57 <property name="stock_id">gtk-add</property>
58 </object>
59 <packing>
60 <property name="expand">False</property>
61 <property name="homogeneous">True</property>
62 </packing>
63 </child>
64 <child>
65 <object class="GtkToolButton" id="remove_button">
66 <property name="use_action_appearance">False</property>
67 <property name="visible">True</property>
68 <property name="can_focus">False</property>
69 <property name="has_tooltip">True</property>
70 <property name="tooltip_markup" translatable="yes">Remove...</property>
71 <property name="tooltip_text" translatable="yes">Remove...</property>
72 <property name="use_underline">True</property>
73 <property name="stock_id">gtk-remove</property>
74 </object>
75 <packing>
76 <property name="expand">False</property>
77 <property name="homogeneous">True</property>
78 </packing>
79 </child>
80 </object>
81 <packing>
82 <property name="expand">False</property>
83 <property name="fill">True</property>
84 <property name="position">0</property>
85 </packing>
86 </child>
87 <child>
88 <object class="GtkTreeView" id="accounts_treeview">
89 <property name="visible">True</property>
90 <property name="can_focus">True</property>
91 <property name="margin_left">1</property>
92 <property name="margin_right">1</property>
93 <property name="margin_top">1</property>
94 <property name="margin_bottom">1</property>
95 <property name="hscroll_policy">natural</property>
96 <property name="vscroll_policy">natural</property>
97 <property name="model">accounts</property>
98 <property name="search_column">1</property>
99 <property name="show_expanders">False</property>
100 <property name="enable_grid_lines">horizontal</property>
101 <child internal-child="selection">
102 <object class="GtkTreeSelection" id="treeview-selection">
103 <property name="mode">browse</property>
104 </object>
105 </child>
106 <child>
107 <object class="GtkTreeViewColumn" id="treeviewcolumn_name">
108 <property name="sizing">fixed</property>
109 <property name="title" translatable="yes">Accounts List</property>
110 <child>
111 <object class="GtkCellRendererText" id="cellrenderertext_name"/>
112 <attributes>
113 <attribute name="text">0</attribute>
114 </attributes>
115 </child>
116 </object>
117 </child>
118 </object>
119 <packing>
120 <property name="expand">True</property>
121 <property name="fill">True</property>
122 <property name="position">1</property>
123 </packing>
124 </child>
125 </object>
126 </child>
127 <child type="tab">
128 <object class="GtkLabel" id="label_accounts">
129 <property name="visible">True</property>
130 <property name="can_focus">False</property>
131 <property name="label" translatable="yes">Accounts</property>
132 </object>
133 <packing>
134 <property name="tab_fill">False</property>
135 </packing>
136 </child>
137 <child>
138 <object class="GtkFrame" id="frame_autostart">
139 <property name="visible">True</property>
140 <property name="can_focus">False</property>
141 <property name="label_xalign">0</property>
142 <property name="shadow_type">none</property>
143 <child>
144 <object class="GtkAlignment" id="alignment4">
145 <property name="visible">True</property>
146 <property name="can_focus">False</property>
147 <property name="left_padding">12</property>
148 <child>
149 <object class="GtkCheckButton" id="checkbutton_autostart">
150 <property name="label" translatable="yes">Start automatically on logon</property>
151 <property name="visible">True</property>
152 <property name="can_focus">True</property>
153 <property name="receives_default">False</property>
154 <property name="valign">start</property>
155 <property name="xalign">0</property>
156 <property name="yalign">0</property>
157 <property name="draw_indicator">True</property>
158 </object>
159 </child>
160 </object>
161 </child>
162 <child type="label">
163 <object class="GtkLabel" id="label_autostart">
164 <property name="visible">True</property>
165 <property name="can_focus">False</property>
166 <property name="label" translatable="yes">&lt;b&gt;Autostart:&lt;/b&gt;</property>
167 <property name="use_markup">True</property>
168 </object>
169 </child>
170 </object>
171 <packing>
172 <property name="position">1</property>
173 </packing>
174 </child>
175 <child type="tab">
176 <object class="GtkLabel" id="label_preferences">
177 <property name="visible">True</property>
178 <property name="can_focus">False</property>
179 <property name="label" translatable="yes">Preferences</property>
180 </object>
181 <packing>
182 <property name="position">1</property>
183 <property name="tab_fill">False</property>
184 </packing>
185 </child>
186 <child>
187 <placeholder/>
188 </child>
189 <child type="tab">
190 <placeholder/>
191 </child>
192 </object>
193 <packing>
194 <property name="expand">True</property>
195 <property name="fill">True</property>
196 <property name="position">0</property>
197 </packing>
198 </child>
199 <child>
200 <object class="GtkButton" id="button_ok">
201 <property name="label">gtk-ok</property>
202 <property name="width_request">100</property>
203 <property name="visible">True</property>
204 <property name="can_focus">True</property>
205 <property name="receives_default">True</property>
206 <property name="halign">end</property>
207 <property name="valign">center</property>
208 <property name="margin_left">5</property>
209 <property name="margin_right">5</property>
210 <property name="margin_top">5</property>
211 <property name="margin_bottom">5</property>
212 <property name="use_stock">True</property>
213 <property name="always_show_image">True</property>
214 </object>
215 <packing>
216 <property name="expand">False</property>
217 <property name="fill">True</property>
218 <property name="position">1</property>
219 </packing>
220 </child>
221 </object>
222 </child>
223 </object>
224</interface>
0225
=== modified file 'gm-notify'
--- gm-notify 2015-12-05 00:25:32 +0000
+++ gm-notify 2015-12-05 00:25:32 +0000
@@ -1,10 +1,11 @@
1#!/usr/bin/python1#!/usr/bin/python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
33
4# gm-notify v0.10.34# gm-notify v1.0
5# a simple and lightweight GMail-Notifier for ubuntu and notify-osd5# a simple and lightweight GMail-Notifier for ubuntu and notify-osd
6#6#
7# Copyright (c) 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com>7# Copyright (c) 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com>
8# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com>
8# 9#
9# This program is free software: you can redistribute it and/or modify10# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by11# it under the terms of the GNU General Public License as published by
@@ -27,7 +28,9 @@
27import gettext28import gettext
28import webbrowser29import webbrowser
2930
30from gi.repository import Gio, GLib, MessagingMenu, Notify31import gi
32gi.require_version('Gst', '1.0')
33from gi.repository import Gio, GLib, Gst, MessagingMenu, Notify
3134
32from twisted.internet import gireactor35from twisted.internet import gireactor
33gireactor.install()36gireactor.install()
@@ -35,6 +38,7 @@
35from twisted.words.protocols.jabber import jid38from twisted.words.protocols.jabber import jid
3639
37from gtalk import MailChecker40from gtalk import MailChecker
41import account_settings_provider
38import gm_notify_keyring as keyring42import gm_notify_keyring as keyring
3943
40_ = gettext.translation('gm-notify', fallback=True).ugettext44_ = gettext.translation('gm-notify', fallback=True).ugettext
@@ -43,6 +47,10 @@
4347
44MAILBOXES_URLS = { "inbox": "" }48MAILBOXES_URLS = { "inbox": "" }
4549
50GMAIL_DOMAINS = ['gmail.com','googlemail.com']
51
52EMAIL_RETRIEVAL_ERROR = "--retrieval-error"
53
46class PathNotFound(Exception): pass54class PathNotFound(Exception): pass
4755
48def get_executable_path(name):56def get_executable_path(name):
@@ -54,28 +62,15 @@
54 if os.path.exists(path) and os.access(path, os.X_OK): return path62 if os.path.exists(path) and os.access(path, os.X_OK): return path
55 raise PathNotFound("%s not found" % name)63 raise PathNotFound("%s not found" % name)
5664
57def play_sound(name):65class Account:
58 '''Spawns a canberra-gtk-play process to play the sound'''66 def __init__(self):
59 if name is None:67 self.username = None
60 return68 self.client = None
61 player_path = "/usr/bin/canberra-gtk-play"69 self.soundfile = None
62 # Not installed?70 self.ignore_inbox = None
63 if not os.path.exists(player_path):71 self.domain = None
64 return72 self.labels = None
65 command = [player_path]73 self.use_mail_client = None
66 # File exists, so use the file flag
67 if os.path.exists(name):
68 command.extend(["-f", name])
69 # Assume it is a sound id
70 else:
71 command.extend(["-i", name])
72 try:
73 result = GLib.spawn_async(command)
74 except:
75 return
76 # Does nothing but the documentation says to do it anyway
77 if len(result) > 0:
78 GLib.spawn_close_pid(result[0])
7974
80class CheckMail(Gio.Application):75class CheckMail(Gio.Application):
81 def __init__(self):76 def __init__(self):
@@ -84,14 +79,36 @@
84 super(CheckMail, self).__init__(application_id="net.launchpad.gm-notify",79 super(CheckMail, self).__init__(application_id="net.launchpad.gm-notify",
85 flags=Gio.ApplicationFlags.FLAGS_NONE)80 flags=Gio.ApplicationFlags.FLAGS_NONE)
8681
82 self.player = None
87 self._has_activated = False83 self._has_activated = False
88 self._counts = {}84 self._counts = {}
89 self.connect("activate", self.on_activate)85 self.connect("activate", self.on_activate)
9086
87 def play_sound(self, name):
88 '''Uses GSound to play the music'''
89 if name is None:
90 return
91
92 if not self.player:
93 Gst.init()
94 self.player = Gst.ElementFactory.make("playbin", "player")
95
96 self.player.set_property("uri", "file://" + name)
97 result = self.player.set_state(Gst.State.PLAYING)
98 if result == Gst.StateChangeReturn.FAILURE:
99 print("Unable to play " + name)
100
91 def on_remote_quit(self, action, args):101 def on_remote_quit(self, action, args):
92 '''Stops the application when the "remote-quit" action is activated'''102 '''Stops the application when the "remote-quit" action is activated'''
93 reactor.stop()103 reactor.stop()
94104
105 def run(self, args):
106 self.register();
107 if self.get_is_remote():
108 self.activate()
109 else:
110 super(CheckMail, self).run(args)
111
95 def on_activate(self, app):112 def on_activate(self, app):
96 '''When first receiving the activate signal, initialize the primary113 '''When first receiving the activate signal, initialize the primary
97 instance. On subsequent activate signals, we are being activated from a114 instance. On subsequent activate signals, we are being activated from a
@@ -113,8 +130,8 @@
113 sys.exit(-1)130 sys.exit(-1)
114131
115 keys = keyring.Keyring("GMail", "mail.google.com", "http")132 keys = keyring.Keyring("GMail", "mail.google.com", "http")
116 if keys.has_credentials():133 if keys.has_any_credentials():
117 self.creds = keys.get_credentials()134 creds = keys.get_all_credentials()
118 else:135 else:
119 print("Failed to get credentials")136 print("Failed to get credentials")
120 # Start gm-notify-config if no credentials are found137 # Start gm-notify-config if no credentials are found
@@ -122,110 +139,140 @@
122 subprocess.call(get_executable_path("gm-notify-config"))139 subprocess.call(get_executable_path("gm-notify-config"))
123 except PathNotFound:140 except PathNotFound:
124 print(_("gm-notify-config utility was not found"))141 print(_("gm-notify-config utility was not found"))
142
125 sys.exit(-1)143 sys.exit(-1)
126 144
127 # check if we use Google Apps to start the correct webinterface
128 gmail_domains = ['gmail.com','googlemail.com']
129 self.jid = jid.JID(self.creds[0])
130 if self.jid.host in gmail_domains:
131 self.domain = None
132 else:
133 self.domain = self.jid.host
134
135 self.client = Gio.Settings("net.launchpad.gm-notify")
136
137 # Set up the sound file
138 self._soundfile = self.client.get_string("soundfile")
139 if self._soundfile == '':
140 self._soundfile = "message-new-instant"
141 if not self.client.get_boolean("play-sound"):
142 self._soundfile = None
143
144 # Messaging Menu integration145 # Messaging Menu integration
145 self._m_menu = MessagingMenu.App.new("gm-notify.desktop")146 self._m_menu = MessagingMenu.App.new("gm-notify.desktop")
146 self._m_menu.register()147 self._m_menu.register()
147 self._m_menu.connect("activate-source", self.source_clicked)148 self._m_menu.connect("activate-source", self.source_clicked)
148149
149 # Read ignore-inbox value. If true you will only receive notifications150 self.accounts = self.init_accounts(creds)
150 # about configured labels151
151 self.ignore_inbox = self.client.get_boolean("ignore-inbox")152
152 153 def init_accounts(self, creds):
153 # Retrieve the mailbox we're gonna check154 accounts = {}
154 self.mailboxes = self.client.get_strv("mailboxes")155 for credentails in creds:
155 self.mailboxes.insert(0, "inbox")156 account = Account()
156 self.checker = MailChecker(self.jid, self.creds[1], self.mailboxes[1:], self.new_mail, self.update_count)157 account.username = credentails.username
157 self.checker.connect()158 local_jid = jid.JID(account.username)
158 159 # check if we use Google Apps to start the correct webinterface
160 if local_jid.host not in GMAIL_DOMAINS:
161 account.domain = local_jid.host
162
163 client = account_settings_provider.create_settings_provider(account.username)
164 account.client = client
165
166 account.use_mail_client = client.retrieve_use_mail_client()
167
168 # Set up the sound file
169 if client.retrieve_sound_enabled():
170 account.soundfile = client.retrieve_sound_file("message-new-instant")
171 # Read ignore-inbox value. If true you will only receive notifications
172 # about configured labels
173 account.ignore_inbox = client.retrieve_ignore_inbox()
174
175 # Retrieve the mailbox we're gonna check
176 account.labels = client.retrieve_labels()
177 account.labels.insert(0, "inbox")
178 account.checker = MailChecker(local_jid, credentails.password, client, account.labels[1:], self.new_mail, self.update_count)
179 account.checker.setOnConnectionErrorCB(self.checker_connection_error)
180 account.checker.setOnAuthFailed(self.checker_auth_failed)
181 account.checker.setOnAuthSucceeded(self.checker_auth_succeeded)
182 account.checker.connect()
183
184 accounts[credentails.username] = account
185 return accounts
186
159 def indicator_clicked(self):187 def indicator_clicked(self):
160 '''called when "Google Mail" is clicked in indicator-messages and188 '''called when "Google Mail" is clicked in indicator-messages and
161 performs a Mail Check'''189 performs a Mail Check'''
162 for label in self.mailboxes:190 for username, account in self.accounts.items():
163 self.remove_attention(label)191 for label in account.labels:
164 192 self.remove_attention(self.compose_id(username, label))
165 self.checker.queryInbox()193
194 for username, account in self.accounts.items():
195 account.checker.queryInbox()
196
197 def compose_label(self, username, label):
198 return "%s (%s)" % (username, label)
199
200 def compose_id(self, username, label):
201 return "%s&%s" % (username, label)
202
203 def decompose_id(self, id):
204 decomposed = id.split("&")
205 return (decomposed[0], decomposed[1])
166206
167 def remove_attention(self, label):207 def remove_attention(self, label):
168 '''Removes attention from the label source if it exists'''208 '''Removes attention from the label source if it exists'''
169 if self._m_menu.has_source(label):209 if self._m_menu.has_source(label):
170 self._m_menu.remove_attention(label)210 self._m_menu.remove_attention(label)
171211
172 def has_source(self, label):212 def has_source(self, username, label):
173 '''Returns true if we have this label, or if we don't and it is in our213 '''Returns true if we have this label, or if we don't and it is in our
174 mailboxes list, create it'''214 mailboxes list, create it'''
175 if label == "inbox" and self.ignore_inbox:215 account = self.accounts[username]
216 if label == "inbox" and account.ignore_inbox:
176 return False217 return False
177 elif label in self.mailboxes:218 elif label in account.labels:
178 if not self._m_menu.has_source(label):219 mail_label = self.compose_id(username, label)
220 if not self._m_menu.has_source(mail_label):
179 if label in MAILBOXES_NAMES:221 if label in MAILBOXES_NAMES:
180 name = MAILBOXES_NAMES[label]222 name = MAILBOXES_NAMES[label]
181 else:223 else:
182 name = label224 name = label
225 name_label = self.compose_label(username, name)
183 if label == "inbox":226 if label == "inbox":
184 self._m_menu.insert_source_with_string(0, label, None, name, _("empty"))227 self._m_menu.insert_source_with_string(0, mail_label, None, name_label, _("empty"))
185 else:228 else:
186 self._m_menu.append_source_with_string(label, None, name, _("empty"))229 self._m_menu.append_source_with_string(mail_label, None, name_label, _("empty"))
187 if label in self._counts:230 if mail_label in self._counts:
188 self._m_menu.set_source_count(label, self._counts[label])231 self._m_menu.set_source_count(mail_label, self._counts[mail_label])
189 return True232 return True
190 else:233 else:
191 return False234 return False
192 235
193 def update_count(self, count):236 def update_count(self, username, count):
194 '''Updates the count for all the mailboxes'''237 '''Updates the count for all the mailboxes'''
238 account = self.accounts[username]
195 for mailbox in count.iteritems():239 for mailbox in count.iteritems():
196 if mailbox[0] == "inbox" and self.ignore_inbox:240 if mailbox[0] == "inbox" and account.ignore_inbox:
197 continue241 continue
198 242
199 if self.has_source(mailbox[0]):243 if self.has_source(username, mailbox[0]):
200 # Get the last count244 # Get the last count
201 last_count = 0245 last_count = 0
202 if mailbox[0] in self._counts:246 mail_label = self.compose_id(username, mailbox[0])
203 last_count = self._counts[mailbox[0]]247 if mail_label in self._counts:
248 last_count = self._counts[mail_label]
204 current_count = int(mailbox[1])249 current_count = int(mailbox[1])
205250
206 # Remove attention if the count has decreased251 # Remove attention if the count has decreased
207 if last_count > current_count:252 if last_count > current_count:
208 self._m_menu.remove_attention(mailbox[0])253 self._m_menu.remove_attention(mail_label)
209 if current_count > 0:254 if current_count > 0:
210 self._m_menu.set_source_count(mailbox[0], current_count)255 self._m_menu.set_source_count(mail_label, current_count)
211 # Remove the source if 0 messages, to save space256 # Remove the source if 0 messages, to save space
212 else:257 else:
213 self._m_menu.remove_source(mailbox[0])258 self._m_menu.remove_source(mail_label)
214 self._counts[mailbox[0]] = current_count259 self._counts[mail_label] = current_count
215 260
216 def new_mail(self, mails):261 def new_mail(self, username, mails):
217 '''Takes mailbox name and titles of mails, to display notification and add indicators'''262 '''Takes mailbox name and titles of mails, to display notification and add indicators'''
263 account = self.accounts[username]
218 text = ""264 text = ""
219 # aggregate the titles of the messages... cut the string if longer than 30 chars265 # aggregate the titles of the messages... cut the string if longer than 30 chars
220 for mail in mails:266 for mail in mails:
221 got_label = False267 got_label = False
222 for label in mail['labels']:268 for label in mail['labels']:
223 if label == u"^i": label = "inbox"269 if label == u"^i": label = "inbox"
224 if label == "inbox" and self.ignore_inbox:270 if label == "inbox" and account.ignore_inbox:
225 continue271 continue
226 if self.has_source(label):272 if self.has_source(username, label):
227 got_label = True273 got_label = True
228 self._m_menu.draw_attention(label)274 mail_label = self.compose_id(username, label)
275 self._m_menu.draw_attention(mail_label)
229 if not got_label: continue276 if not got_label: continue
230 277
231 if "sender_name" in mail: text += mail['sender_name'] + ":\n"278 if "sender_name" in mail: text += mail['sender_name'] + ":\n"
@@ -242,32 +289,75 @@
242 text += "- " + title + "\n"289 text += "- " + title + "\n"
243 290
244 if text:291 if text:
245 self.showNotification(_("Incoming message"), text.strip("\n"))292 self.show_notification("{0}".format(username), text.strip("\n"))
246 play_sound(self._soundfile)293 self.play_sound(self.accounts[username].soundfile)
247 294
248 def source_clicked(self, app, source_id):295 def source_clicked(self, app, source_id):
249 '''called when a label is clicked in the indicator-applet and opens the corresponding gmail page'''296 '''called when a label is clicked in the indicator-applet and opens the corresponding gmail page'''
250 if self.domain:297 # TODO: missing username
251 url = "https://mail.google.com/a/"+self.domain+"/"298 username, label = self.decompose_id(source_id)
252 else:299 if label == EMAIL_RETRIEVAL_ERROR:
253 url = "https://mail.google.com/mail/"300 return
254 301 account = self.accounts[username]
255 try:
256 url += "#%s" % MAILBOXES_URLS[source_id]
257 except KeyError:
258 url += "#label/%s" % source_id
259 302
260 # Open mail client303 # Open mail client
261 if self.client.get_boolean("openclient"):304 if account.use_mail_client:
262 try:305 try:
263 info = Gio.AppInfo.get_default_for_type("x-scheme-handler/mailto", False)306 info = Gio.AppInfo.get_default_for_type("x-scheme-handler/mailto", False)
264 info.launch(None, None)307 info.launch(None, None)
265 except:308 except:
266 pass309 pass
267 else:310 else:
311 url = self.prep_url(account, label)
268 webbrowser.open(url)312 webbrowser.open(url)
269313
270 def showNotification(self, title, message):314 def prep_url(self, account, label):
315 url_domain = self.prep_url_domain(account.domain)
316 url_label = self.prep_url_label(label)
317
318 return ("https://accounts.google.com/AccountChooser?"
319 "Email={0}"
320 "&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F{1}"
321 "&service=mail"
322 "&hd={2}").format(account.username, url_label, url_domain)
323
324 def prep_url_label(self, label):
325 if label in MAILBOXES_URLS:
326 return "%23{0}".format(MAILBOXES_URLS[label])
327 else:
328 return "%23label%2F{0}".format(label)
329
330 def prep_url_domain(self, domain):
331 if domain:
332 return domain
333 else:
334 return "default"
335
336 def checker_auth_succeeded(self, username):
337 error_label = self.compose_id(username, EMAIL_RETRIEVAL_ERROR)
338 self._m_menu.remove_source(error_label)
339
340 def checker_connection_error(self, username, error):
341 self.check_failed(username, _("Cannot retrieve emails"))
342
343 def checker_auth_failed(self, username, error):
344 self.check_failed(username, _("Authentication failed"))
345
346 def check_failed(self, username, feed):
347
348 for id in self._counts:
349 local_username, local_label = self.decompose_id(id)
350 if local_username == username:
351 self._m_menu.remove_source(id)
352
353 error_id = self.compose_id(username, EMAIL_RETRIEVAL_ERROR)
354 if self._m_menu.has_source(error_id):
355 return
356 self._m_menu.append_source(error_id, None, username + _(" - Cannot retrieve emails"))
357 n = Notify.Notification.new(_("Error for ") + username, feed, "messagebox_critical")
358 n.show()
359
360 def show_notification(self, title, message):
271 '''takes a title and a message to display the email notification. Returns the361 '''takes a title and a message to display the email notification. Returns the
272 created notification object'''362 created notification object'''
273 363
@@ -276,6 +366,13 @@
276 366
277 return n367 return n
278 368
369 def shutdown(self):
370 if self.accounts:
371 for username, account in self.accounts.items():
372 if account.checker : account.checker.die()
373
279cm = CheckMail()374cm = CheckMail()
280reactor.registerGApplication(cm)375reactor.registerGApplication(cm)
376reactor.addSystemEventTrigger('before', 'shutdown', cm.shutdown)
281reactor.run()377reactor.run()
378
282379
=== modified file 'gm-notify-config'
--- gm-notify-config 2015-12-05 00:25:32 +0000
+++ gm-notify-config 2015-12-05 00:25:32 +0000
@@ -1,10 +1,11 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
33
4# gm-notify-config v0.10.34# gm-notify-config v1.0
5# GMail Notifier Configuration Utility5# GMail Notifier Configuration Utility
6#6#
7# Copyright (c) 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com>7# Copyright (c) 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com>
8# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com>
8# 9#
9# This program is free software: you can redistribute it and/or modify10# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by11# it under the terms of the GNU General Public License as published by
@@ -27,15 +28,17 @@
27import subprocess28import subprocess
28import shutil29import shutil
2930
30from gi.repository import Gio, Gtk31from gi.repository import Gio, Gtk, Gdk
3132
32from twisted.internet import gtk3reactor33from twisted.internet import gtk3reactor
33gtk3reactor.install()34gtk3reactor.install()
34from twisted.internet import reactor35from twisted.internet import reactor
35from twisted.words.protocols.jabber import jid
3636
37import gm_notify_keyring as keyring37import gm_notify_keyring as keyring
38from gtalk import MailChecker38from gm_notify_keyring import Credentials
39from account_config import AccountConfig
40import account_settings_provider
41
3942
40_ = gettext.translation('gm-notify', fallback=True).ugettext43_ = gettext.translation('gm-notify', fallback=True).ugettext
4144
@@ -57,6 +60,13 @@
57 self.window = None60 self.window = None
58 self.connect("activate", self.on_activate)61 self.connect("activate", self.on_activate)
5962
63 def run(self, args):
64 self.register();
65 if self.get_is_remote():
66 self.activate()
67 else:
68 super(Window, self).run(args)
69
60 def on_activate(self, app):70 def on_activate(self, app):
61 '''Setup the application'''71 '''Setup the application'''
62 if self.window is not None:72 if self.window is not None:
@@ -66,59 +76,39 @@
66 # GUI initialization76 # GUI initialization
67 #####77 #####
68 self.keys = keyring.Keyring("GMail", "mail.google.com", "http")78 self.keys = keyring.Keyring("GMail", "mail.google.com", "http")
69 self.client = Gio.Settings("net.launchpad.gm-notify")
70 79
71 if os.path.exists("gm-config.ui"):80 if os.path.exists("gm-list.ui"):
72 builder_file = "gm-config.ui"81 builder_file = "gm-list.ui"
73 elif os.path.exists("/usr/local/share/gm-notify/gm-config.ui"):82 elif os.path.exists("/usr/local/share/gm-notify/gm-list.ui"):
74 builder_file = "/usr/local/share/gm-notify/gm-config.ui"83 builder_file = "/usr/local/share/gm-notify/gm-list.ui"
75 elif os.path.exists("/usr/share/gm-notify/gm-config.ui"):84 elif os.path.exists("/usr/share/gm-notify/gm-list.ui"):
76 builder_file = "/usr/share/gm-notify/gm-config.ui"85 builder_file = "/usr/share/gm-notify/gm-list.ui"
86
7787
78 self.wTree = Gtk.Builder.new()88 self.wTree = Gtk.Builder.new()
79 self.wTree.add_from_file(builder_file)89 self.wTree.add_from_file(builder_file)
80 self.wTree.set_translation_domain("gm-notify")90 self.wTree.set_translation_domain("gm-notify")
81 self.window = self.wTree.get_object("gmnotify_config_main")91 self.window = self.wTree.get_object("gmnotify_config_main")
82 self.window.show_all()92 self.window.show_all()
93 self.window.connect("delete_event", self.terminate)
94 self.window.connect("window-state-event", self.on_update_accounts)
83 self.add_window(self.window)95 self.add_window(self.window)
96
97 self.accounts = self.wTree.get_object("accounts")
8498
99 self.accounts_treeview = self.wTree.get_object("accounts_treeview")
100 self.accounts_treeview.connect("row-activated", self.on_modify_account);
101
85 self.wTree.get_object("notebook_main").set_current_page(0)102 self.wTree.get_object("notebook_main").set_current_page(0)
86 103
87 #####104
88 # Init with stored values105 self.add_button = self.wTree.get_object("add_button")
89 #####106 self.add_button.connect("clicked", self.on_add_account)
90
91 # Credentials
92 if self.keys.has_credentials():
93 self.creds = self.keys.get_credentials()
94 else:
95 self.creds = ("", "")
96 self.wTree.get_object("input_user").set_text(self.creds[0])
97 self.wTree.get_object("input_password").set_text(self.creds[1])
98
99 self.api = MailChecker("", "")
100 self.api.cb_auth_successful = self.credentials_valid
101 self.api.cb_auth_failed = self.credentials_invalid
102
103 self.check_credentials(None, None)
104
105 # Sound
106 self.wTree.get_object("checkbutton_sound").set_active(self.client.get_boolean("play-sound"))
107 if self.client.get_string("soundfile"):
108 self.wTree.get_object("fcbutton_sound").set_filename(self.client.get_string("soundfile"))
109 self.on_checkbutton_sound_toggled(self.wTree.get_object("checkbutton_sound"))
110
111 # ClickAction
112 if self.client.get_boolean("openclient"):
113 self.wTree.get_object("radiobutton_openclient").set_active(True)
114 else:
115 self.wTree.get_object("radiobutton_openweb").set_active(True)
116
117 # Mailboxes
118 mailboxes = self.client.get_strv("mailboxes")
119 self.wTree.get_object("checkbutton_inbox").set_active(self.client.get_boolean("ignore-inbox"))
120 self.wTree.get_object("entry_labels").set_text(", ".join(mailboxes))
121107
108 self.remove_button = self.wTree.get_object("remove_button")
109 self.remove_button.connect("clicked", self.on_remove_account)
110
111 self.wTree.get_object("button_ok").connect("clicked", self.terminate)
122 # Autorun112 # Autorun
123 if os.path.exists("data/gm-notify.desktop"):113 if os.path.exists("data/gm-notify.desktop"):
124 self.gm_notify_autostart_file = "data/gm-notify.desktop"114 self.gm_notify_autostart_file = "data/gm-notify.desktop"
@@ -129,36 +119,48 @@
129 self.autostart_file = os.path.expanduser("~/.config/autostart/gm-notify.desktop")119 self.autostart_file = os.path.expanduser("~/.config/autostart/gm-notify.desktop")
130 self.wTree.get_object("checkbutton_autostart").set_active(os.path.exists(self.autostart_file))120 self.wTree.get_object("checkbutton_autostart").set_active(os.path.exists(self.autostart_file))
131 121
132 # Connect signals122 def update_accounts(self, accounts, keys):
133 self.wTree.get_object("button_close").connect("clicked", self.terminate)123 accounts.clear()
134 self.wTree.get_object("button_apply").connect("clicked", self.save)124 if not self.keys.has_any_users():
135 self.wTree.get_object("input_password").connect("focus-out-event", self.check_credentials)125 return
136 self.wTree.get_object("checkbutton_sound").connect("toggled", self.on_checkbutton_sound_toggled)126 for user in keys.get_all_users():
137 127 accounts.append((user,))
128
129 def on_update_accounts(self, widget, event):
130 print("Event %s WindowState %s" % (str(event.type), str(event.new_window_state)))
131 if (event.type == Gdk.EventType.WINDOW_STATE
132 and event.new_window_state == Gdk.WindowState.FOCUSED):
133 self.update_accounts(self.accounts, self.keys)
134
135 def on_modify_account(self, widget, path, column):
136 iter = self.accounts.get_iter(path)
137 account = self.accounts[iter]
138 username = account[0]
139 credentials = self.keys.get_credentials(username)
140 account_config = AccountConfig(self.keys, credentials)
141 window = account_config.init_window(self.window)
142
143 def on_add_account(self, widget):
144 account_config = AccountConfig(self.keys, Credentials())
145 window = account_config.init_window(self.window)
146
147 def on_remove_account(self, widget):
148 tree_selection = self.accounts_treeview.get_selection()
149 accounts,iter = tree_selection.get_selected()
150 if iter is None:
151 return
152
153 account = accounts[iter]
154 username = account[0]
155 settings_provider = account_settings_provider.create_settings_provider(username)
156 settings_provider.remove_all_settings()
157 self.keys.delete_credentials(username)
158
159 accounts.remove(iter)
160
138 def save(self, widget, data=None):161 def save(self, widget, data=None):
139 '''saves the entered data and closes the app'''162 '''saves the entered data and closes the app'''
140 # Credentials163
141 self.keys.delete_credentials()
142 self.keys.set_credentials(self.wTree.get_object("input_user").get_text(),
143 self.wTree.get_object("input_password").get_text())
144
145 # Mailboxes
146 mailboxes = []
147 for label in self.wTree.get_object("entry_labels").get_text().split(","):
148 mailboxes.append(label.strip())
149 self.client.set_strv("mailboxes", mailboxes)
150 self.client.set_boolean("ignore-inbox", self.wTree.get_object("checkbutton_inbox").get_active())
151
152 # ClickAction
153 self.client.set_boolean("openclient", self.wTree.get_object("radiobutton_openclient").get_active())
154
155 # Soundfile
156 if self.wTree.get_object("checkbutton_sound").get_active() and self.wTree.get_object("fcbutton_sound").get_filename():
157 self.client.set_boolean("play-sound", True)
158 self.client.set_string("soundfile", str(self.wTree.get_object("fcbutton_sound").get_filename()))
159 else:
160 self.client.set_boolean("play-sound", False)
161
162 # Autorun164 # Autorun
163 if self.wTree.get_object("checkbutton_autostart").get_active():165 if self.wTree.get_object("checkbutton_autostart").get_active():
164 if not os.path.exists(self.autostart_file):166 if not os.path.exists(self.autostart_file):
@@ -182,62 +184,9 @@
182 # Start gm-notify itself184 # Start gm-notify itself
183 subprocess.Popen(get_executable_path("gm-notify"))185 subprocess.Popen(get_executable_path("gm-notify"))
184 186
185 def terminate(self, widget):187 def terminate(self, widget, event = None):
186 reactor.stop()188 reactor.stop()
187 189
188 def on_checkbutton_sound_toggled(self, widget):
189 self.wTree.get_object("fcbutton_sound").set_sensitive(self.wTree.get_object("checkbutton_sound").get_active())
190
191 def check_credentials(self, widget, event, data=None):
192 '''check if the given credentials are valid'''
193
194 input_user = self.wTree.get_object("input_user")
195 input_password = self.wTree.get_object("input_password")
196 image_credentials = self.wTree.get_object("image_credentials")
197 label_credentials = self.wTree.get_object("label_credentials")
198 button_apply = self.wTree.get_object("button_apply")
199 button_apply.set_sensitive(False)
200
201 # Change status text and disable input fields
202 if input_user.get_text() and input_password.get_text():
203 image_credentials.set_from_file("/usr/share/gm-notify/checking.gif")
204 label_credentials.set_text(_("checking..."))
205 input_user.set_sensitive(False)
206 input_password.set_sensitive(False)
207
208 self.api.jid = jid.JID(input_user.get_text())
209 self.api.password = input_password.get_text()
210 self.api.connect()
211 return False
212
213 def credentials_valid(self):
214 input_user = self.wTree.get_object("input_user")
215 input_password = self.wTree.get_object("input_password")
216 image_credentials = self.wTree.get_object("image_credentials")
217 label_credentials = self.wTree.get_object("label_credentials")
218 button_apply = self.wTree.get_object("button_apply")
219
220 image_credentials.set_from_icon_name("gtk-yes", Gtk.IconSize.MENU)
221 label_credentials.set_text(_("Valid credentials"))
222 button_apply.set_sensitive(True)
223 input_user.set_sensitive(True)
224 input_password.set_sensitive(True)
225
226 self.api.die()
227
228 def credentials_invalid(self):
229 input_user = self.wTree.get_object("input_user")
230 input_password = self.wTree.get_object("input_password")
231 image_credentials = self.wTree.get_object("image_credentials")
232 label_credentials = self.wTree.get_object("label_credentials")
233
234 image_credentials.set_from_icon_name("gtk-stop", Gtk.IconSize.MENU)
235 label_credentials.set_text(_("Invalid credentials"))
236 input_user.set_sensitive(True)
237 input_password.set_sensitive(True)
238
239 self.api.die()
240
241t = Window()190t = Window()
242reactor.registerGApplication(t)191reactor.registerGApplication(t)
243reactor.run()192reactor.run()
244193
=== modified file 'gm_notify_keyring.py'
--- gm_notify_keyring.py 2015-12-05 00:25:32 +0000
+++ gm_notify_keyring.py 2015-12-05 00:25:32 +0000
@@ -3,6 +3,9 @@
3__version__ = "$Revision: 14294 $"3__version__ = "$Revision: 14294 $"
44
5from gi.repository import GnomeKeyring, Gtk5from gi.repository import GnomeKeyring, Gtk
6from collections import namedtuple
7Credentials = namedtuple("Credentials", ["username", "password"])
8Credentials.__new__.__defaults__ = ("", "")
69
7def attributes(d):10def attributes(d):
8 '''Converts a dictionary to a GnomeKeyring.Attribute array'''11 '''Converts a dictionary to a GnomeKeyring.Attribute array'''
@@ -28,26 +31,66 @@
28 self._protocol = protocol31 self._protocol = protocol
29 result, self._keyring = GnomeKeyring.get_default_keyring_sync()32 result, self._keyring = GnomeKeyring.get_default_keyring_sync()
30 33
31 def has_credentials(self):34 def has_any_credentials(self):
32 attrs = attributes({"server": self._server, "protocol": self._protocol})35 attrs = attributes({"server": self._server, "protocol": self._protocol})
33 result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)36 result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)
34 if result in (GnomeKeyring.Result.NO_MATCH, GnomeKeyring.Result.DENIED):37 if result in (GnomeKeyring.Result.NO_MATCH, GnomeKeyring.Result.DENIED):
35 return False38 return False
36 return len(items) > 039 return len(items) > 0
3740
38 def get_credentials(self):41 def get_all_credentials(self):
39 attrs = attributes({"server": self._server, "protocol": self._protocol})42 attrs = attributes({"server": self._server, "protocol": self._protocol})
43 result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)
44 if len(items) == 0:
45 raise KeyringException("Credentials not found")
46 credentials_list = []
47 for item in items:
48 d = dict_from_attributes(item.attributes)
49 credentials_list.append(Credentials(d["user"], item.secret))
50 return credentials_list
51
52 def has_any_users(self):
53 return self.has_any_credentials()
54
55 def get_all_users(self):
56 attrs = attributes({"server": self._server, "protocol": self._protocol})
57 result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)
58 if len(items) == 0:
59 raise KeyringException("Credentials not found")
60 users_list = []
61 for item in items:
62 d = dict_from_attributes(item.attributes)
63 users_list.append(d["user"])
64 return users_list
65
66 def get_credentials(self, user):
67 attrs = attributes({
68 "user" : user,
69 "server": self._server,
70 "protocol": self._protocol
71 })
40 result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)72 result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)
41 if len(items) == 0:73 if len(items) == 0:
42 raise KeyringException("Credentials not found")74 raise KeyringException("Credentials not found")
43 d = dict_from_attributes(items[0].attributes)75 d = dict_from_attributes(items[0].attributes)
44 return (d["user"], items[0].secret)76 return Credentials(d["user"], items[0].secret)
45 77
46 def delete_credentials(self):78 def delete_all_credentials(self):
47 attrs = attributes({"server": self._server, "protocol": self._protocol})79 attrs = attributes({"server": self._server, "protocol": self._protocol})
48 result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)80 result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)
49 for item in items:81 for item in items:
50 GnomeKeyring.item_delete_sync(self._keyring, item.item_id)82 GnomeKeyring.item_delete_sync(self._keyring, item.item_id)
83
84 def delete_credentials(self, user):
85 attrs = attributes({
86 "user": user,
87 "server": self._server,
88 "protocol": self._protocol
89 })
90 result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)
91 for item in items:
92 GnomeKeyring.item_delete_sync(self._keyring, item.item_id)
93
51 94
52 def set_credentials(self, user, pw):95 def set_credentials(self, user, pw):
53 attrs = attributes({96 attrs = attributes({
5497
=== modified file 'gtalk.py'
--- gtalk.py 2015-12-05 00:25:32 +0000
+++ gtalk.py 2015-12-05 00:25:32 +0000
@@ -1,10 +1,11 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
33
4# gtalk.py v0.10.34# gtalk.py v1.0
5# Google Talk mail notification client library5# Google Talk mail notification client library
6#6#
7# Copyright (c) 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com>7# Copyright (c) 2009-2010, Alexander Hungenberg <alexander.hungenberg@gmail.com>
8# Copyright (c) 2015, Mateusz Balbus <balbusm@gmail.com>
8# 9#
9# This program is free software: you can redistribute it and/or modify10# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by11# it under the terms of the GNU General Public License as published by
@@ -21,38 +22,86 @@
21#22#
22from __future__ import print_function23from __future__ import print_function
2324
24from threading import Event
25
26from twisted.words.protocols.jabber import xmlstream, client, jid25from twisted.words.protocols.jabber import xmlstream, client, jid
27from twisted.words.xish import domish26from twisted.words.xish import domish
28from twisted.internet import reactor, task, error27from twisted.internet import reactor, task, error, ssl
2928from twisted.internet.error import TimeoutError, ConnectionRefusedError
30_DEBUG = False29
30from datetime import datetime
31
32GTALK_HOST = "talk.google.com"
33
34_DEBUG = True
31COLOR_GREEN = "\033[92m"35COLOR_GREEN = "\033[92m"
32COLOR_END = "\033[0m"36COLOR_END = "\033[0m"
33def DEBUG(msg):37def DEBUG(msg):
34 if _DEBUG: print(COLOR_GREEN + str(msg) + COLOR_END)38 if _DEBUG:
39 curent_time = datetime.now()
40
41 print(COLOR_GREEN + str(curent_time) + " " +str(msg) + COLOR_END)
3542
36class GTalkClientFactory(xmlstream.XmlStreamFactory):43class GTalkClientFactory(xmlstream.XmlStreamFactory):
37 def __init__(self, jid, password):44
45 def __init__(self, jid, password, settings_provider):
38 a = client.XMPPAuthenticator(jid, password)46 a = client.XMPPAuthenticator(jid, password)
39 xmlstream.XmlStreamFactory.__init__(self, a)47 xmlstream.XmlStreamFactory.__init__(self, a)
48 self.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, self.clientConnected)
40 49
50 self.jid = jid
41 self.reconnect = True51 self.reconnect = True
52 self.connection_failed = False
53 self.connection_lost = False
54 self.cb_connection_error = None
55 self.settings_provider = settings_provider
56 self.port = self.settings_provider.retrieve_preferred_port()
42 57
43 def clientConnectionLost(self, connector, reason):58 def clientConnectionLost(self, connector, reason):
44 if self.reconnect: xmlstream.XmlStreamFactory.clientConnectionLost(self, connector, reason)59 DEBUG("clientConnectionLost %s" % str(reason))
60 self.connection_lost = True
61 if not self.reconnect:
62 return
63
64 DEBUG("clientConnectionLost: Reconnecting with the same settings (port %d)" % connector.port)
65 if self.cb_connection_error : self.cb_connection_error(self.jid.full(), reason)
66 # reconnect on the same port as it used to work
67 xmlstream.XmlStreamFactory.clientConnectionLost(self, connector, reason)
68
69 def clientConnectionFailed(self, connector, reason):
70 DEBUG("clientConnectionFailed %s on port: %d reconnecting: %s" % (str(reason), connector.port, str(self.reconnect)))
71 self.connection_failed = True
72 if not self.reconnect:
73 return
74
75 DEBUG("clientConnectionFailed: Connection failed");
76 if self.cb_connection_error : self.cb_connection_error(self.jid.full(), reason)
77
78 def clientConnected(self, xmlstream):
79 DEBUG("clientConnected")
80 self.connection_failed = False
81 self.connection_lost = False
82
83 def getCurrentPort(self):
84 return self.port
85
86 def hasConnectionFailedOrLost(self):
87 return self.connection_failed or self.connection_lost
88
89 def setOnConnectionErrorCB(self, cb_connection_error):
90 self.cb_connection_error = cb_connection_error
91
4592
46class MailChecker():93class MailChecker():
47 def __init__(self, jid, password, labels=[], cb_new=None, cb_count=None):94 def __init__(self, jid, password, settings_provider, labels=[], cb_new=None, cb_count=None):
48 self.host = "talk.google.com"95 self.host = GTALK_HOST
49 self.port = 522296 self.settings_provider = settings_provider
50 self.jid = jid97 self.jid = jid
51 self.password = password98 self.password = password
52 self.cb_new = cb_new99 self.cb_new = cb_new
53 self.cb_count = cb_count100 self.cb_count = cb_count
54 self.cb_auth_successful = None101 self.cb_auth_succeeded = None
55 self.cb_auth_failed = None102 self.cb_auth_failed = None
103 self.cb_connection_error = None
104 self.cb_connected = None
56 105
57 self.last_tids = {}106 self.last_tids = {}
58 self.labels = labels107 self.labels = labels
@@ -66,14 +115,33 @@
66 self.ready_for_query_state = False115 self.ready_for_query_state = False
67 self.timeout_call_id = None116 self.timeout_call_id = None
68 self.disconnected = True117 self.disconnected = True
118 self.running = False
119
120 def setOnConnectionErrorCB(self, cb_connection_error):
121 self.cb_connection_error = cb_connection_error
122
123 def setOnConnectedCB(self, cb_connected):
124 self.cb_connected = cb_connected
125
126 def setOnAuthFailed(self, cb_auth_failed):
127 self.cb_auth_failed = cb_auth_failed
128
129 def setOnAuthSucceeded(self, cb_auth_succeeded):
130 self.cb_auth_succeeded = cb_auth_succeeded
131
132 def is_running(self):
133 return self.running
69 134
70 def die(self):135 def die(self):
136 DEBUG("Dying...")
71 self.factory.reconnect = False137 self.factory.reconnect = False
72 self.query_task.stop()138 self.query_task.stop()
73 self.connector.disconnect()139 self.connector.disconnect()
140 self.running = False
74 141
75 def connect(self):142 def connect(self):
76 self.factory = GTalkClientFactory(self.jid, self.password)143 self.factory = GTalkClientFactory(self.jid, self.password, self.settings_provider)
144 self.factory.setOnConnectionErrorCB(self.cb_connection_error)
77 self.factory.addBootstrap(xmlstream.STREAM_END_EVENT, self.disconnectCB)145 self.factory.addBootstrap(xmlstream.STREAM_END_EVENT, self.disconnectCB)
78 self.factory.addBootstrap(xmlstream.STREAM_ERROR_EVENT, self.disconnectCB)146 self.factory.addBootstrap(xmlstream.STREAM_ERROR_EVENT, self.disconnectCB)
79 self.factory.addBootstrap(xmlstream.INIT_FAILED_EVENT, self.init_failedCB)147 self.factory.addBootstrap(xmlstream.INIT_FAILED_EVENT, self.init_failedCB)
@@ -84,10 +152,12 @@
84 152
85 self.query_task = task.LoopingCall(self.queryInbox)153 self.query_task = task.LoopingCall(self.queryInbox)
86 self.query_task.start(60)154 self.query_task.start(60)
155 self.running = True
87 156
88 self.connector = reactor.connectTCP(self.host, self.port, self.factory)157 self.connector = reactor.connectSSL(self.host, self.factory.getCurrentPort(), self.factory, ssl.ClientContextFactory())
89 158
90 def reply_timeout(self):159 def reply_timeout(self):
160 DEBUG("reply_timeout")
91 self.connector.disconnect() # Our reconnecting factory will try the reconnecting161 self.connector.disconnect() # Our reconnecting factory will try the reconnecting
92162
93 def send_callback_handler(self, data, callback=None, **kargs):163 def send_callback_handler(self, data, callback=None, **kargs):
@@ -115,16 +185,19 @@
115 self.xmlstream.send(data)185 self.xmlstream.send(data)
116 186
117 def disconnectCB(self, xmlstream):187 def disconnectCB(self, xmlstream):
118 self.ready_for_query_state = False
119 self.disconnected = True
120 DEBUG("disconnected")188 DEBUG("disconnected")
121 189 self.ready_for_query_state = False
190 self.factory.reconnect = False
191 self.disconnected = True
192
122 def init_failedCB(self, xmlstream):193 def init_failedCB(self, xmlstream):
123 if self.cb_auth_failed: self.cb_auth_failed()194 DEBUG("init_failedCB")
195 if self.cb_auth_failed: self.cb_auth_failed(self.jid.full(), xmlstream.value)
124 self.disconnectCB(xmlstream)196 self.disconnectCB(xmlstream)
125 197
126 def authenticationCB(self, xmlstream):198 def authenticationCB(self, xmlstream):
127 if self.cb_auth_successful: self.cb_auth_successful()199 DEBUG("authenticationCB")
200 if self.cb_auth_succeeded: self.cb_auth_succeeded(self.jid.full())
128 self.factory.resetDelay()201 self.factory.resetDelay()
129 202
130 # We set the usersetting mail-notification203 # We set the usersetting mail-notification
@@ -135,12 +208,19 @@
135 self.send(iq, "/iq", self.usersettingIQ)208 self.send(iq, "/iq", self.usersettingIQ)
136 209
137 def usersettingIQ(self, iq):210 def usersettingIQ(self, iq):
211 DEBUG("usersettingIQ")
138 self.ready_for_query_state = True212 self.ready_for_query_state = True
139 self.queryInbox()213 self.queryInbox()
140 214
141 def queryInbox(self):215 def queryInbox(self):
142 if not self.ready_for_query_state: return216 DEBUG("queryInbox")
217 if not(self.ready_for_query_state or self.factory.hasConnectionFailedOrLost()):
218 DEBUG("queryInbox: ready for query: %s connection_failed_or_lost: %s" % (str(self.ready_for_query_state), str(self.factory.hasConnectionFailedOrLost())))
219 DEBUG("queryInbox: skipping query request")
220 return
143 if self.disconnected:221 if self.disconnected:
222 DEBUG("queryInbox: disconnected")
223 self.factory.reconnect = True
144 self.connector.connect()224 self.connector.connect()
145 return225 return
146 self.ready_for_query_state = False226 self.ready_for_query_state = False
@@ -149,21 +229,23 @@
149 229
150 iq = domish.Element((None, "iq"), attribs={"type": "get", "id": "mail-request-1"})230 iq = domish.Element((None, "iq"), attribs={"type": "get", "id": "mail-request-1"})
151 query = iq.addElement(("google:mail:notify", "query"))231 query = iq.addElement(("google:mail:notify", "query"))
232 DEBUG("queryInbox: requesting for label")
152 self.send(iq, "/iq", self.gotLabel)233 self.send(iq, "/iq", self.gotLabel)
153 234
154 def queryLabel(self):235 def queryLabel(self):
155 try:236 try:
156 label = self.labels_iter.next()237 label = self.labels_iter.next()
157 238 DEBUG("queryLabel " + label)
158 iq = domish.Element((None, "iq"), attribs={"type": "get", "id": "mail-request-1"})239 iq = domish.Element((None, "iq"), attribs={"type": "get", "id": "mail-request-1"})
159 query = iq.addElement(("google:mail:notify", "query"))240 query = iq.addElement(("google:mail:notify", "query"))
160 query.attributes['q'] = "label:%s AND is:unread" % label241 query.attributes['q'] = "label:%s AND is:unread" % label
161 self.send(iq, "/iq", self.gotLabel, label=label)242 self.send(iq, "/iq", self.gotLabel, label=label)
162 except StopIteration:243 except StopIteration:
244 DEBUG("queryLabel: end of iteration")
163 self.labels_iter = iter(self.labels)245 self.labels_iter = iter(self.labels)
164 self.xmlstream.addObserver("/iq", self.gotNewMail)246 self.xmlstream.addObserver("/iq", self.gotNewMail)
165 if self.cb_count: self.cb_count(self.count)247 if self.cb_count: self.cb_count(self.jid.full(), self.count)
166 if self.mails and self.cb_new: self.cb_new(self.mails)248 if self.mails and self.cb_new: self.cb_new(self.jid.full(), self.mails)
167 self.mails = []249 self.mails = []
168 self.ready_for_query_state = True250 self.ready_for_query_state = True
169 251
@@ -242,21 +324,25 @@
242 mail['snippet'] = unicode(child)324 mail['snippet'] = unicode(child)
243 mails.append(mail)325 mails.append(mail)
244 326
245 self.cb_new(mails)327 self.cb_new(self.jid.full(), mails)
246 328
247 self.ready_for_query_state = True329 self.ready_for_query_state = True
248 if iq: self.queryInbox()330 if iq: self.queryInbox()
249 331
250 def rawDataIn(self, buf):332 def rawDataIn(self, buf):
251 print(u"< %s" % unicode(buf, "utf-8"))333 print("> %s" % buf)
252 334
253 def rawDataOut(self, buf):335 def rawDataOut(self, buf):
254 print(u"> %s" % unicode(buf, "utf-8"))336 print("< %s" % buf)
255 337
256 def connectedCB(self, xmlstream):338 def connectedCB(self, xmlstream):
257 self.xmlstream = xmlstream339 self.xmlstream = xmlstream
258 self.disconnected = False340 self.disconnected = False
341 if self.cb_connected: self.cb_connected(self.jid.full())
259 342
260 if _DEBUG:343 if _DEBUG:
261 xmlstream.rawDataInFn = self.rawDataIn344 xmlstream.rawDataInFn = self.rawDataIn
262 xmlstream.rawDataOutFn = self.rawDataOut345 xmlstream.rawDataOutFn = self.rawDataOut
346
347 def getCurrentPort(self):
348 return self.factory.getCurrentPort()
263349
=== modified file 'setup.py'
--- setup.py 2015-12-05 00:25:32 +0000
+++ setup.py 2015-12-05 00:25:32 +0000
@@ -3,16 +3,17 @@
3from distutils.core import setup3from distutils.core import setup
44
5setup( name='gm-notify',5setup( name='gm-notify',
6 version='0.10.3',6 version='1.0.0',
7 description='Highly Ubuntu integrated GMail Notifier',7 description='Highly Ubuntu integrated GMail Notifier',
8 author='Alexander Hungenberg',8 author='Mateusz Balbus',
9 author_email='alexander.hungenberg@gmail.com',9 author_email='balbusmg@gmail.com',
10 py_modules=['gtalk', 'gm_notify_keyring'],10 py_modules=['gtalk', 'gm_notify_keyring', 'account_settings_provider', 'account_config'],
11 scripts=['gm-notify', 'gm-notify-config'],11 scripts=['gm-notify', 'gm-notify-config'],
12 data_files=[('/usr/share/applications', ['data/gm-notify-config.desktop']),12 data_files=[('/usr/share/applications', ['data/gm-notify-config.desktop']),
13 ('/usr/share/applications', ['data/gm-notify.desktop']),13 ('/usr/share/applications', ['data/gm-notify.desktop']),
14 ('/usr/share/gm-notify', ['data/checking.gif']),14 ('/usr/share/gm-notify', ['data/checking.gif']),
15 ('/usr/share/gm-notify', ['gm-config.ui']),15 ('/usr/share/gm-notify', ['gm-config.ui']),
16 ('/usr/share/gm-notify', ['gm-list.ui']),
16 ('/usr/share/glib-2.0/schemas', ['data/net.launchpad.gm-notify.gschema.xml']),17 ('/usr/share/glib-2.0/schemas', ['data/net.launchpad.gm-notify.gschema.xml']),
17 ('/usr/share/locale/da/LC_MESSAGES', ['po/da/gm-notify.mo']),18 ('/usr/share/locale/da/LC_MESSAGES', ['po/da/gm-notify.mo']),
18 ('/usr/share/locale/bg/LC_MESSAGES', ['po/bg/gm-notify.mo']),19 ('/usr/share/locale/bg/LC_MESSAGES', ['po/bg/gm-notify.mo']),

Subscribers

People subscribed via source and target branches

to status/vote changes: