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