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