Commit 71310f7b81f22d1d03877f5f0ad644759090974a

Plugins Updated, and some extra modifications

Commit diff

gmail-sentinel.conf.sample

 
2020
2121[plugins]
2222path = /home/rodrigo/.gsentinel.d/plugins
23list = ""
23list = dummy
24
25[plugin-DummyPlugin]
26key = value
2427
2528[plugin-NotifyPlugin]
2629iconpath = /usr/local/pshare/gmail-sentinel/media/
toggle raw diff

gmail-sentinel.py

 
2929
3030import pygtk
3131pygtk.require('2.0')
32import time
3332import gtk, gobject
3433import locale, gettext
3534import sys, os
3636from os.path import dirname, pardir
3737from gettext import gettext as _
3838from ConfigParser import ConfigParser
39from gmailatom import GmailAtom
39from gsentinel.gmailatom import GmailAtom
4040from gsentinel.plugin import PluginManager
4141
4242APP_NAME = "Gmail-Sentinel"
4343APP_VERSION = "0.2_beta2"
44APP_COPYRIGHT = "(C)2008 Rodrigo Lazo.\n" + _("GoogleMail and Gmail are trademarks of Google, Inc.")
44APP_COPYRIGHT = "(C)2008 Rodrigo Lazo.\n" + \
45 _("GoogleMail and Gmail are trademarks of Google, Inc.")
4546APP_DESCRIPTION = _('GoogleMail Sentinel')
4647APP_WEBSITE = 'http://code.google.com/p/gmail-sentinel/'
4748
102102 dialog.set_comments(APP_DESCRIPTION)
103103 dialog.set_website(APP_WEBSITE)
104104 dialog.set_logo(gtk.gdk.pixbuf_new_from_file(self.gmail_icon))
105 dialog.set_translator_credits(_("translator-credits"))
105 dialog.set_translator_credits(_("translator-credits"))
106106 dialog.run()
107107 dialog.destroy()
108108
119119 """Initializer. Creates the status icon and opens the
120120 connection."""
121121 self.config = ConfigParser()
122 self.plugin_manager = PluginManager()
122 self.read_msgs = list()
123 self.plugin_manager = PluginManager()
123124 self.connection = None
124125 self.unread_msg_count = 0
125126
126 logging.info("[Main] Loading configuration file")
127 logging.info("[Main] Loading configuration file")
127128 if not self.load_configuration():
128129 logging.critical("[Main] Configuration file does not exist.")
129130 sys.exit(1)
130131
131 # plugin system initialization
132 logging.info("[Main] Starting PluginManager")
133 self.plugin_manager.initialize(self.config.get('plugins', 'path'),
134 self.config.get('plugins', 'list'))
135 logging.info("[Main] Creating StatusIcon")
132 logging.info("[Main] Creating StatusIcon")
136133 self.status_icon = GmailStatusIcon(self.config.get('media', 'path'))
137
134
135 logging.info("[Main] Starting PluginManager")
136 self.plugin_manager.initialize(self.config)
137
138138 # Make sure the status icon shows up
139139 while gtk.events_pending():
140140 gtk.main_iteration(True)
141 logging.info("[Main] First run. Checking for new mail")
141 logging.info("[Main] First run. Checking for new mail")
142142 if self.connect() :
143143 self.check_inbox()
144144 # The multiplier is to simplify the configuration
145145 # file. Converts the miliseconds into seconds
146 logging.info("[Main] Registering interval function")
146 logging.info("[Main] Registering interval function")
147147 interval = int(self.config.get('gmail', 'checkinterval'))*1000
148148 self.main_timer = gobject.timeout_add(interval, self.check_inbox)
149 logging.info("[Main] Sleeping interval %d miliseconds. Sleeping now.", interval)
149 logging.info("[Main] Sleeping interval %d miliseconds. Sleeping now.",
150 interval)
150151
151152 def load_configuration (self):
152153 """Loads the configuration. If file does not exist returns
170170 self.connection = GmailAtom(self.config.get('gmail','username'),
171171 self.config.get('gmail','password'))
172172 except:
173 logging.error("[Main] Login failed, will retry later")
173 logging.exception("[Main] Login failed, will retry later")
174174 self.status_icon.set_tooltip(_("Connection failed"))
175175 return False
176 if self.connection.refreshInfo():
177 logging.info("[Main] Connection open")
178 self.status_icon.set_tooltip(_("Connected"))
179 return True
180 else:
181 logging.error("[Main] Connection failed")
182 self.status_icon.set_tooltip(_("Not connected"))
183 return False
176 if self.connection.refreshInfo():
177 logging.info("[Main] Connection open")
178 self.status_icon.set_tooltip(_("Connected"))
179 return True
180 else:
181 logging.error("[Main] Connection failed")
182 self.status_icon.set_tooltip(_("Not connected"))
183 return False
184184
185185 def check_inbox(self):
186186 """Checks for new mail."""
187187 if self.connection is None or self.connect() is False:
188188 # Try again latter
189189 return True
190 logging.info("[Main] Checking for new mail.")
190 logging.info("[Main] Checking for new mail.")
191191 while gtk.events_pending():
192192 gtk.main_iteration( True )
193193
194194 if not self.has_new_mail():
195 logging.info("[Main] No new messages")
196 self.plugin_manager.emit_signal('no_new_messages')
195 logging.info("[Main] No new messages")
196 self.plugin_manager.emit_signal('no_new_messages')
197197 # Check again later
198 self.unread_msg_count > 0 or \
198 len(self.read_msgs) == 0 or \
199199 self.status_icon.set_from_file(self.status_icon.inactive_icon)
200200 return True
201201 self.status_icon.set_from_file(self.status_icon.active_icon)
202202 count = self.connection.getUnreadMsgCount()
203 logging.info("[Main] %s New messages found", count)
204 last_message = self.get_last_message()
205 self.plugin_manager.emit_signal('new_messages', messages=self.connection.getMsgList())
203 logging.info("[Main] %s New messages found", count)
204 self.plugin_manager.emit_signal('new_messages',
205 messages=self.connection.getMsgList())
206206 return True
207207
208208 def has_new_mail (self):
209209 """Checks if new mail has arrived."""
210 # TODO: new mail != read mail != no mail
210211 try:
211 # Hackerish?
212212 self.connection.refreshInfo()
213 current_msg_count = self.connection.getUnreadMsgCount()
214 new_mail = current_msg_count > 0 and \
215 current_msg_count != self.unread_msg_count
216 self.unread_msg_count = current_msg_count
217 return new_mail
218 except:
219 logging.error("[Main] getUnreadMsgCount() failed, will try again later")
220 self.unread_msg_count = 0
213 msgs = self.connection.getMsgList()
214 # are all the messages already seen?
215 set1 = set(x["id"] for x in self.read_msgs)
216 same_messages = set1.issuperset(x["id"] for x in msgs)
217 if not same_messages:
218 self.read_msgs = msgs
219 return not same_messages
220 except Exception, ex:
221 logging.exception("[Main] getUnreadMsgCount() failed, will try again later")
221222 return False
222223
223 def get_last_message (self):
224 """ Returns the sender, subject and snippet of the last
225 message retrieved."""
226 alist = self.connection.getMsgList()
227 return {'sender' : alist[0]['author_name'],
228 'subject' : alist[0]['title'],
229 'snippet' : alist[0]['summary'][:40] + "..."}
230
231224 def main (self):
232225 """ Mailoop"""
233226 gtk.main()
234227
235228if __name__ == "__main__":
236229 # Path definition
237 SHARE_PATH = os.path.abspath(os.path.join(dirname(__file__), pardir, pardir, 'share'))
230 SHARE_PATH = os.path.abspath(os.path.join(dirname(__file__),
231 pardir, pardir, 'share'))
238232 LOCALE_PATH = os.path.join(SHARE_PATH, 'locale')
239233
240234 # L10n initialization
238238
239239 # Loggging configuration
240240 logging.basicConfig(level=logging.INFO,
241 format='%(asctime)s %(levelname)s %(message)s')
241 format='%(asctime)s %(levelname)s %(message)s')
242242
243243 # app itself
244244 notifier = Gnotify()
toggle raw diff

gmailatom.py

 
0#!/usr/bin/python
1# -*- coding: utf-8 -*-
2
3# gmailatom 0.0.2
4#
5# by Juan Grande <juan.grande@gmail.com>
6# modified by Rodrigo Lazo <rlazo.paz@gmail.com>
7
8"""
9Gmailatom.
10
11Helps connection to Gmail using the atom interface.
12
13HOW TO USE:
141) Create an instance of 'GmailAtom' class. The two arguments
15 its constructor take are the username (including '@gmail.com')
16 and the password.
172) To retrieve the account status call 'refreshInfo()'.
183) To get the unread messages count call 'getUnreadMsgCount()'.
19 You MUST call 'refreshInfo()' at least one time before using
20 this method or it will return zero.
214) To get specific information about an unread email you must
22 call the corresponding getter method passing to it the number
23 of the message. The number zero represents the newest one.
24 You MUST call 'refreshInfo()' at least one time before calling getMsgList
25
26 The getMsgList returns a list of Messages (dictionary-like) with
27 the following data:
28 id
29 title
30 summary
31 author_name
32 author_email
33 link
34"""
35
36from xml.sax.handler import ContentHandler
37from xml import sax
38import urllib2
39
40# Because Message acts like a struct we only need to inheret from dict
41class Message (dict):
42 """Message struct"""
43 def __init__ (self):
44 self.update( {'id' : "",
45 'title' : "",
46 'summary' : "",
47 'author_name' : "",
48 'author_email' : "",
49 'link' : "" } )
50
51 def __cmp__ (self, msg):
52 """Messages are compared by id"""
53 return cmp(self['id'], msg['id'])
54
55
56# Sax XML Handler
57class MailHandler(ContentHandler):
58 """Sax XML Handler for gmail atom interface"""
59
60 # Tags
61 TAG_FEED = "feed"
62 TAG_FULLCOUNT = "fullcount"
63 TAG_ENTRY = "entry"
64 TAG_TITLE = "title"
65 TAG_SUMMARY = "summary"
66 TAG_LINK = "link"
67 TAG_ID = "id"
68 TAG_AUTHOR = "author"
69 TAG_NAME = "name"
70 TAG_EMAIL = "email"
71
72 # Path the information
73 PATH_FULLCOUNT = [ TAG_FEED, TAG_FULLCOUNT ]
74 PATH_ENTRY = [ TAG_FEED, TAG_ENTRY ]
75 PATH_LINK = PATH_ENTRY + [ TAG_LINK ]
76 PATH_ID = PATH_ENTRY + [ TAG_ID ]
77 PATH_TITLE = PATH_ENTRY + [ TAG_TITLE ]
78 PATH_SUMMARY = PATH_ENTRY + [ TAG_SUMMARY ]
79 PATH_AUTHOR = PATH_ENTRY + [ TAG_AUTHOR ]
80 PATH_AUTHOR_NAME = PATH_AUTHOR + [ TAG_NAME ]
81 PATH_AUTHOR_EMAIL = PATH_AUTHOR + [ TAG_EMAIL ]
82
83 def __init__(self):
84 ContentHandler.__init__(self)
85 self.entries = list()
86 self.actual = list()
87 self.mail_count = "0"
88 self.current_msg = None
89
90 def startDocument (self):
91 """Document initialization"""
92 del self.entries[:]
93 del self.actual[:]
94 self.mail_count = "0"
95 self.current_msg = None
96
97 def startElement( self, name, attrs):
98 # update actual path
99 self.actual.append(name)
100
101 # Message link
102 if self.actual == self.PATH_LINK:
103 self.current_msg['link'] = attrs['href']
104
105 # add a new email to the list
106 if name == "entry":
107 self.current_msg = Message()
108
109 def endElement( self, name):
110 # update actual path
111 if name == "entry":
112 self.entries.append(self.current_msg.copy())
113 self.current_msg.clear()
114 self.actual.pop()
115
116 def characters( self, content):
117 # New messages count
118 if (self.actual == self.PATH_FULLCOUNT):
119 self.mail_count = content
120
121 # Message title
122 if (self.actual == self.PATH_TITLE):
123 self.current_msg['title'] += content
124
125 # Message summary
126 if (self.actual == self.PATH_SUMMARY):
127 self.current_msg['summary'] += content
128
129 # Message id
130 if self.actual == self.PATH_ID:
131 self.current_msg['id'] += content
132
133 # Message author name
134 if (self.actual == self.PATH_AUTHOR_NAME):
135 self.current_msg['author_name'] += content
136
137 # Message author email
138 if (self.actual == self.PATH_AUTHOR_EMAIL):
139 self.current_msg['author_email'] += content
140
141 def getUnreadMsgCount(self):
142 return int(self.mail_count)
143
144# The mail class
145class GmailAtom:
146 """Gmail's atom interface proxy"""
147
148 realm = "New mail feed"
149 host = "https://mail.google.com"
150 url = host + "/mail/feed/atom"
151
152 def __init__(self, user, pswd):
153 self.mh = MailHandler()
154 # initialize authorization handler
155 auth_handler = urllib2.HTTPBasicAuthHandler()
156 auth_handler.add_password( self.realm, self.host, user, pswd)
157 opener = urllib2.build_opener(auth_handler)
158 urllib2.install_opener(opener)
159
160 def sendRequest(self):
161 """Opens a conection with the remote server"""
162 del self.mh.entries[:]
163 return urllib2.urlopen(self.url)
164
165 def refreshInfo(self):
166 """Requests information from the remote server and parses the
167 information retrieved"""
168 try:
169 sax.parseString( self.sendRequest().read(), self.mh)
170 return True
171 except urllib2.URLError:
172 return False
173
174 def getMsgList (self):
175 """Returns a list containing the new messages or empty. It
176 will replace all the getMsg* functions in the next
177 release. Every entry on the list acts like a dictionary:
178
179 old api
180 getMsgSummary(1) #Get the summary of the first message
181
182 new api
183 l = getMsgList()
184 l[0]['summary']"""
185 return self.mh.entries
186
187 def getUnreadMsgCount(self):
188 return self.mh.getUnreadMsgCount()
toggle raw diff

gsentinel/__init__.py

 
1313# You should have received a copy of the GNU General Public License
1414# along with this program; if not, write to the Free Software
1515# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16
17# Because Message acts like a struct we only need to inheret from dict
18class Message (dict):
19 """Message struct"""
20 def __init__ (self):
21 self.update( {'id' : "",
22 'title' : "",
23 'summary' : "",
24 'author_name' : "",
25 'author_email' : "",
26 'link' : ""} )
27
28 def __cmp__ (self, msg):
29 """Messages are compared by id"""
30 return cmp(self['id'], msg['id'])
31
32 def __hash__ (self):
33 """Hash method"""
34 return hash(self['id'])
toggle raw diff

gsentinel/gmailatom.py

 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# gmailatom 0.0.2
5#
6# by Juan Grande <juan.grande@gmail.com>
7# modified by Rodrigo Lazo <rlazo.paz@gmail.com>
8
9"""
10Gmailatom.
11
12Helps connection to Gmail using the atom interface.
13
14HOW TO USE:
151) Create an instance of 'GmailAtom' class. The two arguments
16 its constructor take are the username (including '@gmail.com')
17 and the password.
182) To retrieve the account status call 'refreshInfo()'.
193) To get the unread messages count call 'getUnreadMsgCount()'.
20 You MUST call 'refreshInfo()' at least one time before using
21 this method or it will return zero.
224) To get specific information about an unread email you must
23 call the corresponding getter method passing to it the number
24 of the message. The number zero represents the newest one.
25 You MUST call 'refreshInfo()' at least one time before calling getMsgList
26
27 The getMsgList returns a list of Messages (dictionary-like) with
28 the following data:
29 id
30 title
31 summary
32 author_name
33 author_email
34 link
35"""
36
37from gsentinel import Message
38from xml.sax.handler import ContentHandler
39from datetime import datetime
40from xml import sax
41import urllib2
42
43# Sax XML Handler
44class MailHandler(ContentHandler):
45 """Sax XML Handler for gmail atom interface"""
46
47 # Tags
48 TAG_FEED = "feed"
49 TAG_FULLCOUNT = "fullcount"
50 TAG_ENTRY = "entry"
51 TAG_TITLE = "title"
52 TAG_SUMMARY = "summary"
53 TAG_LINK = "link"
54 TAG_ID = "id"
55 TAG_AUTHOR = "author"
56 TAG_NAME = "name"
57 TAG_EMAIL = "email"
58
59 # Path the information
60 PATH_FULLCOUNT = [ TAG_FEED, TAG_FULLCOUNT ]
61 PATH_ENTRY = [ TAG_FEED, TAG_ENTRY ]
62 PATH_LINK = PATH_ENTRY + [ TAG_LINK ]
63 PATH_ID = PATH_ENTRY + [ TAG_ID ]
64 PATH_TITLE = PATH_ENTRY + [ TAG_TITLE ]
65 PATH_SUMMARY = PATH_ENTRY + [ TAG_SUMMARY ]
66 PATH_AUTHOR = PATH_ENTRY + [ TAG_AUTHOR ]
67 PATH_AUTHOR_NAME = PATH_AUTHOR + [ TAG_NAME ]
68 PATH_AUTHOR_EMAIL = PATH_AUTHOR + [ TAG_EMAIL ]
69
70 def __init__(self):
71 ContentHandler.__init__(self)
72 self.entries = list()
73 self.actual = list()
74 self.mail_count = "0"
75 self.current_msg = None
76
77 def startDocument (self):
78 """Document initialization"""
79 del self.entries[:]
80 del self.actual[:]
81 self.mail_count = "0"
82 self.current_msg = None
83
84 def startElement( self, name, attrs):
85 # update actual path
86 self.actual.append(name)
87
88 # Message link
89 if self.actual == self.PATH_LINK:
90 self.current_msg['link'] = attrs['href']
91
92 # add a new email to the list
93 if name == "entry":
94 self.current_msg = Message()
95
96 def endElement( self, name):
97 # update actual path
98 if name == "entry":
99 self.entries.append(self.current_msg.copy())
100 self.current_msg.clear()
101 self.actual.pop()
102
103 def characters( self, content):
104 # New messages count
105 if (self.actual == self.PATH_FULLCOUNT):
106 self.mail_count = content
107
108 # Message title
109 if (self.actual == self.PATH_TITLE):
110 self.current_msg['title'] += content
111
112 # Message summary
113 if (self.actual == self.PATH_SUMMARY):
114 self.current_msg['summary'] += content
115
116 # Message id
117 if self.actual == self.PATH_ID:
118 self.current_msg['id'] += content
119
120 # Message author name
121 if (self.actual == self.PATH_AUTHOR_NAME):
122 self.current_msg['author_name'] += content
123
124 # Message author email
125 if (self.actual == self.PATH_AUTHOR_EMAIL):
126 self.current_msg['author_email'] += content
127
128 def getUnreadMsgCount(self):
129 return int(self.mail_count)
130
131# The mail class
132class GmailAtom:
133 """Gmail's atom interface proxy"""
134
135 realm = "New mail feed"
136 host = "https://mail.google.com"
137 url = host + "/mail/feed/atom"
138
139 def __init__(self, user, pswd):
140 self.mh = MailHandler()
141 # initialize authorization handler
142 auth_handler = urllib2.HTTPBasicAuthHandler()
143 auth_handler.add_password( self.realm, self.host, user, pswd)
144 opener = urllib2.build_opener(auth_handler)
145 urllib2.install_opener(opener)
146
147 def sendRequest(self):
148 """Opens a conection with the remote server"""
149 del self.mh.entries[:]
150 return urllib2.urlopen(self.url)
151
152 def refreshInfo(self):
153 """Requests information from the remote server and parses the
154 information retrieved"""
155 try:
156 sax.parseString( self.sendRequest().read(), self.mh)
157 return True
158 except urllib2.URLError:
159 return False
160
161 def getMsgList (self):
162 """Returns a list containing the new messages or empty. It
163 will replace all the getMsg* functions in the next
164 release. Every entry on the list acts like a dictionary:
165
166 old api
167 getMsgSummary(1) #Get the summary of the first message
168
169 new api
170 l = getMsgList()
171 l[0]['summary']"""
172 return self.mh.entries
173
174 def getUnreadMsgCount(self):
175 return self.mh.getUnreadMsgCount()
toggle raw diff

gsentinel/plugin.py

 
2727import traceback
2828import logging
2929
30class Plugin (object):
30class PluginBase (object):
3131 """ Base class for plugins. """
3232
3333 NAME = ""
3535 VERSION = ""
3636 DESCRIPTION = ""
3737
38 def initialize (self):
38 def initialize (self, config):
3939 """ Routine for plugin initialization"""
4040 pass
4141
6565 """
6666 self.__plugins = {}
6767
68 def __load_plugins (self, plugins):
68 def __load_plugins (self, plugins, config):
6969 """ Imports and initialize the PLUGINS"""
7070 logging.info("[PluginManager] Importing plugins")
7171 for plugin in plugins:
7272 logging.info("[PluginManager] Importing plugin %s", plugin)
73 __import__(plugin, None, None, [''])
73 __import__(plugin, None, None, [])
7474 logging.info("[PluginManager] Loading plugins")
75 for subclass in Plugin.__subclasses__():
75 for subclass in PluginBase.__subclasses__():
7676 logging.info("[PluginManager] NAME: %s VERSION: %s AUTHORS: %s",
77 subclass.NAME, subclass.VERSION, ",".join(subclass.authors))
77 subclass.NAME, subclass.VERSION, ",".join(subclass.AUTHORS))
7878 obj = subclass()
79 cfg_section = "plugin-%s" % subclass.__name__
7980 try:
80 obj.initialize()
81 if config.has_section(cfg_section):
82 obj.initialize(config.items(cfg_section))
83 else:
84 obj.initialize(None)
8185 if self.__plugins.has_key(subclass):
8286 logging.error("[PluginManager] Plugin %s v.%s already loaded",