- adding some debugout witch hopefully helps to find errors with loggin in to differe...
[enigma2-plugins.git] / emailclient / src / plugin.py
1 from Components.ActionMap import ActionMap
2 from Components.GUIComponent import GUIComponent
3 from Components.HTMLComponent import HTMLComponent
4 from Components.Label import Label
5 from Components.MenuList import MenuList
6 from Components.MultiContent import MultiContentEntryText
7 from Components.Pixmap import Pixmap, MovingPixmap
8 from Components.ScrollLabel import ScrollLabel
9 from Components.config import config, ConfigSubsection, ConfigInteger, ConfigYesNo, ConfigText
10 from EmailConfig import EmailConfigScreen
11 from Plugins.Plugin import PluginDescriptor
12 from Screens.ChoiceBox import ChoiceBox
13 from Screens.Screen import Screen
14 from enigma import eListboxPythonMultiContent, eListbox, gFont
15 from twisted.mail import imap4
16 from zope.interface import implements
17 import email
18 import email.Parser
19 from TagStrip import TagStrip
20 import protocol
21 import uu
22
23
24
25
26
27 config.plugins.emailimap = ConfigSubsection()
28 config.plugins.emailimap.username = ConfigText("user", fixed_size=False)
29 config.plugins.emailimap.password = ConfigText("password", fixed_size=False)
30 config.plugins.emailimap.server = ConfigText("please.config.first", fixed_size=False)
31 config.plugins.emailimap.port = ConfigInteger(143, limits = (1, 65536))
32
33 # 0= fetch all header , 10= fetch only the last 10 headers/messages of a mailbox
34 config.plugins.emailimap.maxheadertoload = ConfigInteger(0, limits = (1, 100)) 
35
36 class EmailHandler:
37     def __init__(self):
38         pass
39     def onConnect(self, proto):
40         pass
41
42 class EmailScreen(Screen, EmailHandler):
43     implements(imap4.IMailboxListener)
44     
45     skin = """
46         <screen position="110,83" size="530,430" title="Email" >
47             <widget name="boxlist" position="0,0" size="150,400" scrollbarMode="showOnDemand" />            
48             <widget name="messagelist" position="150,0" size="380,400" scrollbarMode="showOnDemand" />            
49             <widget name="infolabel" position="0,400" size="530,30"   foregroundColor=\"white\" font=\"Regular;18\" />           
50         </screen>"""
51          
52     currentmailbox = None
53     proto = None
54     
55     def __init__(self, session, args = 0):
56         EmailHandler.__init__(self)
57         self.session = session
58         
59         self.skin = EmailScreen.skin
60         Screen.__init__(self, session)
61         protocol.createFactory(self, config.plugins.emailimap.username.value, config.plugins.emailimap.server.value, config.plugins.emailimap.port.value)
62
63         self["actions"] = ActionMap(["InfobarChannelSelection", "WizardActions", "DirectionActions", "MenuActions", "ShortcutActions", "GlobalActions", "HelpActions", "NumberActions"], 
64             {
65              "ok": self.action_ok, 
66              "back": self.action_exit, 
67              "historyNext": self.selectMessagelist, 
68              "historyBack": self.selectBoxlist, 
69              "down":        self.down, 
70              "up":          self.up, 
71              "left":        self.left, 
72              "right":       self.right, 
73              "menu":        self.action_menu, 
74              }, -1)
75         self["boxlist"] = MenuList([])
76         self["messagelist"] = MailList([])
77         self["infolabel"] = Label("")
78         self.onLayoutFinish.append(self.selectBoxlist)
79         
80     def action_menu(self):
81         self.session.open(EmailConfigScreen)
82         
83     def selectBoxlist(self):
84         self.currList = "boxlist"
85         self["boxlist"].selectionEnabled(1)
86         self["messagelist"].selectionEnabled(0)
87         
88     def selectMessagelist(self):
89         self.currList = "messagelist"
90         self["boxlist"].selectionEnabled(0)
91         self["messagelist"].selectionEnabled(1)
92     
93     def up(self):
94         self[self.currList].up()
95     
96     def down(self):
97         self[self.currList].down()
98         
99     def left(self):
100         self[self.currList].pageUp()
101     
102     def right(self):
103         self[self.currList].pageDown()
104     
105     def action_ok(self):
106         if self.currList == "boxlist":
107             self.onBoxSelected()
108         else:
109             self.onMessageSelected()
110         
111     def onBoxSelected(self):
112         c = self["boxlist"].getCurrent()
113         if c is not None:
114             self.proto.examine(UTF7toUTF8(c[1][2])
115                                ).addCallback(self.onExamine, c[0] , self.proto
116                               ).addErrback(self.onExamineFailed, self.proto
117                               )
118     
119     def onMessageSelected(self):
120         c = self["messagelist"].getCurrent()
121         if c is not None:
122             self.fetchMessageSize(c[0])
123             
124     def fetchMessageSize(self, message):
125         print "fetchMessageSize",message
126         self.proto.fetchSize(message.uid
127             ).addCallback(self.onMessageSizeLoaded, message, self.proto
128             ).addErrback(self.onMessageLoadFailed, message, self.proto
129             )
130             
131     def onMessageSizeLoaded(self, result, message, proto):
132         print "onMessageSizeLoaded", result, message
133         size = int(result[message.uid]['RFC822.SIZE'])
134         self.MAX_MESSAGE_SIZE_TO_OPEN = 4000000
135         if size >= self.MAX_MESSAGE_SIZE_TO_OPEN:
136             #ask here to open message
137             print "message to large to open (size=", size, ")"
138         else:
139             self.loadMessage(message)
140         
141 #    def fetchBodyStructure(self, message):
142 #        print "fetchBodyStructure",message
143 #        self.proto.fetchBodyStructure(message.uid
144 #            ).addCallback(self.onBodystructureLoaded, message, self.proto
145 #            ).addErrback(self.onMessageLoadFailed, message, self.proto
146 #            )
147     
148     def loadMessage(self, message):
149         print "loadMessage",message
150         self["infolabel"].setText("loading message")
151         
152         self.proto.fetchMessage(message.uid
153             ).addCallback(self.onMessageLoaded, message, self.proto
154             ).addErrback(self.onMessageLoadFailed, message, self.proto
155             )
156     
157     def onMessageLoaded(self, result, message, proto):
158         self["infolabel"].setText("parsing message")
159         print "onMessageLoaded"#,result,message
160         msgstr = result[message.uid]['RFC822']
161         msg = email.Parser.Parser().parsestr(msgstr)
162         msg.messagebodys = []
163         msg.attachments = []
164         
165         if msg.is_multipart():
166             for part in msg.walk():
167                 if part.get_content_maintype()=="multipart":
168                     continue
169                 if part.get_content_maintype() == 'text' and part.get_filename() is None:
170                     if part.get_content_subtype() == "html":
171                         msg.messagebodys.append(EmailBody(part))
172                     elif part.get_content_subtype() == "plain":
173                         msg.messagebodys.append(EmailBody(part))
174                     else:
175                         print "unkown content type= ", part.get_content_maintype(), "/", part.get_content_subtype()
176                 else:
177                      print "found Attachment with  ", part.get_content_type(), "and name", part.get_filename()
178                      msg.attachments.append(EmailAttachment(part.get_filename(), part.get_content_type(), part.get_payload()))    
179         else:
180             msg.messagebodys.append(EmailBody(msg))
181         self.session.open(ScreenMailView, msg)
182             
183     def onMessageLoadFailed(self, failure, message, proto):
184         print "onMessageLoadFailed", failure, message
185         self["infolabel"].setText(failure.getErrorMessage())
186
187     def action_exit(self):
188         if self.proto is not None:
189             self.proto.logout(
190                             ).addCallback(self.onLogedOut, self.proto
191                 ).addErrback(self.onLogedOut, self.proto
192                             )
193         else:
194             self.close()
195     
196     def onLogedOut(self, result, proto):
197         print "onLogedOut", result
198         self.close()
199         
200             
201     def onConnect(self, proto):
202         self["infolabel"].setText("connected")
203         proto.getCapabilities(
204                         ).addCallback(self.cbCapabilities, proto
205                         ).addErrback(self.ebCapabilities, proto
206                         )
207
208     def cbCapabilities(self,reason,proto):
209         print "cbCapabilities",reason
210         
211         self.doLogin(proto)
212
213     def ebCapabilities(reason,proto):
214         print "ebCapabilities",reason
215         
216     def onConnectFailed(self, reason):
217         self["infolabel"].setText(reason.getErrorMessage())
218
219     def onAuthentication(self, result, proto):
220         self.proto = proto
221         self["infolabel"].setText("logged in")
222         proto.list("", "*"
223         ).addCallback(self.onMailboxList, proto
224         )
225         
226     def doLogin(self, proto):
227         print "login secure"
228         useTLS = False #True
229         if useTLS:
230             context = proto.context.getContext()
231             d = proto.startTLS(context)
232             d = d.addCallback(proto.authenticate, config.plugins.emailimap.password.value)
233         else:
234             d = proto.authenticate(config.plugins.emailimap.password.value)
235         d.addCallback(self.onAuthentication, proto)
236         d.addErrback(self.onAuthenticationFailed, proto)
237         return d
238
239     def onAuthenticationFailed(self, failure, proto):
240         # If it failed because no SASL mechanisms match
241         print "onAuthenticationFailed", failure, proto
242         self["infolabel"].setText(failure.getErrorMessage())
243         try:
244             failure.trap(imap4.NoSupportedAuthentication)
245             self.doLoginInsecure(proto)
246         except Exception,e:
247             print e,e.message
248             
249     def doLoginInsecure(self, proto):
250         print "login INSECURE"
251         proto.login(config.plugins.emailimap.username.value, config.plugins.emailimap.password.value
252                 ).addCallback(self.onAuthentication, proto
253                 ).addErrback(self.onInsecureAuthenticationFailed, proto
254                 )
255     
256     def onInsecureAuthenticationFailed(self, failure, proto):
257         print "onInsecureAuthenticationFailed", failure, proto
258         self["infolabel"].setText(failure.getErrorMessage())
259         
260     def onMailboxList(self, result, proto):
261         print "onMailboxList", result, proto
262         list = []
263         for i in result:
264             flags, hierarchy_delimiter, name = i
265             list.append((UTF7toUTF8(name).encode('utf-8'), i))
266         self["boxlist"].l.setList(list) 
267         
268     def onExamine(self, result, mboxname, proto):
269         print "onExamine", result, mboxname
270         self.setTitle("Mailbox: "+mboxname)
271         self.currentmailbox = mboxname
272         numMessagesinFolder = int(result['EXISTS'])
273         if numMessagesinFolder <= 0:
274             self["infolabel"].setText("Box '"+mboxname+"' is empty")
275             self["messagelist"].l.setList([]) 
276         
277         else:
278             if config.plugins.emailimap.maxheadertoload.value > 0:
279                 maxMessagesToFetch = config.plugins.emailimap.maxheadertoload.value
280                 startmsg = numMessagesinFolder-maxMessagesToFetch+1
281                 if startmsg <= 0:
282                     startmsg = 1
283                 rangeToFetch = [startmsg, numMessagesinFolder]
284             else:
285                 rangeToFetch = [1, numMessagesinFolder]
286             self["infolabel"].setText("loading headers "+str(rangeToFetch[0])+"-"+str(rangeToFetch[1])+" of Box '"+mboxname+"'")
287             
288             try:
289 #                proto.fetchEnvelope('%i:%i'%(rangeToFetch[0], rangeToFetch[1])    #'1:*'
290 #                           ).addCallback(self.onEnvelopeList, proto
291 #                           )
292                 proto.fetchHeaders('%i:%i'%(rangeToFetch[0], rangeToFetch[1])    #'1:*'
293                            ).addCallback(self.onHeaderList, proto
294                            )
295
296             except imap4.IllegalServerResponse, e:
297                 print e
298                 
299     def onExamineFailed(self, failure, proto):
300         print "onExamineFailed", failure, proto
301         self["infolabel"].setText(failure.getErrorMessage())
302
303     def onHeaderList(self, result, proto):
304         print "onHeaderList"#,result,proto
305         self["infolabel"].setText("headers loaded, now parsing ...")
306         list = []
307         for m in result:
308             try:
309                 list.append(self.buildMessageListItem(MessageHeader(m, result[m]['RFC822.HEADER'])))
310             except Exception,e:
311                 print e
312         list.reverse()
313         self["messagelist"].l.setList(list) 
314         self["infolabel"].setText("have "+str(len(result))+" messages ")
315             
316     def buildMessageListItem(self, message):
317         res = [ message ]
318         res.append(MultiContentEntryText(pos=(5, 0), size=(380, 19), font=0, text=message.getSenderString()))
319         res.append(MultiContentEntryText(pos=(5, 19), size=(380, 19), font=0, text=message.get('date', default='kein Datum')))
320         res.append(MultiContentEntryText(pos=(5, 38), size=(380, 19), font=0, text=message.getSubject()))
321         return res  
322     #
323     # IMailboxListener methods
324     #
325     def modeChanged(self, writeable):
326         print "modeChanged", writeable
327
328     def flagsChanged(self, newFlags):
329         print "flagsChanged", newFlags
330
331     def newMessages(self, exists, recent):
332         print "newMessages", exists, recent
333       
334 class ScreenMailView(Screen):
335     skin=""
336     def __init__(self, session, email, args = 0):
337         self.session = session
338         self.email = email
339         self.skin = "<screen position=\"85,80\" size=\"550,476\" title=\"view Email\" >"
340         self.skin +=  """<widget name="from" position="0,0" size="550,25"  font="Regular;20" />
341             <widget name="date" position="0,25" size="550,25"  font="Regular;20" />
342             <widget name="subject" position="0,50" size="550,25"  font="Regular;20" />
343             <widget name="body" position="0,75" size="550,375"  font="Regular;20" />
344             <widget name="buttonred" position="10,436" size="100,30" backgroundColor="red" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/> 
345             <widget name="buttongreen" position="120,436" size="100,30" backgroundColor="green" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/> 
346             <widget name="buttonyellow" position="230,436" size="100,30" backgroundColor="yellow" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/> 
347             <widget name="buttonblue" position="340,436" size="100,30" backgroundColor="blue" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/>            
348         </screen>""" 
349         Screen.__init__(self, session)
350         self["from"] = Label(_("From: %s" %self.email.get('from', 'no-from')))
351         self["date"] = Label(_("Date: %s" %self.email.get('date', 'no-date')))
352         self["subject"] = Label(_(self.email.get('subject', 'no-subject')))
353         self["body"] = ScrollLabel(_(self.email.messagebodys[0].getData()))
354         self["buttonred"] = Label("")
355         self["buttongreen"] = Label("")
356         self["buttonyellow"] =  Label("Headers")
357         self["buttonblue"] =  Label(_("delete"))
358         self["actions"] = ActionMap(["WizardActions", "DirectionActions", "MenuActions", "ShortcutActions"], 
359             {
360              "back": self.close, 
361              "up": self["body"].pageUp, 
362              "down": self["body"].pageDown, 
363              "left": self["body"].pageUp, 
364              "right": self["body"].pageDown, 
365              "red": self.selectBody, 
366              "green": self.selectAttachment, 
367              "yellow": self.openMessagesHeaders, 
368              "blue": self.delete, 
369              
370              }, -1)
371         self.onLayoutFinish.append(self.updateButtons)
372         
373     def delete(self):
374         pass #self.session.openWithCallback(self.deleteCB, ChoiceBox, title="really delete Mail?", list=[(_("yes"), True),(_("no"), False)])
375
376     def deleteCB(self, returnValue):
377         if returnValue[1] is True:
378             pass
379         
380     def openMessagesHeaders(self):
381         pass #self.session.open(ScreenMailViewHeader,self.profil,self.email)
382                    
383     def updateButtons(self):
384         self["buttonred"].setText(_("Bodys"))
385         if len(self.email.attachments):
386             self["buttongreen"].setText("Attachments")
387         else:
388             self["buttongreen"].setText("")  
389     
390     def selectBody(self):
391         if len(self.email.messagebodys):
392             list = []
393             for a in self.email.messagebodys:
394                 list.append((a.getContenttype(), a))
395             self.session.openWithCallback(self.selectBodyCB, ChoiceBox, _("select Body"), list)
396             
397     def selectBodyCB(self, choice):
398         if choice is not None:
399            self["body"].setText(choice[1].getData())
400             
401     def selectAttachment(self):
402         if len(self.email.attachments):
403             list = []
404             for a in self.email.attachments:
405                 list.append((a.getFilename(), a))
406             self.session.openWithCallback(self.selectAttachmentCB, ChoiceBox, _("select Attachment"), list)
407             
408     def selectAttachmentCB(self, choice):
409         if choice is not None:
410             print "Attachment selected", choice[1].getFilename()
411             #showMessageBox(self.session)
412     
413 class MailList(MenuList, HTMLComponent, GUIComponent):
414     def __init__(self, list):
415         MenuList.__init__(self, list)
416         GUIComponent.__init__(self)
417         self.l = eListboxPythonMultiContent()
418         self.list = list
419         self.l.setList(list)
420         self.l.setFont(0, gFont("Regular", 18))
421         self.l.setFont(1, gFont("Regular", 20))
422         
423     GUI_WIDGET = eListbox
424
425     def postWidgetCreate(self, instance):
426         instance.setContent(self.l)
427         instance.setItemHeight(60)
428
429 class MessageHeader(object):        
430     def __init__(self, uid, message):
431         self.uid = uid #must be int
432         self.message = email.Parser.Parser().parsestr(message)
433         
434     def getSenderString(self):
435         if self.get("from") is None:
436             return "no sender"
437         else:
438             return self.get("from")
439         
440     def getSubject(self):
441         if self.get("subject") is None:
442             return "no subject"
443             """
444         # need some decoding for subjects like =?iso-8859-1?Q?=DCbermut?=
445         elif self.get("subject").startswith("=?") and self.get("subject").endswith("?="):
446             c = self.get("subject").split("?")
447             print c
448             codec = c[1]
449             text = "?".join(c[3:])
450             
451             
452             print "CODEC",codec
453             print "TEXT",text
454             return text.decode(codec).encode("utf-8")
455             """
456         else:
457             return self.get("subject")
458             
459     def get(self, key, default=None):
460         return self.message.get(key,failobj=default)    
461         
462     def __str__(self):
463         return "<MessageHeader uid="+str(self.uid)+", subject="+self.get("subject","no-subject")+">"
464     
465 ############
466 class EmailBody:
467     def __init__(self,data):
468         self.data = data
469     
470     def getEncoding(self):
471         return self.data.get_content_charset()
472     
473     def getData(self):
474         text = self.data.get_payload(decode=True)
475         if self.getEncoding() is not None:
476             print "decoding text with charset ",self.getEncoding()
477             text = text.decode(self.getEncoding())            
478         if self.getContenttype() == "text/html":
479             print "stripping html"
480             text = TagStrip().strip_readable(text)
481         try:
482             return text.encode('utf-8')
483         except Exception,e:
484             print e
485             return text
486     
487     def getContenttype(self):
488         return self.data.get_content_type()
489
490 ############
491 class EmailAttachment:
492     def __init__(self, filename, contenttype, data):
493         self.filename = filename
494         self.contenttype = contenttype
495         self.data = data
496         
497     def save(self,folder):
498         try:
499             fp = open(folder+"/"+self.getFilename(),"wb")
500             fp.write(self.data)
501             fp.close()
502         except Exception,e:
503             print e
504             return False
505         return True
506             
507     def getFilename(self):
508         return self.filename 
509     
510     def getContenttype(self):
511         return self.contenttype 
512     
513     def getData(self):
514         return self.data
515     
516 def UTF7toUTF8(str):
517     return imap4.decoder(str)[0]
518
519 def UTF8toUTF7(str):
520     return imap4.encoder(str.decode('utf-8'))[0]
521
522 def main(session, **kwargs):
523     session.open(EmailScreen)    
524
525 def Plugins(path, **kwargs):
526     global plugin_path
527     plugin_path = path
528     return [
529              PluginDescriptor(name="Email Client", description="view Emails via IMAP4", 
530              where = PluginDescriptor.WHERE_PLUGINMENU, 
531              fnc = main,
532              icon="plugin.png"
533              ), 
534              #PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = startScrobbler)
535         ]