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