better error handling, does not crash any more on loss of connection to mail server
[enigma2-plugins.git] / emailclient / src / plugin.py
1 from Components.ActionMap import ActionMap
2 from Components.Label import Label
3 from Screens.MessageBox import MessageBox
4 from Components.MenuList import MenuList
5 from Components.MultiContent import MultiContentEntryText
6 from Components.ScrollLabel import ScrollLabel
7 from Components.Button import Button
8 from Components.config import config, ConfigSubsection, ConfigInteger, ConfigText, ConfigEnableDisable
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 Tools import Notifications
14 from enigma import eListboxPythonMultiContent, gFont, eTimer
15 from twisted.mail import imap4
16 from zope.interface import implements
17 import email, time
18 from email.header import decode_header
19 from TagStrip import strip_readable
20 from protocol import createFactory
21
22 from . import _, initLog, debug
23
24 config.plugins.emailimap = ConfigSubsection()
25 config.plugins.emailimap.username = ConfigText("user", fixed_size=False)
26 config.plugins.emailimap.password = ConfigText("password", fixed_size=False)
27 config.plugins.emailimap.server = ConfigText("please.config.first", fixed_size=False)
28 config.plugins.emailimap.port = ConfigInteger(default=143, limits = (1, 65536))
29 config.plugins.emailimap.showDeleted = ConfigEnableDisable(default=False)
30 config.plugins.emailimap.checkForNewMails = ConfigEnableDisable(default=True)
31 config.plugins.emailimap.checkPeriod = ConfigInteger(default=60, limits=(0, 900)) #  in minutes
32 config.plugins.emailimap.timeout = ConfigInteger(default=0, limits=(0, 90)) # in seconds
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 config.plugins.emailimap.debug = ConfigEnableDisable(default=False)
36
37 from enigma import getDesktop
38 DESKTOP_WIDTH = getDesktop(0).size().width()
39 DESKTOP_HEIGHT = getDesktop(0).size().height()
40 #
41 # this is pure magic.
42 # It returns the first value, if HD (1280x720),
43 # the second if SD (720x576),
44 # else something scaled accordingly
45 # if one of the parameters is -1, scale proportionally
46 #
47 def scaleH(y2, y1):
48         if y2 == -1:
49                 y2 = y1*1280/720
50         elif y1 == -1:
51                 y1 = y2*720/1280
52         return scale(y2, y1, 1280, 720, DESKTOP_WIDTH)
53 def scaleV(y2, y1):
54         if y2 == -1:
55                 y2 = y1*720/576
56         elif y1 == -1:
57                 y1 = y2*576/720
58         return scale(y2, y1, 720, 576, DESKTOP_HEIGHT)
59 def scale(y2, y1, x2, x1, x):
60         return (y2 - y1) * (x - x1) / (x2 - x1) + y1
61
62 def decodeHeader(text, default=''):
63         if text is None:
64                 return _(default)
65         text = text.replace('\r',' ').replace('\n',' ').replace('\t',' ')
66         while text.find('  ') != -1:
67                 text = text.replace('  ',' ')
68         textNew = ""
69         for part in decode_header(text):
70                 (content, charset) = part
71                 # print("decodeHeader content/charset: %s/%s" %(repr(content),charset))
72                 if charset:
73                         textNew += content.decode(charset)
74                 else:
75                         textNew += content
76         try:
77                 return textNew.encode('utf-8')
78         except: # for faulty mail software systems
79                 return textNew.decode('iso-8859-1').encode('utf-8')
80
81 IS_UNSEEN = 0
82 IS_SEEN = 1
83 IS_DELETED = 2 
84
85 class EmailHandler:
86         def __init__(self):
87                 pass
88         def onConnect(self, proto):
89                 pass
90
91 class EmailScreen(Screen, EmailHandler):
92         implements(imap4.IMailboxListener)
93
94         width = scaleH(-1,530)
95         height = scaleV(-1,430)
96         boxlistWidth = scaleH(-1,150)
97         messagelistWidth = width-boxlistWidth
98         infolabelHeight = scaleV(-1,30)
99         skin = """
100                 <screen position="%d,%d" size="%d,%d" title="Email" >
101                         <widget name="boxlist" position="0,0" size="%d,%d" scrollbarMode="showOnDemand" />
102                         <widget name="messagelist" position="%d,%d" size="%d,%d" scrollbarMode="showOnDemand" />
103                         <widget name="infolabel" position="%d,%d" size="%d,%d"   foregroundColor=\"white\" font=\"Regular;%d\" />
104                 </screen>""" %(
105                                            (DESKTOP_WIDTH-width)/2, (DESKTOP_HEIGHT-height)/2, width, height,
106                                            boxlistWidth, height-infolabelHeight,
107                                            boxlistWidth, 0, messagelistWidth, height-infolabelHeight,
108                                            0, height-infolabelHeight, width, infolabelHeight, scaleV(20,18)
109                                            )
110
111         currentmailbox = None
112         proto = None
113
114         def __init__(self, session, args = 0):
115                 EmailHandler.__init__(self)
116                 self.session = session
117
118                 self.skin = EmailScreen.skin
119                 Screen.__init__(self, session)
120                 self.factory = createFactory(self, config.plugins.emailimap.username.value, config.plugins.emailimap.server.value, config.plugins.emailimap.port.value)
121
122                 self["actions"] = ActionMap(["InfobarChannelSelection", "WizardActions", "DirectionActions", "MenuActions", "ShortcutActions", "GlobalActions", "HelpActions", "NumberActions", "ChannelSelectBaseActions"],
123                         {
124                          "ok": self.action_ok,
125                          "back": self.action_exit,
126                          "historyNext": self.selectMessagelist,
127                          "historyBack": self.selectBoxlist,
128                          "nextBouquet": self.selectMessagelist,
129                          "prevBouquet": self.selectBoxlist,
130                          "down":                self.down,
131                          "up":            self.up,
132                          "left":                self.left,
133                          "right":          self.right,
134                          "menu":                self.action_menu,
135                          }, -1)
136                 self["boxlist"] = MenuList([])
137                 self["messagelist"] = MailList([])
138                 self["infolabel"] = Label("")
139                 self.onLayoutFinish.append(self.selectBoxlist)
140
141         def action_menu(self):
142                 self.session.open(EmailConfigScreen).onHide.append(self.onBoxSelected)
143
144         def selectBoxlist(self):
145                 self.currList = "boxlist"
146                 self["boxlist"].selectionEnabled(1)
147                 self["messagelist"].selectionEnabled(0)
148
149         def selectMessagelist(self):
150                 self.currList = "messagelist"
151                 self["boxlist"].selectionEnabled(0)
152                 self["messagelist"].selectionEnabled(1)
153
154         def up(self):
155                 self[self.currList].up()
156
157         def down(self):
158                 self[self.currList].down()
159
160         def left(self):
161                 self[self.currList].pageUp()
162
163         def right(self):
164                 self[self.currList].pageDown()
165
166         def action_ok(self):
167                 if self.currList == "boxlist":
168                         self.onBoxSelected()
169                 else:
170                         self.onMessageSelected()
171
172         def onBoxSelected(self):
173                 c = self["boxlist"].getCurrent()
174                 if c is not None:
175                         self.proto.select(UTF7toUTF8(c[1][2]) # select instead of examine to get write access
176                                                            ).addCallback(self.onExamine, c[0] , self.proto
177                                                           ).addErrback(self.onExamineFailed, c[0], self.proto
178                                                           )
179                 # self.proto.search(imap4.Query(unseen=1)).addCallback(self.cbOk).addErrback(self.cbNotOk)
180
181         def onMessageSelected(self):
182                 c = self["messagelist"].getCurrent()
183                 if c is not None:
184                         self.fetchMessageSize(c[0])
185
186         def fetchMessageSize(self, message):
187                 debug("[EmailScreen] fetchMessageSize: " + str(message))
188                 self.proto.fetchSize(message.uid
189                         ).addCallback(self.onMessageSizeLoaded, message, self.proto
190                         ).addErrback(self.onMessageLoadFailed, message, self.proto
191                         )
192
193         def onMessageSizeLoaded(self, result, message, proto):
194                 debug("[EmailScreen] onMessageSizeLoaded: " + str(result) + ' ' + str(message))
195                 size = int(result[message.uid]['RFC822.SIZE'])
196                 self.MAX_MESSAGE_SIZE_TO_OPEN = 4000000
197                 if size >= self.MAX_MESSAGE_SIZE_TO_OPEN:
198                         #ask here to open message
199                         debug("message to large to open (size=%d" %size)
200                 else:
201                         self.loadMessage(message)
202
203 #       def fetchBodyStructure(self, message):
204 #               print "fetchBodyStructure",message
205 #               self.proto.fetchBodyStructure(message.uid
206 #                       ).addCallback(self.onBodystructureLoaded, message, self.proto
207 #                       ).addErrback(self.onMessageLoadFailed, message, self.proto
208 #                       )
209
210         def loadMessage(self, message):
211                 debug("[EmailScreen] loadMessage: " + str(message))
212                 self["infolabel"].setText(_("loading message"))
213
214                 self.proto.fetchMessage(message.uid
215                         ).addCallback(self.onMessageLoaded, message, self.proto
216                         ).addErrback(self.onMessageLoadFailed, message, self.proto
217                         )
218
219         def onMessageLoaded(self, result, message, proto):
220                 self["infolabel"].setText(_("parsing message"))
221                 debug("[EmailScreen] onMessageLoaded") #,result,message
222                 try:
223                         msgstr = result[message.uid]['RFC822']
224                 except KeyError:
225                         self.loadMessage(message)
226                         return
227                 msg = email.Parser.Parser().parsestr(msgstr)
228                 msg.messagebodys = []
229                 msg.attachments = []
230
231                 if msg.is_multipart():
232                         for part in msg.walk():
233                                 if part.get_content_maintype()=="multipart":
234                                         continue
235                                 if part.get_content_maintype() == 'text' and part.get_filename() is None:
236                                         if part.get_content_subtype() == "html":
237                                                 msg.messagebodys.append(EmailBody(part))
238                                         elif part.get_content_subtype() == "plain":
239                                                 msg.messagebodys.append(EmailBody(part))
240                                         else:
241                                                 debug("[EmailScreen] unkown content type= " + part.get_content_maintype() + "/", part.get_content_subtype())
242                                 else:
243                                         debug("[EmailScreen] found Attachment with  " + part.get_content_type() + " and name " + part.get_filename())
244                                         msg.attachments.append(EmailAttachment(part.get_filename(), part.get_content_type(), part.get_payload()))
245                 else:
246                         msg.messagebodys.append(EmailBody(msg))
247                 self.session.open(ScreenMailView, msg, message.uid, proto, self.flagsList[message.uid]['FLAGS']).onHide.append(self.onBoxSelected)
248
249         def onMessageLoadFailed(self, failure, message, proto):
250                 debug("[EmailScreen] onMessageLoadFailed: " + str(failure) + ' ' + str(message))
251                 self["infolabel"].setText(_("failed to load message") + ': ' + failure.getErrorMessage())
252
253         def action_exit(self):
254                 global mailChecker
255                 if not mailChecker and self.proto is not None:
256                         self.proto.logout().addCallback(self.onLogedOut, self.proto).addErrback(self.onLogedOut, self.proto)
257                 else:
258                         self.factory.stopFactory()
259                         self.close()
260
261         def onLogedOut(self, result, proto):
262                 debug("[EmailScreen] onLogedOut: " + str(result))
263                 self.factory.stopFactory()()
264                 self.close()
265
266         def onConnect(self, proto):
267                 self["infolabel"].setText(_("connected"))
268                 proto.getCapabilities(
269                                                 ).addCallback(self.cbCapabilities, proto
270                                                 ).addErrback(self.ebCapabilities, proto
271                                                 )
272                 self.factory.resetDelay()
273
274         def cbCapabilities(self,reason,proto):
275                 debug("[EmailScreen] \n\
276 ####################################################################################################\n\
277 # If you have problems to log into your imap-server, please send me the output of the following line\n\
278 # cbCapabilities: " + str(reason) +"\n\
279 ####################################################################################################\n")
280                 self.doLogin(proto)
281
282         def ebCapabilities(self,reason,proto):
283                 debug("[EmailScreen] ebCapabilities: " + str(reason))
284
285         def onConnectFailed(self, reason):
286                 debug("[EmailScreen] onConnectFailed: " + reason.getErrorMessage())
287                 if self.has_key('infolabel'):
288                         self["infolabel"].setText(_("connection to %(server)s:%(port)d failed") %{'server':config.plugins.emailimap.server.value,'port':config.plugins.emailimap.port.value}) # + ': ' + reason.getErrorMessage()) # the messages provided by twisted are crap here
289                 self.action_exit()
290
291         def onAuthentication(self, result, proto):
292                 self.proto = proto
293                 self["infolabel"].setText(_("logged in"))
294                 # better use LSUB here to get only the subscribed to mailboxes
295                 proto.lsub("", "*").addCallback(self.onMailboxList, proto)
296
297         def doLogin(self, proto):
298                 debug("[EmailScreen] login secure")
299                 useTLS = False #True
300                 if useTLS:
301                         context = proto.context.getContext()
302                         d = proto.startTLS(context)
303                         d = d.addCallback(proto.authenticate, config.plugins.emailimap.password.value)
304                 else:
305                         d = proto.authenticate(config.plugins.emailimap.password.value)
306                 d.addCallback(self.onAuthentication, proto)
307                 d.addErrback(self.onAuthenticationFailed, proto)
308                 return d
309
310         def onAuthenticationFailed(self, failure, proto):
311                 # If it failed because no SASL mechanisms match
312                 debug("[EmailScreen] onAuthenticationFailed: " + failure.getErrorMessage())
313                 self["infolabel"].setText(_("encrypted login failed, trying without encryption"))
314                 try:
315                         failure.trap(imap4.NoSupportedAuthentication)
316                         self.doLoginInsecure(proto)
317                 except Exception,e:
318                         print e,e.message
319
320         def doLoginInsecure(self, proto):
321                 debug("[EmailScreen] login INSECURE")
322                 proto.login(config.plugins.emailimap.username.value, config.plugins.emailimap.password.value
323                                 ).addCallback(self.onAuthentication, proto
324                                 ).addErrback(self.onInsecureAuthenticationFailed, proto
325                                 )
326
327         def onInsecureAuthenticationFailed(self, failure, proto):
328                 debug("[EmailScreen] onInsecureAuthenticationFailed: " + failure.getErrorMessage())
329                 self["infolabel"].setText(_("login failed") + ': ' + failure.getErrorMessage())
330
331         def onMailboxList(self, result, proto):
332                 debug("[EmailScreen] onMailboxList: " + str(result) + ' ' + str(proto))
333                 list = []
334                 inboxPos = 0
335                 for i in result:
336                         flags, hierarchy_delimiter, name = i #@UnusedVariable
337                         list.append((UTF7toUTF8(name).encode('utf-8'), i))
338                         if name.lower() == 'inbox':
339                                 inboxPos = len(list)
340                 self["boxlist"].setList(list)
341                 self["boxlist"].moveToIndex(inboxPos-1)
342
343         def onExamine(self, result, mboxname, proto):
344                 debug("[EmailScreen] onExamine: " + str(result) + ' ' + mboxname)
345                 self.setTitle(_("Mailbox")+": "+mboxname)
346                 self.currentmailbox = mboxname
347                 numMessagesinFolder = int(result['EXISTS'])
348                 if numMessagesinFolder <= 0:
349                         self["infolabel"].setText(_("Box '%s' is empty") %(mboxname))
350                         self["messagelist"].l.setList([])
351
352                 else:
353                         if config.plugins.emailimap.maxheadertoload.value > 0:
354                                 maxMessagesToFetch = config.plugins.emailimap.maxheadertoload.value
355                                 startmsg = numMessagesinFolder-maxMessagesToFetch+1
356                                 if startmsg <= 0:
357                                         startmsg = 1
358                                 rangeToFetch = [startmsg, numMessagesinFolder]
359                         else:
360                                 rangeToFetch = [1, numMessagesinFolder]
361                         self["infolabel"].setText(_("loading headers %(from)d-%(to)d of Box '%(name)s'") % {'from': rangeToFetch[0], 'to': rangeToFetch[1], 'name': mboxname})
362
363                         try:
364 #                               proto.fetchEnvelope('%i:%i'%(rangeToFetch[0], rangeToFetch[1])  #'1:*'
365 #                                                  ).addCallback(self.onnvelopeList, proto
366 #                                                  )
367                                 self.proto = proto
368                                 self.rangeToFetch = rangeToFetch
369                                 proto.fetchFlags('%i:%i'%(rangeToFetch[0], rangeToFetch[1])     #'1:*'
370                                                    ).addCallback(self.onFlagsList)
371
372                         except imap4.IllegalServerResponse, e:
373                                 debug("[EmailScreen] onExamine exception: " + str(e))
374                         self.selectMessagelist()
375
376         def onFlagsList(self, result):
377                 self.flagsList = result
378                 self.proto.fetchHeaders('%i:%i'%(self.rangeToFetch[0], self.rangeToFetch[1])    #'1:*'
379                                    ).addCallback(self.onHeaderList, self.proto
380                                    )
381
382         def onExamineFailed(self, failure, mboxname, proto):
383                 debug("[EmailScreen] onExamineFailed: " + mboxname + ' ' + str(failure) + ' ' + str(proto))
384                 self["infolabel"].setText(_("cannot access mailbox '%(mboxname)s'") % {'mboxname':mboxname})
385
386         def cbOk(self, result):
387                 debug("[EmailScreen] cbOk result: %s" %repr(result))
388
389         def cbNotOk(self, result):
390                 debug("[EmailScreen] cbNotOk result: %s" %(str(result)))
391
392         def onHeaderList(self, result, proto):
393                 debug("[EmailScreen] onHeaderList") #,result,proto
394                 self["infolabel"].setText(_("headers loaded, now parsing ..."))
395                 list = []
396                 for m in result:
397                         state = IS_UNSEEN
398                         # debug("onHeaderList :" + repr(self.flagsList[m]['FLAGS']))
399                         if '\\Seen' in self.flagsList[m]['FLAGS']:
400                                 state = IS_SEEN
401                         if '\\Deleted' in self.flagsList[m]['FLAGS']:
402                                 if not config.plugins.emailimap.showDeleted.value:
403                                         continue
404                                 else:
405                                         state = IS_DELETED
406                         try:
407                                 list.append(self.buildMessageListItem(MessageHeader(m, result[m]['RFC822.HEADER']), state))
408                         except Exception,e:
409                                 try:
410                                         list.append(self.buildMessageListItem(MessageHeader(m, result[m]['RFC822.HEADER'].decode('iso8859-1', 'replace'), state)))
411                                 except:
412                                         # this appear to be errors in the formatting of the mail itself...
413                                         debug("[EmailScreen] onHeaderList error: %s (%s)" %(result[m]['RFC822.HEADER'], str(e)))
414                 if list:
415                         list.reverse()
416                         self["messagelist"].l.setList(list)
417                         self["infolabel"].setText(_("have %d messages") %(len(list)))
418                 else:
419                         self["messagelist"].l.setList([])
420                         self["infolabel"].setText(_("have no messages"))
421                         # self.onBoxSelected() # brings us into endless loop, when still deleted messages are in there...
422                         self.selectBoxlist()
423
424         def buildMessageListItem(self, message, state):
425                 if state == IS_UNSEEN:
426                         font = 0
427                         color = 0x00FFFFFF # white
428                 elif state == IS_DELETED:
429                         font = 1 
430                         color = 0x00FF6666 # redish :)
431                 else:
432                         font = 2
433                         color = 0x00888888 # grey
434                 return [
435                         message,
436                         MultiContentEntryText(pos=(5, 0), size=(self.messagelistWidth, scaleV(20,18)+5), font=font, text=message.getSenderString(), color=color, color_sel=color),
437                         MultiContentEntryText(pos=(5, scaleV(20,18)+1), size=(self.messagelistWidth, scaleV(20,18)+5), font=font, text=message.get('date', default=_('no date')), color=color, color_sel=color),
438                         MultiContentEntryText(pos=(5, 2*(scaleV(20,18)+1)), size=(self.messagelistWidth, scaleV(20,18)+5), font=font, text=message.getSubject(), color=color, color_sel=color)
439                 ]
440         #
441         # IMailboxListener methods
442         #
443         def modeChanged(self, writeable):
444                 debug("[EmailScreen] modeChanged: " + str(writeable))
445
446         def flagsChanged(self, newFlags):
447                 debug("[EmailScreen] flagsChanged: " + str(newFlags))
448
449         def newMessages(self, exists, recent):
450                 debug("[EmailScreen] newMessages: " + str(exists) + ' ' +  str(recent))
451
452 class ScreenMailView(Screen):
453         skin=""
454         def __init__(self, session, email, uid, proto, flags):
455                 self.session = session
456                 self.email = email
457                 # debug('ScreenMailView ' + repr(email) + ' dir: ' + repr(dir(email)))
458                 width = max(4*140,scaleH(-1,550))
459                 height = scaleV(-1,476)
460                 fontSize = scaleV(24,20)
461                 lineHeight = fontSize+5
462                 buttonsGap = (width-4*140)/5
463                 self.skin = """
464                 <screen position="%d,%d" size="%d,%d" title="view Email" >
465                         <widget name="from" position="%d,%d" size="%d,%d"  font="Regular;%d" />
466                         <widget name="date" position="%d,%d" size="%d,%d"  font="Regular;%d" />
467                         <widget name="subject" position="%d,%d" size="%d,%d"  font="Regular;%d" />
468                         <eLabel position="%d,%d" size="%d,2" backgroundColor="#aaaaaa" />
469                         <widget name="body" position="%d,%d" size="%d,%d"  font="Regular;%d" />
470                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
471                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
472                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
473                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
474                         <widget name="buttonred" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
475                         <widget name="buttongreen" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
476                         <widget name="buttonyellow" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
477                         <widget name="buttonblue" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
478                 </screen>""" %(
479                                            (DESKTOP_WIDTH-width)/2, (DESKTOP_HEIGHT-height)/2, width, height,
480                                            0, 0, width, lineHeight, fontSize-1, # from
481                                            0, lineHeight, width, lineHeight, fontSize-1, # date
482                                            0, 2*lineHeight, width, lineHeight, fontSize-1, # subject 
483                                            0, 3*lineHeight+1, width, # line 
484                                            0, 3*lineHeight+5, width, height-3*lineHeight-5-5-30-5, fontSize, # body
485                                            buttonsGap, height-30-5,
486                        2*buttonsGap+140, height-30-5,
487                        3*buttonsGap+2*140, height-30-5,
488                        4*buttonsGap+3*140, height-30-5,
489                        buttonsGap, height-30-5, scaleV(18,16),
490                        2*buttonsGap+140, height-30-5, scaleV(18,16),
491                        3*buttonsGap+2*140, height-30-5, scaleV(18,16),
492                        4*buttonsGap+3*140, height-30-5, scaleV(18,16),
493                                            )
494                 Screen.__init__(self, session)
495                 self["from"] = Label(decodeHeader(_("From") +": %s" %self.email.get('from', _('no from'))))
496                 self["date"] = Label(_("Date") +": %s" %self.email.get('date', 'no-date'))
497                 self["subject"] = Label(decodeHeader(_("Subject") +": %s" %self.email.get('subject', _('no subject'))))
498                 self["body"] = ScrollLabel(_(self.email.messagebodys[0].getData()))
499                 # TODO: show headers
500                 self["buttonred"] = Button("")
501                 self["buttongreen"] = Button("")
502                 self["buttonyellow"] = Button(_("leave unread"))
503                 if '\\Deleted' in flags:
504                         self["buttonblue"] = Button(_("undelete"))
505                 else:
506                         self["buttonblue"] = Button(_("delete"))
507                 self["actions"] = ActionMap(["WizardActions", "DirectionActions", "MenuActions", "ShortcutActions"],
508                         {
509                          "back": self.close,
510                          "up": self["body"].pageUp,
511                          "down": self["body"].pageDown,
512                          # TODO: perhaps better use left/right for previous/next message
513                          "left": self["body"].pageUp,
514                          "right": self["body"].pageDown,
515                          "red": self.selectBody,
516                          "green": self.selectAttachment,
517                          "yellow": self.markUnread,
518                          "blue": self.delete,
519
520                          }, -1)
521                 self.flags = flags
522                 self.proto = proto
523                 self.uid = uid
524                 proto.fetchFlags(self.uid).addCallback(self.cbOk).addErrback(self.cbNotOk)
525                 self.onLayoutFinish.append(self.updateButtons)
526
527         def cbOk(self, result):
528                 debug("[ScreenMailView] cbOk result: %s" %repr(result))
529
530         def cbNotOk(self, result):
531                 debug("[ScreenMailView] cbNotOk result: %s" %(str(result)))
532
533         def delete(self):
534                 if '\\Deleted' in self.flags:
535                         self.session.openWithCallback(self.deleteCB, ChoiceBox, title=_("really undelete Mail?"), list=[(_("yes"), True),(_("no"), False)])
536                 else:
537                         self.session.openWithCallback(self.deleteCB, ChoiceBox, title=_("really delete Mail?"), list=[(_("yes"), True),(_("no"), False)])
538
539         def deleteCB(self, returnValue):
540                 if returnValue and returnValue[1] is True:
541                         if '\\Deleted' in self.flags:
542                                 self.proto.removeFlags(self.uid, ["\\Deleted"]).addCallback(self.cbOk).addErrback(self.cbNotOk)
543                         else:
544                                 self.proto.addFlags(self.uid, ["\\Deleted"]).addCallback(self.cbOk).addErrback(self.cbNotOk)
545                         debug("[ScreenMailView] deleteCB: %s"  %repr(self.email))
546                         self.close()
547
548         def markUnread(self):
549                 self.proto.removeFlags(self.uid, ["\\Seen"]).addCallback(self.cbOk).addErrback(self.cbNotOk)
550                 self.close()
551
552         def openMessagesHeaders(self):
553                 pass #self.session.open(ScreenMailViewHeader,self.profil,self.email)
554
555         def updateButtons(self):
556                 self["buttonred"].setText(_("Bodys"))
557                 if len(self.email.attachments):
558                         self["buttongreen"].setText(_("Attachments"))
559                 else:
560                         self["buttongreen"].setText("")
561
562         def selectBody(self):
563                 if len(self.email.messagebodys):
564                         list = []
565                         for a in self.email.messagebodys:
566                                 list.append((a.getContenttype(), a))
567                         self.session.openWithCallback(self.selectBodyCB, ChoiceBox, _("select Body"), list)
568
569         def selectBodyCB(self, choice):
570                 if choice is not None:
571                         self["body"].setText(choice[1].getData())
572
573         def selectAttachment(self):
574                 if len(self.email.attachments):
575                         list = []
576                         for a in self.email.attachments:
577                                 name = a.getFilename()
578                                 if name:
579                                         list.append((a.getFilename(), a))
580                                 else:
581                                         list.append((_("no filename"), a))
582                         debug("[ScreenMailView] selectAttachment : " + repr(list))
583                         self.session.openWithCallback(self.selectAttachmentCB, ChoiceBox, _("select Attachment"), list)
584
585         def selectAttachmentCB(self, choice):
586                 if choice is not None:
587                         debug("[ScreenMailView] Attachment selected: " + choice[1].getFilename())
588                         #showMessageBox(self.session)
589
590 class MailList(MenuList):
591         def __init__(self, list, enableWrapAround = False):
592                 MenuList.__init__(self, list, enableWrapAround, eListboxPythonMultiContent)
593                 self.l.setFont(0, gFont("Regular", scaleV(20,18))) # new
594                 self.l.setFont(1, gFont("Regular", scaleV(18,16))) # deleted
595                 self.l.setFont(2, gFont("Regular", scaleV(18,16))) # seen
596
597         def postWidgetCreate(self, instance):
598                 MenuList.postWidgetCreate(self, instance)
599                 instance.setItemHeight(scaleV(70,60))
600
601 class MessageHeader(object):
602         def __init__(self, uid, message):
603                 self.uid = uid #must be int
604                 self.message = email.Parser.Parser().parsestr(message)
605
606         def getSenderString(self):
607                 return decodeHeader(self.get("from"), _("no sender"))
608
609         def getSubject(self):
610                 return decodeHeader(self.get("subject"), _("no subject"))
611
612         def get(self, key, default=None):
613                 return self.message.get(key,failobj=default)
614
615         def __str__(self):
616                 return "<MessageHeader uid="+str(self.uid)+", subject="+self.get("subject",_("no subject"))+">"
617
618 ############
619 class EmailBody:
620         def __init__(self,data):
621                 self.data = data
622
623         def getEncoding(self):
624                 return self.data.get_content_charset()
625
626         def getData(self):
627                 text = self.data.get_payload(decode=True)
628                 if self.getEncoding():
629                         try:
630                                 text = text.decode(self.getEncoding())
631                         except UnicodeDecodeError:
632                                 pass    
633                 # debug('EmailBody/getData text: ' +  text)
634                 #=======================================================================
635                 # if self.getEncoding():
636                 #       text = text.decode(self.getEncoding())
637                 #=======================================================================
638                 if self.getContenttype() == "text/html":
639                         debug("[EmailBody] stripping html")
640                         text = strip_readable(text)
641                         # debug('EmailBody/getData text: ' +  text)
642
643                 try:
644                         return text.encode('utf-8')
645                 except UnicodeDecodeError:
646                         return text
647                 
648
649         def getContenttype(self):
650                 return self.data.get_content_type()
651
652 ############
653 class EmailAttachment:
654         def __init__(self, filename, contenttype, data):
655                 self.filename = filename
656                 self.contenttype = contenttype
657                 self.data = data
658
659         def save(self,folder):
660                 try:
661                         fp = open(folder+"/"+self.getFilename(),"wb")
662                         fp.write(self.data)
663                         fp.close()
664                 except Exception,e:
665                         debug("[EmailAttachment] save %s" %str(e))
666                         return False
667                 return True
668
669         def getFilename(self):
670                 return self.filename
671
672         def getContenttype(self):
673                 return self.contenttype
674
675         def getData(self):
676                 return self.data
677
678 def UTF7toUTF8(str):
679         return imap4.decoder(str)[0]
680
681 def UTF8toUTF7(str):
682         return imap4.encoder(str.decode('utf-8'))[0]
683
684 def main(session, **kwargs):
685         import os,shutil
686         if os.path.isfile('/usr/lib/python2.5/uu.py') is not True:
687                 shutil.copy('/usr/lib/enigma2/python/Plugins/Extensions/EmailClient/uu.py', '/usr/lib/python2.5/uu.py')
688                 global session2
689                 session2 = session
690                 session.openWithCallback(MessageCB, MessageBox, _('In order of missing standart python library files\ni have copied the nessary files now.\nBut you have to restart your Box\n to apply this!'), type = MessageBox.TYPE_INFO)
691         else:
692                 session.open(EmailScreen)
693
694 def MessageCB(*args):
695         global session2
696         session2.open(EmailScreen)
697
698 class CheckMail:
699         implements(imap4.IMailboxListener)
700         _timer = None
701
702         def __init__(self):
703                 debug('[CheckMail] __init__ on %s' %time.ctime())
704                 self.factory = createFactory(self, config.plugins.emailimap.username.value, config.plugins.emailimap.server.value, config.plugins.emailimap.port.value)
705                 if not self._timer:
706                         debug('[CheckMail] __init__ creatings timer: %s' %time.ctime())
707                         self._timer = eTimer()
708                         # self._timer.timeout.get().append(self._checkMail)
709                         self._timer.callback.append(self._checkMail)
710                         self._timer.start(config.plugins.emailimap.checkPeriod.value*60*1000) # it is minutes
711                 self._unseenList = None
712                 self._proto = None
713
714         def exit(self):
715                 if self._proto:
716                         self._proto.logout()
717                         self._proto = None
718                 self._timer.stop()
719                 self.factory.stopFactory()
720
721         def _checkMail(self):
722                 debug('[CheckMail] _checkMail on %s' %time.ctime())
723                 if self._proto:
724                         self._proto.search(imap4.Query(unseen=1)).addCallback(self._cbNotify).addErrback(self._ebNotify, _("cannot get list of new messages"))
725                 else:
726                         self.factory.retry()
727
728         def _cbNotify(self, newUnseenList):
729                 def haveNotSeenBefore(messageNo): return messageNo not in self._unseenList
730
731                 debug("[CheckMail] _cbNotify newUnseenList: %s" %repr(newUnseenList))
732                 if self._unseenList is None:
733                         Notifications.AddNotification(MessageBox, str(len(newUnseenList)) + ' ' + _("unread messages in mailbox"), type=MessageBox.TYPE_INFO, timeout=config.plugins.emailimap.timeout.value)
734                 else:
735                         newMessages = filter(haveNotSeenBefore, newUnseenList)
736                         if newMessages:
737                                 debug("[CheckMail] _cbNotify newMessages: %s" %repr(newMessages))
738                                 newMessageSet = imap4.MessageSet()
739                                 for messageNo in newMessages:
740                                         newMessageSet.add(messageNo)
741                                 self._proto.fetchHeaders(newMessageSet).addCallback(self._onHeaderList).addErrback(self._ebNotify, _("cannot get headers of new messages"))
742                 self._unseenList = newUnseenList
743
744         def _onHeaderList(self, headers):
745                 # debug("_onHeaderList headers: %s" %repr(headers))
746                 message = _("New mail arrived:\n\n")
747                 for h in headers:
748                         m = MessageHeader(h, headers[h]['RFC822.HEADER'])
749                         message += m.getSenderString() + '\n' + m.getSubject() + '\n\n'
750                 Notifications.AddNotification(MessageBox, message, type=MessageBox.TYPE_INFO, timeout=config.plugins.emailimap.timeout.value)
751
752         def _ebNotify(self, result, where, what):
753                 debug("[CheckMail] _ebNotify error in %s: %s: %s" %(where, what, result.getErrorMessage()))
754                 Notifications.AddNotification(MessageBox, "EmailClient:\n\n" + what, type=MessageBox.TYPE_ERROR, timeout=config.plugins.emailimap.timeout.value)
755                 # self.exit()
756
757         def _cbOk(self, result):
758                 debug("[CheckMail] _cbOk result: %s" %repr(result))
759
760         def onConnect(self, proto):
761                 debug('[CheckMail] onConnect ' + str(proto))
762                 self._proto = proto
763                 proto.getCapabilities().addCallback(self._cbCapabilities).addErrback(self._ebNotify, "getCapabilities", _("cannot get capabilities of mailserver"))
764
765         def onConnectFailed(self, reason):
766                 debug('[CheckMail] onConnectFailed: ' + reason.getErrorMessage())
767                 self._ebNotify(reason, "onConnectFailed", _("connection to %(server)s:%(port)d failed:\n%(reason)s") %{'server':config.plugins.emailimap.server.value,'port':config.plugins.emailimap.port.value,'reason':reason.getErrorMessage()})
768                 self._proto = None
769
770         def _cbCapabilities(self,reason):
771                 debug("[CheckMail] _cbCapabilities\n\
772 ####################################################################################################\n\
773 # If you have problems to log into your imap-server, please send me the output of the following line\n\
774 # cbCapabilities: " + str(reason) +"\n\
775 ####################################################################################################\n")
776                 self._doLogin()
777                 
778         def _doLogin(self):
779                 useTLS = False #True
780                 if useTLS:
781                         #d = self._proto.startTLS().addCallback(self._proto.authenticate, config.plugins.emailimap.password.value)
782                         d = self._proto.startTLS().addCallback(self._proto.authenticate) # don't know, why authenticate wants no param...
783                 else:
784                         d = self._proto.authenticate(config.plugins.emailimap.password.value)
785                 d.addCallback(self._onAuthentication).addErrback(self._onAuthenticationFailed)
786                 
787         def _onAuthentication(self, result):
788                 debug("[CheckMail] onAuthentication: logged in")
789                 self._proto.examine('inbox').addCallback(self._cbOk).addErrback(self._ebNotify, "examine", _("cannot access inbox"))
790                 self._checkMail()
791
792         def _onAuthenticationFailed(self, failure):
793                 # If it failed because no SASL mechanisms match
794                 debug("[CheckMail] onAuthenticationFailed: " + failure.getErrorMessage())
795                 try:
796                         failure.trap(imap4.NoSupportedAuthentication)
797                         self._doLoginInsecure()
798                 except Exception,e:
799                         debug("[CheckMail] onAuthenticationFailed: %s" %str(e))
800
801         def _doLoginInsecure(self):
802                 debug("[CheckMail] doLoginInsecure")
803                 self._proto.login(config.plugins.emailimap.username.value, config.plugins.emailimap.password.value
804                                 ).addCallback(self._onAuthentication).addErrback(self._ebNotify, "login", _("login failed"))
805
806 mailChecker = None
807 def autostart(reason, **kwargs):
808         # ouch, this is a hack
809         if kwargs.has_key("session"):
810                 global my_global_session
811                 my_global_session = kwargs["session"]
812                 return
813
814         debug("[EmailClient] - Autostart")
815         global mailChecker
816         if config.plugins.emailimap.checkForNewMails.value and not mailChecker:
817                 mailChecker = CheckMail()
818
819 initLog()
820
821 def Plugins(path, **kwargs):
822         global plugin_path
823         plugin_path = path
824         return [
825                          PluginDescriptor(name=_("Email Client"), description=_("view Emails via IMAP4"),
826                          where = PluginDescriptor.WHERE_PLUGINMENU,
827                          fnc = main,
828                          icon="plugin.png"
829                          ),
830                          PluginDescriptor(where=[PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc=autostart)
831                 ]