~jhonnyc/cgmail/jonathanc-branch

« back to all changes in this revision

Viewing changes to src/service/checker.py

  • Committer: Marco Ferragina
  • Date: 2007-05-06 15:51:12 UTC
  • Revision ID: marco.ferragina@gmail.com-20070506155112-874uk2m8blrknyuf
Restructured package source dir. Now is much more organized. Implemented right click menu on account window treeview

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import urllib2 
 
2
import time
 
3
import thread
 
4
 
 
5
from lib import feedparser
 
6
from lib.common import *
 
7
from lib.accountmanager import AccountManager
 
8
from notifier import Notifier
 
9
 
 
10
from lib.pop3 import *
 
11
from lib.imap import *
 
12
 
 
13
class BaseChecker:
 
14
        def __init__(self, account_id, set_status_cb):
 
15
                self.account_id = account_id
 
16
                self.set_status_cb = set_status_cb
 
17
 
 
18
                self.checking = False
 
19
                self.notifier = Notifier()
 
20
 
 
21
        def reset(self): 
 
22
                """
 
23
                After this method is invoked, the checer must consider
 
24
                all mails as not notified
 
25
                """
 
26
                raise NotImplementedError()
 
27
        
 
28
        def notify_error(self, error, text, f):
 
29
                self.notifier.notify(error, text, msec = 10000, 
 
30
                        buttons = False, error = True, force = f)
 
31
        
 
32
        def notify_msg(self, mailbox, count, mails):
 
33
                subject_i18n = _(u"<b>Subject:</b>")
 
34
                author_i18n = _(u"<b>From:</b>")
 
35
                message = u""
 
36
                total = 0
 
37
                mailboxname = _("<b>Box:</b> %s") % mailbox
 
38
                total += count
 
39
                for author, subject in mails:
 
40
                        try:
 
41
                                author = unicode(author, "utf-8")
 
42
                        except UnicodeDecodeError:
 
43
                                author = unicode(author, "latin-1", "replace")
 
44
                        except:
 
45
                                pass
 
46
                        try:
 
47
                                subject = unicode(subject, "utf-8")
 
48
                        except UnicodeDecodeError:
 
49
                                subject = unicode(subject, "latin-1", "replace")
 
50
                        except:
 
51
                                pass
 
52
                        try:
 
53
                                message += u"%s %s\n%s %s\n%s\n\n" % \
 
54
                                        (subject_i18n, subject,
 
55
                                        author_i18n, author, mailboxname)
 
56
                        except:
 
57
                                message += u"%s %s\n%s %s\n%s\n\n" % \
 
58
                                        (subject_i18n, _("Unknown"),
 
59
                                        author_i18n, _("Unknown"), mailboxname)
 
60
                if total > 1:
 
61
                        title = _("There are %s new mails") % total
 
62
                else:
 
63
                        title = _("There is a new mail")
 
64
 
 
65
                self.set_status_cb(self.account_id, count, title, message)
 
66
                
 
67
        def check(self): 
 
68
                """
 
69
                Must return a list of 3 elements. First element must
 
70
                be the mailbox name, second the message count into mailbox, 
 
71
                third element a list of tuple. Each
 
72
                tuple must contain the from and subject field of the email
 
73
                Example:
 
74
                        ["test@gmail.com", 2, [ ["Test Mailer <test@domain.com>", "This is the subject"], 
 
75
                                        ["Second <sec@test.com>", "Second Subject"] ]]
 
76
                """
 
77
                raise NotImplementedError()
 
78
        
 
79
        def update_info(self, account):
 
80
                """
 
81
                This method receive an account dic and update checker info
 
82
                using account values
 
83
                """
 
84
                raise NotImplementedError()
 
85
 
 
86
class GmailChecker(BaseChecker):
 
87
        def __init__(self, account_id, status_cb, user, password):
 
88
                self.username = user
 
89
                self.password = password
 
90
                self.notified = []
 
91
                BaseChecker.__init__(self, account_id, status_cb)
 
92
        
 
93
        def reset(self):
 
94
                self.notified = []
 
95
 
 
96
        def getfeed(self):
 
97
                ah = urllib2.HTTPBasicAuthHandler()
 
98
                ah.add_password('New mail feed', GMAIL_URL, \
 
99
                                        self.username, self.password)
 
100
                op = urllib2.build_opener(ah)
 
101
                urllib2.install_opener(op)
 
102
                res = urllib2.urlopen(GMAIL_ATOM_URL)
 
103
                return ''.join(res.readlines())
 
104
        
 
105
        def update_info(self, account):
 
106
                self.username = account["username"]
 
107
                self.password = account["password"]
 
108
        
 
109
        def check(self):
 
110
                """
 
111
                Check for new mails.
 
112
                """
 
113
                # prevent recalling
 
114
                if self.checking: return
 
115
                self.checking = True
 
116
 
 
117
                print "checking gmail account %s ..." % self.username
 
118
 
 
119
                try:
 
120
                        feed = self.getfeed()
 
121
                except urllib2.HTTPError, e:
 
122
                        if str(e) == "HTTP Error 401: Unauthorized":
 
123
                                msg = _("Invalid username or password! Please check your settings!")
 
124
                        else:
 
125
                                msg = str(e)
 
126
                        
 
127
                        error = _("Gmail Error on account %s") % self.username
 
128
                        self.notify_error(error, msg, True)
 
129
                        self.checking = False
 
130
                        return
 
131
                except Exception, e:
 
132
                        print "Warning:", e
 
133
                        self.notify_error(error, msg, False)
 
134
                        self.checking = False
 
135
                        return
 
136
 
 
137
                print "...done"
 
138
 
 
139
                try:
 
140
                        atom = feedparser.parse(feed)
 
141
                except Exception, detail:
 
142
                        print "Warning: Exception while parsing gmail feeds", detail
 
143
                        self.notify_error(_("Gmail Warning"), _("Exception while parsing gmail feeds", False))
 
144
                        self.checking = False
 
145
                        return
 
146
 
 
147
                count = len(atom.entries)
 
148
                
 
149
                mailbox = "%s@gmail.com" % self.username
 
150
                mails = []
 
151
 
 
152
                if count == 0:
 
153
                        self.checking = False
 
154
                        #return mailbox, 0, mails
 
155
                        return
 
156
 
 
157
                if count < MAX_NOTIFIED_MAILS:
 
158
                        loop = count
 
159
                else:
 
160
                        loop = MAX_NOTIFIED_MAILS
 
161
                
 
162
                mustnotify = False
 
163
 
 
164
                for i in xrange(loop):
 
165
                        link = atom.entries[i].link
 
166
                        tmp = link[link.find("message_id"):]
 
167
                        message_id =  tmp[:tmp.find("&")].split("=")[1]
 
168
 
 
169
                        if message_id not in self.notified:
 
170
                                mustnotify = True
 
171
                                self.notified.append(message_id)
 
172
                                title  = atom.entries[i].title
 
173
                                author = atom.entries[i].author
 
174
                                mails.append([author, title])
 
175
                
 
176
                if not mustnotify:
 
177
                        self.notify_msg(mailbox, count, [])
 
178
                else:
 
179
                        self.notify_msg(mailbox, count, mails)
 
180
 
 
181
                self.checking = False
 
182
 
 
183
class POP3Checker(BaseChecker):
 
184
        def __init__(self, account_id, status_cb, username, password, server, port, ssl):
 
185
                self.username = username
 
186
                self.server = server
 
187
                self.port = port
 
188
                
 
189
                self.notified = []
 
190
 
 
191
                self.popbox = PopBox(username, password, server, port, ssl)
 
192
 
 
193
                BaseChecker.__init__(self, account_id, status_cb)
 
194
                                        
 
195
        def reset(self):
 
196
                self.notified = []
 
197
        
 
198
        def update_info(self, account):
 
199
                username = account["username"]
 
200
                password = account["password"]
 
201
                ssl = account["ssl"]
 
202
                self.server = account["server"]
 
203
                self.port = account["port"]
 
204
                ssl = False
 
205
                if account.has_key("ssl"):
 
206
                        if account["ssl"] == "1":
 
207
                                ssl = True
 
208
                        elif account["ssl"] == "0":
 
209
                                ssl = False
 
210
                del self.popbox
 
211
                self.popbox = PopBox(username, password, self.server, self.port, ssl)
 
212
        
 
213
        def check(self):
 
214
                # prevent recalling
 
215
                if self.checking: return
 
216
                self.checking = True
 
217
 
 
218
                mailbox = "%s@%s" % (self.username, self.server)
 
219
                
 
220
                print "checking pop3 account %s@%s ..." % (self.username, self.server)
 
221
                try:
 
222
                        # each mail in mail: [subject, from, msgid]
 
223
                        mails = self.popbox.get_mails()
 
224
                except PopBoxConnectionError:
 
225
                        err = _("POP3 Error")
 
226
                        msg = _("Error while connecting to %s on port %s") % (self.server, 
 
227
                                                                                self.port)
 
228
                        self.notify_error(error, msg, False)
 
229
                        self.checking = False
 
230
                        return
 
231
                except PopBoxAuthError:
 
232
                        err = _("POP3 Auth Error")
 
233
                        msg = _("Invalid Username or password for account %s@%s") % (self.username, 
 
234
                                                                                self.server)
 
235
                        self.notify_error(error, msg, True)
 
236
                        self.checking = False
 
237
                        return
 
238
 
 
239
                count = len(mails)
 
240
                returnlist = []
 
241
 
 
242
                if count == 0:
 
243
                        self.checking = False
 
244
                        return
 
245
                        #return mailbox, 0, returnlist
 
246
 
 
247
                if count < MAX_NOTIFIED_MAILS:
 
248
                        loop = count
 
249
                else:
 
250
                        loop = MAX_NOTIFIED_MAILS
 
251
 
 
252
                tmp = 0
 
253
                
 
254
                mustnotify = False
 
255
                mails.reverse()
 
256
                for mail in mails:
 
257
                        msgid = mail[2]
 
258
                        if msgid not in self.notified:
 
259
                                if tmp <= loop:
 
260
                                        try:
 
261
                                                subject = mail[0]
 
262
                                                author = mail[1]
 
263
                                                returnlist.append([author, subject])
 
264
                                        except:
 
265
                                                print "Warning: pop3checker cannot display the message"
 
266
                                        tmp += 1
 
267
                                self.notified.append(msgid)
 
268
                                mustnotify = True
 
269
 
 
270
                print "...done"
 
271
                if not mustnotify:
 
272
                        self.notify_msg(mailbox, count, [])
 
273
                else:
 
274
                        self.notify_msg(mailbox, count, mails)
 
275
 
 
276
                self.checking = False
 
277
 
 
278
class IMAPChecker(BaseChecker):
 
279
        def __init__(self, account_id, status_cb, username, password, 
 
280
                        server, port, ssl, 
 
281
                        use_default_mbox, mbox_dir = None):
 
282
                self.username = username
 
283
                self.server = server
 
284
                self.port = port
 
285
                self.use_default_mbox = use_default_mbox
 
286
                self.mbox_dir = mbox_dir
 
287
                
 
288
                self.notified = []
 
289
 
 
290
                self.imapbox = ImapBox(username, password, 
 
291
                                                server, port, ssl,
 
292
                                                use_default_mbox, mbox_dir)
 
293
 
 
294
                BaseChecker.__init__(self, account_id, status_cb)
 
295
                                        
 
296
        def reset(self):
 
297
                self.notified = []
 
298
        
 
299
        def update_info(self, account):
 
300
                self.username = account["username"]
 
301
                password = account["password"]
 
302
                ssl = account["ssl"]
 
303
                self.server = account["server"]
 
304
                self.port = account["port"]
 
305
                ssl = False
 
306
                if account.has_key("ssl"):
 
307
                        if account["ssl"] == "1":
 
308
                                ssl = True
 
309
                        elif account["ssl"] == "0":
 
310
                                ssl = False
 
311
                mbox_dir = None
 
312
                if account["use_default_mbox"] == "1":
 
313
                        use_default_mbox = True
 
314
                else:
 
315
                        use_default_mbox = False
 
316
                        mbox_dir = account["mbox"]
 
317
                        
 
318
                del self.imapbox
 
319
                self.imapbox = ImapBox(self.username, password, 
 
320
                                                self.server, self.port, ssl,
 
321
                                                use_default_mbox, mbox_dir)
 
322
 
 
323
        def check(self):
 
324
                # prevent recalling
 
325
                if self.checking: return
 
326
                self.checking = True
 
327
 
 
328
                mailbox = "%s@%s" % (self.username, self.server)
 
329
                
 
330
                print "checking imap account %s@%s ..." % (self.username, self.server)
 
331
                try:
 
332
                        # each mail in mail: [subject, from, msgid]
 
333
                        mails = self.imapbox.get_mails()
 
334
                except ImapBoxConnectionError:
 
335
                        err = _("IMAP Error")
 
336
                        msg = _("Error while connecting to %s on port %s") % (self.server, 
 
337
                                                                                self.port)
 
338
                        self.notify_error(error, msg, False)
 
339
                        self.checking = False
 
340
                        return
 
341
                except ImapBoxAuthError:
 
342
                        err = _("IMAP Auth Error")
 
343
                        msg = _("Invalid Username or password for account %s@%s") % (self.username, 
 
344
                                                                                self.server)
 
345
                        
 
346
                        self.notify_error(error, msg, True)
 
347
                        self.checking = False
 
348
                        return
 
349
 
 
350
                count = len(mails)
 
351
                returnlist = []
 
352
 
 
353
                if count == 0:
 
354
                        return mailbox, 0, returnlist
 
355
 
 
356
                if count < MAX_NOTIFIED_MAILS:
 
357
                        loop = count
 
358
                else:
 
359
                        loop = MAX_NOTIFIED_MAILS
 
360
 
 
361
                tmp = 0
 
362
                
 
363
                mustnotify = False
 
364
                mails.reverse()
 
365
                for mail in mails:
 
366
                        msgid = mail[2]
 
367
                        if msgid not in self.notified:
 
368
                                if tmp <= loop:
 
369
                                        try:
 
370
                                                subject = mail[0]
 
371
                                                author = mail[1]
 
372
                                                returnlist.append([author, subject])
 
373
                                        except:
 
374
                                                print "Warning: imapchecker cannot display the message"
 
375
                                        tmp += 1
 
376
                                self.notified.append(msgid)
 
377
                                mustnotify = True
 
378
 
 
379
                print "...done"
 
380
                if not mustnotify:
 
381
                        self.notify_msg(mailbox, count, [])
 
382
                else:
 
383
                        self.notify_msg(mailbox, count, returnlist)
 
384
 
 
385
                self.checking = False
 
386
                
 
387
class Checker:
 
388
 
 
389
        def __init__(self):
 
390
                self.notifier = Notifier()
 
391
                self.checkers = {} # account_id: checker, messages_count
 
392
                self.checking = False
 
393
                self.status_cbs = []
 
394
        
 
395
        def add_status_cb(self, cb):
 
396
                self.status_cbs.append(cb)
 
397
        
 
398
        def remove_status_cb(self,cb):
 
399
                self.status_cbs.remove(cb)
 
400
 
 
401
        def set_no_accounts_cb(self, cb):
 
402
                self.no_accounts_cb = cb
 
403
 
 
404
        def init_checkers(self, no_accounts_cb):
 
405
                """
 
406
                Build checkers list.
 
407
                """
 
408
                #self.checkers = []
 
409
                
 
410
                am = AccountManager().get_manager()
 
411
                accounts = am.get_accounts_dicts()
 
412
                if accounts is None or len(accounts) == 0:
 
413
                        self.checkers = {}
 
414
                        if no_accounts_cb is not None:
 
415
                                no_accounts_cb()
 
416
                        # nothing more to do
 
417
                        return
 
418
 
 
419
                
 
420
                id_list = []
 
421
                for account in accounts:
 
422
                        id_list.append(account["id"])
 
423
 
 
424
                        needed_keys = ["type", "username", "password", "enabled"]
 
425
                        has_needed = True
 
426
                        for k in needed_keys:
 
427
                                if not account.has_key(k):
 
428
                                        print "Warnig: bad configration"
 
429
                                        has_needed = False
 
430
                                        break
 
431
 
 
432
                        if not has_needed: continue
 
433
 
 
434
                        if account["enabled"] == "0":
 
435
                                # we no more want this checker
 
436
                                if self.checkers.has_key(account["id"]):
 
437
                                        del self.checkers[account["id"]]
 
438
                                continue
 
439
 
 
440
                        if account["id"] in self.checkers.keys():
 
441
                                # We already have a checker for this account
 
442
                                checker, msg_count = self.checkers[account["id"]]
 
443
                                checker.update_info(account)
 
444
                                continue
 
445
 
 
446
                        if account["type"] == "gmail":
 
447
                                tmp = GmailChecker(account["id"], self.set_status,
 
448
                                                        account["username"], 
 
449
                                                        account["password"])
 
450
                                self.checkers[account["id"]] = [tmp, 0]
 
451
                        elif account["type"] == "pop3" or account["type"] == "imap":
 
452
                                user = account["username"]
 
453
                                passw = account["password"]
 
454
                                if not account.has_key("server"):
 
455
                                        print "Warnig: bad configration"
 
456
                                        continue
 
457
                                server = account["server"]
 
458
                                ssl = False
 
459
                                if account.has_key("ssl"):
 
460
                                        if account["ssl"] == "1":
 
461
                                                ssl = True
 
462
                                        elif account["ssl"] == "0":
 
463
                                                ssl = False
 
464
                                if account["type"] == "pop3":
 
465
                                        port = 110 # default pop3 port
 
466
                                        if account.has_key("port"):
 
467
                                                port = account["port"]
 
468
 
 
469
                                        tmp = POP3Checker(account["id"], self.set_status,
 
470
                                                        user, passw, server, port, ssl)
 
471
                                else:
 
472
                                        port = 143 # default imap port
 
473
                                        if account.has_key("port"):
 
474
                                                port = account["port"]
 
475
                                        #imap
 
476
                                        if account["use_default_mbox"] == "1":
 
477
 
 
478
                                                tmp = IMAPChecker(account["id"], self.set_status,
 
479
                                                                user, passw, 
 
480
                                                                server, port, ssl, True)
 
481
                                        else:
 
482
                                                mbox = account["mbox"]
 
483
                                                tmp = IMAPChecker(user, passw, 
 
484
                                                                server, port, 
 
485
                                                                ssl, False, mbox)
 
486
                                self.checkers[account["id"]] = [tmp, 0]
 
487
                        else:
 
488
                                print "Error: unrecognized account type"
 
489
                
 
490
                # remove checker if the account no more exist
 
491
                for id in self.checkers.keys():
 
492
                        if id not in id_list:
 
493
                                del self.checkers[id]
 
494
 
 
495
                
 
496
        def reset(self):
 
497
                for account_id, values in self.checkers.iteritems():
 
498
                        checker = values[0]
 
499
                        checker.reset()
 
500
        
 
501
        def set_status(self, account_id, messages_count, title, message):
 
502
                """
 
503
                This method is only called by BaseChecker
 
504
                """
 
505
                checker, msgs = self.checkers[account_id]
 
506
                self.checkers[account_id] = [checker, messages_count]
 
507
 
 
508
                total = 0
 
509
                for checker, count in self.checkers.values():
 
510
                        total += count
 
511
 
 
512
                if message != "":
 
513
                        self.notifier.notify(title, message, msec = 10000)
 
514
                
 
515
                        for cb in self.status_cbs:
 
516
                                cb(total, title, message)
 
517
                else:
 
518
                        # only update count
 
519
                        for cb in self.status_cbs:
 
520
                                cb(total, None, None)
 
521
 
 
522
        
 
523
        def check(self):
 
524
                
 
525
                self.init_checkers(None)
 
526
 
 
527
                mailslists = []
 
528
                for account_id, values in self.checkers.iteritems():
 
529
                        checker = values[0]
 
530
                        thread.start_new_thread(checker.check, ())
 
531
                
 
532
        
 
533
if __name__ == "__main__":
 
534
        c = Checker()
 
535
        c.check()