~bioxy/awn-extras/sabnzbd-applet

« back to all changes in this revision

Viewing changes to applets/maintained/mail/mail.py

  • Committer: Gabor Karsay
  • Date: 2011-01-24 08:04:02 UTC
  • mfrom: (1494.1.21 awn-extras-keyring)
  • Revision ID: gabor.karsay@gmx.at-20110124080402-l7a1a0cfixkgswcv
Mail, awnlib: fix issues with GNOME keyring,
Mail: decode internationalized subject headers in IMAP and POP backend
Mail: some small fixes
Mail: remove feature "Hide applet if no new messages"

Show diffs side-by-side

added added

removed removed

Lines of Context:
59
59
    return (entry, hbox)
60
60
 
61
61
 
 
62
def check_login_data(backend, data):
 
63
    '''Sanity check for login data. Data from login dialog should always
 
64
    pass this test, this is mainly for wrong or manipulated keys.'''
 
65
 
 
66
    for field in backend.fields:
 
67
        if field not in data:
 
68
            raise LoginError("Wrong or corrupt key")
 
69
        if data[field] is None:
 
70
            raise LoginError("Key with field is None")
 
71
        if type(data[field]) == str and len(data[field]) == 0:
 
72
            raise LoginError("Key with empty field")
 
73
 
 
74
    # Optional fields must be in data but may be empty.
 
75
    if hasattr(backend, "optional"):
 
76
        for field in backend.optional:
 
77
            if field not in data:
 
78
                raise LoginError("Wrong or corrupt key")
 
79
            if data[field] is None:
 
80
                raise LoginError("Key with field is None")
 
81
 
 
82
 
 
83
def decode_header(message):
 
84
    ''' Decodes internationalized headers like these:
 
85
    =?UTF-8?B?dMOkc3Q=?= '''
 
86
 
 
87
    if message is None or len(message) == 0:
 
88
        return _("[No Subject]")
 
89
    text, charset = email.Header.decode_header(message)[0]
 
90
    if charset:
 
91
        message = text.decode(charset)
 
92
    return message
 
93
 
 
94
 
 
95
class LoginError(Exception):
 
96
    pass
 
97
 
 
98
 
62
99
class MailApplet:
63
100
 
64
101
    def __init__(self, applet):
77
114
 
78
115
        self.__dialog = MainDialog(self)
79
116
 
80
 
        self.login()
81
 
 
82
 
    def login(self, force=False):
83
 
        """
84
 
        Login. Try to login from saved key, if this does not exist or
85
 
        force is True, show login dialog
86
 
 
87
 
        """
88
 
        self.awn.theme.icon("login")
89
 
 
90
 
        # If we're forcing initiation, just draw the dialog.
91
 
        # We wouldn't be forcing if we want to use the saved login token.
92
 
        if force:
 
117
        # Login from key or dialog
 
118
        self.init_keyring()
 
119
        login_data = self.get_data_from_key(self.get_key())
 
120
        if login_data:
 
121
            self.login(login_data)
 
122
        else:
93
123
            self.__dialog.login_form()
94
 
            self.awn.dialog.toggle("main", "show")
95
 
            return
96
 
 
97
 
        try:
98
 
            token = self.awn.settings["login-token"]
99
 
        except:  # You know what? too bad. No get_null, no exception handling
100
 
            token = 0
101
 
 
102
 
        # Force login if the token is 0, which we take to mean that there is no
103
 
        # login information. We'd delete the key, but that's not always
104
 
        # supported.
105
 
        if token == 0:
106
 
            return self.login(True)
107
 
 
108
 
        key = self.awn.keyring.from_token(token)
109
 
 
110
 
        if not self.awn.keyring.unlock():
111
 
            return self.login(True)
112
 
 
113
 
        self.perform_login(key, startup=True)
 
124
            #self.awn.dialog.toggle("main", "show")
 
125
 
 
126
    def init_keyring(self):
 
127
        try:
 
128
            self.keyring = awnlib.Keyring()
 
129
        except awnlib.KeyringError:
 
130
            self.keyring = None
 
131
 
 
132
    def get_key(self):
 
133
        '''Get key for backend from Gnome Keyring'''
 
134
 
 
135
        def set_login_settings_to_default():
 
136
            # Default setting means we have no key at all
 
137
            self.awn.settings["login-keyring-token"] = ["Backend", "Keyring", "Token"]
 
138
 
 
139
        if self.keyring is None:
 
140
            return None
 
141
        if self.back.__name__ == "UnixSpool":
 
142
            return None
 
143
 
 
144
        # Migration code from old "login-token" to new "login-keyring-token"
 
145
        # To be deleted in the version following version 0.6
 
146
        try:
 
147
            old_token = self.awn.settings["login-token"]
 
148
        except ValueError:
 
149
            # New installation, token does not exist
 
150
            old_token = 0
 
151
        if old_token != 0:
 
152
            import gnomekeyring
 
153
            self.awn.settings["login-keyring-token"] = \
 
154
                [self.back.__name__,
 
155
                 gnomekeyring.get_default_keyring_sync(),
 
156
                 str(old_token)]
 
157
            self.awn.settings["login-token"] = 0
 
158
 
 
159
        keydata = self.awn.settings["login-keyring-token"]
 
160
        if len(keydata) == 0 or len(keydata) > 3:
 
161
            set_login_settings_to_default()
 
162
            return None
 
163
        if keydata[0] != self.back.__name__:
 
164
            return None
 
165
 
 
166
        try:
 
167
            key = self.keyring.from_token(keydata[1], long(keydata[2]))
 
168
            return key
 
169
        except awnlib.KeyringNoMatchError:
 
170
            set_login_settings_to_default()
 
171
            return None
 
172
 
 
173
    def get_data_from_key(self, key):
 
174
        # Unix Spool has no password, get data from config
 
175
        if self.back.__name__ == "UnixSpool":
 
176
            path = self.awn.settings["unix-spool"]
 
177
            if path == "default":
 
178
                path = os.path.join("/var/spool/mail/",
 
179
                    os.path.split(os.path.expanduser("~"))[1])
 
180
            data = {}
 
181
            data['path'] = path
 
182
            data['username'] = os.path.split(path)[1]
 
183
            return data
 
184
 
 
185
        if self.keyring is None or key is None:
 
186
            return None
 
187
 
 
188
        try:
 
189
            data = key.attrs
 
190
            data['password'] = key.password
 
191
        except awnlib.KeyringError:
 
192
            # Reads data from keyring, probably only KeyringCancelledError,
 
193
            # but return None on any failure
 
194
            return None
 
195
 
 
196
        return data
 
197
 
 
198
    def save_key(self, data):
 
199
        '''Save login data for backend in Gnome Keyring'''
 
200
 
 
201
        if self.keyring is None or data is None:
 
202
            return
 
203
 
 
204
        # Spool has no password, just save path in config
 
205
        if self.back.__name__ == "UnixSpool":
 
206
            self.awn.settings["unix-spool"] = data["path"]
 
207
            return
 
208
 
 
209
        password = data['password']
 
210
        attrs = data.copy()
 
211
        del attrs['password']
 
212
        desc = "Awn Extras/Mail/" + self.back.__name__ + "/" + attrs['username']
 
213
 
 
214
        key = self.get_key()
 
215
        if key is None:
 
216
            try:
 
217
                key = self.keyring.new(
 
218
                          keyring=None,
 
219
                          name=desc,
 
220
                          pwd=password,
 
221
                          attrs=attrs,
 
222
                          type='generic')
 
223
                self.awn.settings["login-keyring-token"] = [self.back.__name__,
 
224
                                                            key.keyring,
 
225
                                                            str(key.token)]
 
226
            except awnlib.KeyringCancelledError:
 
227
                # User cancelled himself
 
228
                pass
 
229
        else:
 
230
            try:
 
231
                key.password = password
 
232
                key.attrs = attrs
 
233
                key.name = desc
 
234
            except awnlib.KeyringCancelledError:
 
235
                pass
114
236
 
115
237
    def logout(self):
116
238
        if hasattr(self, "timer"):
117
239
            self.timer.stop()
118
240
        self.awn.theme.icon("login")
119
 
        self.awn.settings["login-token"] = 0
120
 
 
121
 
    def perform_login(self, key, startup=False):
122
 
        if key.token == 0:
123
 
            return
124
 
 
125
 
        try:
126
 
            self.mail = self.back(key)  # Login
127
 
        except RuntimeError, error:
128
 
            self.__dialog.login_form(True, str(error))
129
 
        else:
130
 
            try:
131
 
                self.mail.update()  # Update
132
 
            except RuntimeError:
133
 
                self.__dialog.login_form(True)
134
 
 
135
 
            else:
136
 
                self.awn.notification.send(_("Mail Applet"),
137
 
                    _("Logging in as %s") % key.attrs["username"],
138
 
                    self.__getIconPath("login"))
139
 
 
140
 
                # Login successful
141
 
                self.awn.theme.icon("read")
142
 
 
143
 
                self.awn.settings["login-token"] = key.token
144
 
 
145
 
                self.timer = self.awn.timing.register(self.refresh,
146
 
                                                     self.awn.settings["timeout"] * 60)
147
 
                if startup and self.awn.settings["hide"]:
148
 
                    self.awn.timing.delay(self.refresh, 0.1)  # init must finish first
149
 
                else:
150
 
                    self.refresh(show=False)
151
 
 
152
 
    def refresh(self, show=True):
 
241
        self.awn.tooltip.set(_("Mail Applet (Click to Log In)"))
 
242
 
 
243
    def login(self, data):
 
244
        # Initialize backend, check login data
 
245
        # IMAP backend already connects to server
 
246
        try:
 
247
            self.mail = self.back(data)
 
248
        except LoginError, error:
 
249
            self.__dialog.login_form(True, str(error))
 
250
            return
 
251
 
 
252
        # Connect to server
 
253
        try:
 
254
            self.mail.update()
 
255
        except LoginError, error:
 
256
            self.__dialog.login_form(True, str(error))
 
257
            return
 
258
 
 
259
        # Login successful
 
260
        self.__dialog.update_email_list()
 
261
        self.awn.notification.send(_("Mail Applet"),
 
262
                                   _("Logging in as %s") % data["username"],
 
263
                                   self.__getIconPath("login"))
 
264
        self.save_key(data)
 
265
        self.timer = self.awn.timing.register(self.refresh,
 
266
                                             self.awn.settings["timeout"] * 60)
 
267
 
 
268
    def refresh(self):
153
269
        oldSubjects = self.mail.subjects
154
270
 
 
271
        # Login
155
272
        try:
156
273
            self.mail.update()
157
 
        except RuntimeError, e:
 
274
        except LoginError, e:
158
275
            self.awn.theme.icon("error")
159
 
 
160
276
            if self.awn.settings["show-network-errors"]:
161
277
                self.awn.notification.send(_("Network error - Mail Applet"), str(e), "")
162
278
            return
163
279
 
 
280
        self.__dialog.update_email_list()
 
281
 
 
282
        # Notify on new subjects
164
283
        diffSubjects = [i for i in self.mail.subjects if i not in oldSubjects]
165
 
 
166
284
        if len(diffSubjects) > 0:
167
285
            msg = strMailMessages(len(diffSubjects)) + ":\n" + \
168
286
                                                        "\n".join(diffSubjects)
169
 
 
170
287
            self.awn.notification.send(_("New Mail - Mail Applet"), msg,
171
288
                                 self.__getIconPath("mail-unread"))
172
289
 
173
 
        self.awn.tooltip.set(strMessages(len(self.mail.subjects)))
174
 
 
175
 
        self.awn.theme.icon("unread" if len(self.mail.subjects) > 0 else "read")
176
 
 
177
 
        if self.awn.settings["hide"] and len(self.mail.subjects) == 0:
178
 
            self.awn.icon.hide()
179
 
            self.awn.dialog.hide()
180
 
        elif show:
181
 
            self.awn.show()
182
 
 
183
 
        self.__dialog.update_email_list()
184
 
 
185
290
    def __getIconPath(self, name):
186
291
        path = os.path.join(mail_theme_dir, self.awn.settings["theme"], "scalable", name + ".svg")
187
292
        if os.path.isfile(path):
253
358
        binder = self.awn.settings.get_binder(prefs)
254
359
        binder.bind("theme", "combobox-theme", key_callback=self.awn.theme.theme)
255
360
        binder.bind("email-client", "entry-client")
256
 
        binder.bind("hide", "checkbutton-hide-applet", key_callback=self.refresh_hide_applet)
257
361
        binder.bind("show-network-errors", "checkbutton-alert-errors")
258
362
        binder.bind("timeout", "spinbutton-timeout", key_callback=change_timeout)
259
363
        self.awn.settings.load_bindings(binder)
260
364
 
261
 
    def refresh_hide_applet(self, value):
262
 
        if hasattr(self, "mail") and value and len(self.mail.subjects) == 0:
263
 
            self.awn.icon.hide()
264
 
            self.awn.dialog.hide()
265
 
        else:
266
 
            self.awn.show()
267
 
 
268
365
 
269
366
class MainDialog:
270
367
 
350
447
        parent.remove(self.__email_list)
351
448
 
352
449
        mail = self.__parent.mail
 
450
        self.__parent.awn.tooltip.set(strMessages(len(mail.subjects)))
 
451
        self.__parent.awn.theme.icon("unread" if len(mail.subjects) > 0 else "read")
353
452
        if len(mail.subjects) > 0:
354
453
            self.__dialog.set_title(strMessages(len(self.__parent.mail.subjects)))
355
454
 
376
475
        for widget in self.login_widgets:
377
476
            widget.destroy()
378
477
 
379
 
        if hasattr(self.__parent.back, "drawLoginWindow"):
380
 
            t = self.__parent.back.drawLoginWindow(*groups)
381
 
            self.login_widgets.append(t["layout"])
382
 
            vbox.add(t["layout"])
383
 
        else:
384
 
            usrE, box = get_label_entry(_("Username:"), *groups)
385
 
            vbox.add(box)
386
 
            self.login_widgets.append(box)
387
 
 
388
 
            pwdE, box = get_label_entry(_("Password:"), *groups)
389
 
            pwdE.set_visibility(False)
390
 
            vbox.add(box)
391
 
            self.login_widgets.append(box)
392
 
 
393
 
            t = {}
394
 
 
395
 
            t["callback"] = \
396
 
                lambda widgets, awn: awn.keyring.new(
397
 
                    "Mail Applet - %s(%s)" % (widgets[0].get_text(),
398
 
                                        self.__parent.awn.settings["backend"]),
399
 
                    widgets[1].get_text(),
400
 
                    {"username": widgets[0].get_text()}, "network")
401
 
 
402
 
            t["widgets"] = [usrE, pwdE]
403
 
 
 
478
        t = self.__parent.back.drawLoginWindow(*groups)
 
479
        self.login_widgets.append(t["layout"])
 
480
        vbox.add(t["layout"])
404
481
        vbox.show_all()
405
 
 
406
482
        return t
407
483
 
408
484
    def login_form(self, error=False, message=_("Wrong username or password")):
414
490
        self.__current_type = "login_form"
415
491
 
416
492
        self.__dialog.set_title(_("Log In"))
 
493
        self.callback = None
417
494
 
418
495
        vbox = gtk.VBox(spacing=12)
419
496
        vbox.set_border_width(6)
423
500
        label_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
424
501
 
425
502
        # Display an error message if there is an error
 
503
        errorbox = None
426
504
        if error:
427
505
            image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_ERROR,
428
506
                                             gtk.ICON_SIZE_MENU)
435
513
 
436
514
            # Align the image and label in the center, with the image
437
515
            # right next to the label
438
 
            hboxbox = gtk.HBox(False)
439
 
            hboxbox.pack_start(hbox, True, False)
 
516
            errorbox = gtk.HBox(False)
 
517
            errorbox.pack_start(hbox, True, False)
440
518
 
441
 
            vbox.add(hboxbox)
 
519
            vbox.add(errorbox)
442
520
 
443
521
        # Allow user to change the backend in the login dialog
444
522
        def changed_backend_cb(combobox, label_group):
448
526
                backends = [i for i in dir(Backends) if i[:2] != "__"]
449
527
                self.__parent.awn.settings["backend"] = backends[backend]
450
528
                self.__parent.back = getattr(Backends(), backends[backend])
451
 
                self.__login_get_widgets(vbox, label_group)
 
529
                self.callback = self.__login_get_widgets(vbox, label_group)
 
530
 
 
531
                # Remove previous error message, fill in data if available
 
532
                if errorbox:
 
533
                    errorbox.hide()
 
534
                fill_in()
 
535
 
 
536
                # Make submit button insensitive if required data is missing
 
537
                connect_entries()
 
538
                onchanged(None)
452
539
 
453
540
        label_backend = gtk.Label(_("Type:"))
454
541
        label_backend.set_alignment(0.0, 0.5)
470
557
        vbox.add(hbox_backend)
471
558
 
472
559
        self.login_widgets = []
473
 
        t = self.__login_get_widgets(vbox, label_group)
 
560
        self.callback = self.__login_get_widgets(vbox, label_group)
 
561
 
 
562
        def fill_in():
 
563
            login_data = self.__parent.get_data_from_key(self.__parent.get_key())
 
564
            if login_data:
 
565
                try:
 
566
                    check_login_data(self.__parent.back, login_data)
 
567
                    self.callback['fill-in'](self.callback['widgets'], login_data)
 
568
                except LoginError:
 
569
                    # Corrupt key, user won't know what to do, just skip it
 
570
                    pass
 
571
 
 
572
        fill_in()
474
573
 
475
574
        image_login = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
476
575
                                               gtk.ICON_SIZE_BUTTON)
478
577
        submit_button.set_image(image_login)
479
578
 
480
579
        def onsubmit(widget):
481
 
            self.__parent.perform_login(
482
 
                                t["callback"](t["widgets"], self.__parent.awn))
 
580
            self.__parent.login(
 
581
                                self.callback["callback"](self.callback["widgets"]))
483
582
        submit_button.connect("clicked", onsubmit)
484
583
 
 
584
        # Make submit button insensitive if required data is missing
 
585
        def onchanged(entry):
 
586
            state = True
 
587
            for e in self.entries:
 
588
                if e.get_text() == "":
 
589
                    state = False
 
590
            submit_button.set_sensitive(state)
 
591
 
 
592
        def connect_entries():
 
593
            self.entries = []
 
594
            for w in self.callback['widgets']:
 
595
                if isinstance(w, gtk.Entry):
 
596
                    w.connect("activate", onsubmit)
 
597
                    if not hasattr(w, "optional"):
 
598
                        self.entries.append(w)
 
599
                        w.connect("changed", onchanged)
 
600
 
 
601
        connect_entries()
 
602
        onchanged(None)
 
603
 
485
604
        hbox_login = gtk.HBox(False, 0)
486
605
        hbox_login.pack_start(submit_button, True, False)
487
606
        vbox.pack_end(hbox_login)
501
620
    class GMail:
502
621
 
503
622
        title = "Gmail"
 
623
        fields = ['username', 'password']
504
624
 
505
 
        def __init__(self, key):
506
 
            self.key = key
 
625
        def __init__(self, data):
 
626
            self.data = data
 
627
            check_login_data(self, self.data)
507
628
 
508
629
        def url(self):
509
630
            return "https://mail.google.com/mail/"
510
631
 
511
632
        def update(self):
512
 
 
513
 
            if not "username" in self.key.attrs:
514
 
                raise RuntimeError(_("Could not log in: No username"))
515
 
                return
516
 
 
517
633
            f = feedparser.parse(\
518
634
                "https://%s:%s@mail.google.com/gmail/feed/atom" \
519
 
                            % (self.key.attrs["username"], self.key.password))
 
635
                            % (self.data["username"], self.data['password']))
520
636
 
521
637
            if "bozo_exception" in f.keys():
522
 
                raise RuntimeError(_("There seem to be problems with our \
 
638
                raise LoginError(_("There seem to be problems with our \
523
639
connection to your account. Your best bet is probably \
524
640
to log out and try again."))
525
641
            # Hehe, Google is funny. Bozo exception
566
682
            # Get source of message
567
683
            return n
568
684
 
 
685
        @classmethod
 
686
        def drawLoginWindow(cls, *groups):
 
687
            vbox = gtk.VBox(spacing=12)
 
688
 
 
689
            usrE, box = get_label_entry(_("Username:"), *groups)
 
690
            vbox.add(box)
 
691
 
 
692
            pwdE, box = get_label_entry(_("Password:"), *groups)
 
693
            pwdE.set_visibility(False)
 
694
            vbox.add(box)
 
695
 
 
696
            return {"layout": vbox, "callback": cls.__submitLoginWindow,
 
697
                "widgets": [usrE, pwdE],
 
698
                "fill-in": cls.__fillinLoginWindow}
 
699
 
 
700
        @staticmethod
 
701
        def __submitLoginWindow(widgets):
 
702
            return {'username': widgets[0].get_text(), \
 
703
                    'password': widgets[1].get_text()}
 
704
 
 
705
        @staticmethod
 
706
        def __fillinLoginWindow(widgets, data):
 
707
            widgets[0].set_text(data['username'])
 
708
            widgets[1].set_text(data['password'])
 
709
 
569
710
    class GApps:
570
711
 
571
712
        title = _("Google Apps")
 
713
        fields = ['username', 'domain', 'password']
572
714
 
573
 
        def __init__(self, key):
574
 
            self.key = key
 
715
        def __init__(self, data):
 
716
            self.data = data
 
717
            check_login_data(self, self.data)
575
718
 
576
719
        def url(self):
577
 
            return "https://mail.google.com/a/%s" % self.key.attrs["domain"]
 
720
            return "https://mail.google.com/a/%s" % self.data["domain"]
578
721
 
579
722
        def update(self):
580
 
 
581
 
            if not "username" in self.key.attrs or \
582
 
               not "domain" in self.key.attrs:
583
 
                raise RuntimeError(_("Could not log in: No username or domain"))
584
 
                return
585
 
 
586
723
            f = feedparser.parse(\
587
724
                "https://%s%%40%s:%s@mail.google.com/a/%s/feed/atom" \
588
 
                 % (self.key.attrs["username"], self.key.attrs["domain"], \
589
 
                    self.key.password, self.key.attrs["domain"]))
 
725
                 % (self.data["username"], self.data["domain"], \
 
726
                    self.data['password'], self.data["domain"]))
590
727
 
591
728
            if "bozo_exception" in f.keys():
592
 
                raise RuntimeError(_("There seem to be problems with our \
 
729
                raise LoginError(_("There seem to be problems with our \
593
730
connection to your account. Your best bet is probably \
594
731
to log out and try again."))
595
732
            # Hehe, Google is funny. Bozo exception
651
788
            vbox.add(box)
652
789
 
653
790
            return {"layout": vbox, "callback": cls.__submitLoginWindow,
654
 
                "widgets": [usrE, pwdE, domE]}
655
 
 
656
 
        @staticmethod
657
 
        def __submitLoginWindow(widgets, awn):
658
 
            return awn.keyring.new("Mail Applet - %s(%s)" \
659
 
                % (widgets[0].get_text(), "GApps"), \
660
 
                widgets[1].get_text(), \
661
 
                {"username": widgets[0].get_text(),
662
 
                "domain": widgets[2].get_text()}, "network")
 
791
                "widgets": [usrE, pwdE, domE],
 
792
                "fill-in": cls.__fillinLoginWindow}
 
793
 
 
794
        @staticmethod
 
795
        def __submitLoginWindow(widgets):
 
796
            return {'username': widgets[0].get_text(), \
 
797
                    'password': widgets[1].get_text(), \
 
798
                    'domain': widgets[2].get_text()}
 
799
 
 
800
        @staticmethod
 
801
        def __fillinLoginWindow(widgets, data):
 
802
            widgets[0].set_text(data['username'])
 
803
            widgets[1].set_text(data['password'])
 
804
            widgets[2].set_text(data['domain'])
663
805
 
664
806
    try:
665
807
        global mailbox
671
813
        class UnixSpool:
672
814
 
673
815
            title = _("Unix Spool")
 
816
            fields = ['username', 'path']
674
817
 
675
 
            def __init__(self, key):
676
 
                self.path = key.attrs["path"]
 
818
            def __init__(self, data):
 
819
                self.path = data['path']
677
820
 
678
821
            def update(self):
679
 
                self.box = mailbox.mbox(self.path)
 
822
                try:
 
823
                    self.box = mailbox.mbox(self.path)
 
824
                except IOError, e:
 
825
                    raise LoginError(e)
680
826
                email = []
681
827
 
682
828
                self.subjects = []
695
841
                path, box = get_label_entry(_("Spool Path:"), *groups)
696
842
                vbox.add(box)
697
843
 
698
 
                path.set_text("/var/spool/mail/" + os.path.split(os.path.expanduser("~"))[1])
699
 
 
700
844
                return {"layout": vbox, "callback": cls.__submitLoginWindow,
 
845
                    "fill-in": cls.__fillinLoginWindow,
701
846
                    "widgets": [path]}
702
847
 
703
848
            @staticmethod
704
 
            def __submitLoginWindow(widgets, awn):
705
 
                return awn.keyring.new("Mail Applet - %s" \
706
 
                    % "UnixSpool", "-", \
707
 
                    {"path": widgets[0].get_text(),
708
 
                     "username": os.path.split(widgets[0].get_text())[1]},
709
 
                     "network")
 
849
            def __submitLoginWindow(widgets):
 
850
                return {'path': widgets[0].get_text(), \
 
851
                        'username': os.path.split(widgets[0].get_text())[1]}
 
852
 
 
853
            @staticmethod
 
854
            def __fillinLoginWindow(widgets, data):
 
855
                widgets[0].set_text(data['path'])
710
856
 
711
857
    try:
712
858
        global poplib
718
864
        class POP:
719
865
 
720
866
            title = "POP"
 
867
            fields = ['username', 'url', 'usessl', 'password']
721
868
 
722
 
            def __init__(self, key):
723
 
                self.key = key
 
869
            def __init__(self, data):
 
870
                self.data = data
 
871
                check_login_data(self, self.data)
724
872
 
725
873
            def update(self):
726
874
                # POP is not designed for being logged in continously.
728
876
 
729
877
                # Log in
730
878
                try:
731
 
                    if self.key.attrs["usessl"]:
732
 
                        self.server = poplib.POP3_SSL(self.key.attrs["url"])
 
879
                    if 'usessl' in self.data and self.data["usessl"]:
 
880
                        self.server = poplib.POP3_SSL(self.data["url"])
733
881
                    else:
734
 
                        self.server = poplib.POP3(self.key.attrs["url"])
 
882
                        self.server = poplib.POP3(self.data["url"])
735
883
                except socket.gaierror, message:
736
 
                    raise RuntimeError(_("Could not log in: ") + str(message))
 
884
                    raise LoginError(_("Could not log in: ") + str(message))
737
885
                except socket.error, message:
738
 
                    raise RuntimeError(_("Could not log in: ") + str(message))
 
886
                    raise LoginError(_("Could not log in: ") + str(message))
739
887
 
740
888
                else:
741
 
                    if not "username" in self.key.attrs:
742
 
                        raise RuntimeError(_("Could not log in: No username"))
743
 
                    self.server.user(self.key.attrs["username"])
 
889
                    self.server.user(self.data["username"])
744
890
                    try:
745
 
                        self.server.pass_(self.key.password)
 
891
                        self.server.pass_(self.data['password'])
746
892
                    except poplib.error_proto:
747
 
                        raise RuntimeError(_("Could not log in: Username or password incorrect"))
 
893
                        raise LoginError(_("Could not log in: Username or password incorrect"))
748
894
 
749
895
                # Fetch mails
750
896
                try:
751
897
                    messagesInfo = self.server.list()[1][-20:]
752
 
                except poplib.error_proto, err:
753
 
                    raise RuntimeError("POP protocol error: %s" % err)
 
898
                except poplib.error_proto, message:
 
899
                    raise LoginError(_("Could not log in: ") + str(message))
754
900
 
755
901
                emails = []
756
902
                for msg in messagesInfo:
776
922
                    # TODO: Implement body previews
777
923
 
778
924
                    if "subject" in msg:
779
 
                        subject = msg["subject"]
 
925
                        subject = decode_header(msg["subject"])
780
926
                    else:
781
927
                        subject = _("[No Subject]")
782
928
 
807
953
                vbox.add(sslE)
808
954
 
809
955
                return {"layout": vbox, "callback": cls.__submitLoginWindow,
810
 
                    "widgets": [usrE, pwdE, srvE, sslE]}
811
 
 
812
 
            @staticmethod
813
 
            def __submitLoginWindow(widgets, awn):
814
 
                return awn.keyring.new("Mail Applet - %s(%s)" \
815
 
                    % (widgets[0].get_text(), "POP"), \
816
 
                    widgets[1].get_text(), \
817
 
                    {"username": widgets[0].get_text(),
818
 
                    "url": widgets[2].get_text(),
819
 
                    "usessl": widgets[3].get_active()}, "network")
 
956
                    "widgets": [usrE, pwdE, srvE, sslE],
 
957
                    "fill-in": cls.__fillinLoginWindow}
 
958
 
 
959
            @staticmethod
 
960
            def __submitLoginWindow(widgets):
 
961
                return {'username': widgets[0].get_text(), \
 
962
                        'password': widgets[1].get_text(), \
 
963
                        'url': widgets[2].get_text(), \
 
964
                        "usessl": widgets[3].get_active()}
 
965
 
 
966
            @staticmethod
 
967
            def __fillinLoginWindow(widgets, data):
 
968
                widgets[0].set_text(data['username'])
 
969
                widgets[1].set_text(data['password'])
 
970
                widgets[2].set_text(data['url'])
 
971
                widgets[3].set_active(data['usessl'])
820
972
 
821
973
    try:
822
974
        global imaplib
828
980
        class IMAP:
829
981
 
830
982
            title = "IMAP"
831
 
 
832
 
            def __init__(self, key):
833
 
                args = key.attrs["url"].split(":")
834
 
 
835
 
                if key.attrs["usessl"]:
836
 
                    self.server = imaplib.IMAP4_SSL(*args)
837
 
                else:
838
 
                    self.server = imaplib.IMAP4(*args)
839
 
 
840
 
                try:
841
 
                    self.server.login(key.attrs["username"], key.password)
842
 
                except poplib.error_proto:
843
 
                    raise RuntimeError(_("Could not log in"))
 
983
            fields = ['username', 'url', 'usessl', 'password']
 
984
            optional = ['folder']
 
985
 
 
986
            def __init__(self, data):
 
987
                self.data = data
 
988
                check_login_data(self, self.data)
 
989
                args = self.data["url"].split(":")
 
990
 
 
991
                try:
 
992
                    if self.data["usessl"]:
 
993
                        self.server = imaplib.IMAP4_SSL(*args)
 
994
                    else:
 
995
                        self.server = imaplib.IMAP4(*args)
 
996
                except imaplib.socket.error:
 
997
                    raise LoginError(_("Server did not respond"))
 
998
 
 
999
                try:
 
1000
                    self.server.login(self.data["username"], self.data['password'])
 
1001
                except imaplib.IMAP4.error:
 
1002
                    raise LoginError(_("Could not log in"))
844
1003
 
845
1004
                mboxs = [i.split(")")[1].split(" ", 2)[2].strip('"') for i in self.server.list()[1]]
846
 
                self.box = key.attrs["folder"]
 
1005
                self.box = self.data["folder"]
847
1006
 
848
1007
                if self.box not in mboxs and self.box != "":
849
 
                    raise RuntimeError(_("Folder does not exst"))
 
1008
                    raise LoginError(_("Folder does not exst"))
850
1009
 
851
1010
                if self.box != "":
852
1011
                    self.server.select(self.box)
854
1013
            def update(self):
855
1014
                self.subjects = []
856
1015
 
857
 
                if self.box != "":
858
 
                    emails = [i for i in self.server.search(None, "(UNSEEN)")[1][0].split(" ") if i != ""]
859
 
 
 
1016
                def get_subject(emails):
860
1017
                    for i in emails:
861
1018
                        s = self.server.fetch(i, '(BODY[HEADER.FIELDS (SUBJECT)])')[1][0]
862
 
 
 
1019
                        if self.data['url'] == "imap.gmail.com":  # GMail sets mails unread
 
1020
                            self.server.store(i, '-FLAGS', '\\Seen')
863
1021
                        if s is not None:
864
 
                            self.subjects.append(s[1][9:].replace("\r\n", "\n").replace("\n", ""))  # Don't ask
 
1022
                            subject = s[1][9:].replace("\r\n", "\n").replace("\n", "")  # Don't ask
 
1023
                            self.subjects.append(decode_header(subject))
 
1024
 
 
1025
                if self.box != "":
 
1026
                    emails = [i for i in self.server.search(None, "(UNSEEN)")[1][0].split(" ") if i != ""]
 
1027
                    get_subject(emails)
 
1028
 
865
1029
                else:
866
1030
                    mboxs = [re.search("(\W*) (\W*) (.*)", i).groups()[2] for i in self.server.list()[1]]
867
 
                    mboxs = [i for i in mboxs if i not in ("Sent", "Trash") and i[:6] != "[Gmail]"]
 
1031
                    mboxs = [i for i in mboxs if i not in ("Sent", "Trash") and i[1:8] != "[Gmail]"]
868
1032
 
869
 
                    emails = []
870
1033
                    for b in mboxs:
871
1034
                        r, d = self.server.select(b)
872
1035
 
875
1038
 
876
1039
                        p = self.server.search("UTF8", "(UNSEEN)")[1][0].split(" ")
877
1040
 
878
 
                        emails.extend([i for i in p if i != ""])
879
 
 
880
 
                        for i in emails:
881
 
                            s = self.server.fetch(i, '(BODY[HEADER.FIELDS (SUBJECT)])')[1][0]
882
 
 
883
 
                            if s is not None:
884
 
                                self.subjects.append(s[1][9:].replace("\r\n", "\n").replace("\n", ""))  # Don't ask
 
1041
                        emails = []
 
1042
                        emails.extend(i for i in p if i != "")
 
1043
                        get_subject(emails)
885
1044
 
886
1045
            @classmethod
887
1046
            def drawLoginWindow(cls, *groups):
906
1065
 
907
1066
                foldE, boxE = get_label_entry(_("Folder:"), *groups)
908
1067
                foldE.set_text("INBOX")
 
1068
                foldE.optional = True
909
1069
                alignmentE = gtk.Alignment(0.5, 0.5, 1.0, 1.0)
910
1070
                alignmentE.props.left_padding = 12
911
1071
                alignmentE.add(boxE)
915
1075
                    box.set_sensitive(widget.get_active())
916
1076
 
917
1077
                allE.connect("toggled", on_toggle, boxE)
918
 
 
919
1078
                return {"layout": vbox, "callback": cls.__submitLoginWindow,
920
 
                    "widgets": [usrE, pwdE, srvE, sslE, allE, foldE]}
 
1079
                    "widgets": [usrE, pwdE, srvE, sslE, allE, foldE],
 
1080
                    "fill-in": cls.__fillinLoginWindow}
921
1081
 
922
1082
            @staticmethod
923
 
            def __submitLoginWindow(widgets, awn):
 
1083
            def __submitLoginWindow(widgets):
924
1084
                if widgets[4].get_active():
925
1085
                    folder = widgets[5].get_text()
926
1086
 
928
1088
                        folder = "INBOX"
929
1089
                else:
930
1090
                    folder = ""
931
 
 
932
 
                return awn.keyring.new("Mail Applet - %s(%s)" \
933
 
                    % (widgets[0].get_text(), "IMAP"), \
934
 
                    widgets[1].get_text(), \
935
 
                    {"username": widgets[0].get_text(),
936
 
                    "url": widgets[2].get_text(),
937
 
                    "usessl": widgets[3].get_active(),
938
 
                    "folder": folder}, "network")
 
1091
                return {'username': widgets[0].get_text(), \
 
1092
                        'password': widgets[1].get_text(), \
 
1093
                        'url': widgets[2].get_text(), \
 
1094
                        'usessl': widgets[3].get_active(), \
 
1095
                        'folder': folder}
 
1096
 
 
1097
            @staticmethod
 
1098
            def __fillinLoginWindow(widgets, data):
 
1099
                widgets[0].set_text(data['username'])
 
1100
                widgets[1].set_text(data['password'])
 
1101
                widgets[2].set_text(data['url'])
 
1102
                widgets[3].set_active(data['usessl'])
 
1103
                if data['folder'] == "":
 
1104
                    widgets[4].set_active(False)
 
1105
                else:
 
1106
                    widgets[4].set_active(True)
 
1107
                widgets[5].set_text(data['folder'])
 
1108
 
939
1109
 
940
1110
if __name__ == "__main__":
941
1111
    awnlib.init_start(MailApplet, {
947
1117
        "author": "Pavel Panchekha",
948
1118
        "copyright-year": "2008",
949
1119
        "email": "pavpanchekha@gmail.com",
950
 
        "authors": ["onox <denkpadje@gmail.com>",
 
1120
        "authors": ["Gabor Karsay <gabor.karsay@gmx.at>",
 
1121
                    "onox <denkpadje@gmail.com>",
951
1122
                    "sharkbaitbobby <sharkbaitbobby+awn@gmail.com>",
952
1123
                    "Pavel Panchekha"]})