Added PermanentClock plugin
[enigma2-plugins.git] / fritzcall / src / plugin.py
1 # -*- coding: utf-8 -*-
2 #===============================================================================
3 # $Author$
4 # $Revision$
5 # $Date$
6 #==============================
7 from Screens.Screen import Screen
8 from Screens.MessageBox import MessageBox
9 from Screens.NumericalTextInputHelpDialog import NumericalTextInputHelpDialog
10 from Screens.InputBox import InputBox
11 from Screens import Standby
12 from Screens.HelpMenu import HelpableScreen
13
14 from enigma import eListboxPythonMultiContent, gFont, RT_HALIGN_LEFT
15
16 from Components.MenuList import MenuList
17 from Components.ActionMap import ActionMap
18 from Components.Label import Label
19 from Components.Button import Button
20 from Components.config import config, ConfigSubsection, ConfigSelection, ConfigEnableDisable, getConfigListEntry, ConfigText, ConfigInteger, ConfigPassword
21 from Components.ConfigList import ConfigListScreen
22
23 from Plugins.Plugin import PluginDescriptor
24 from Tools import Notifications
25 from Tools.NumericalTextInput import NumericalTextInput
26
27 from twisted.internet import reactor
28 from twisted.internet.protocol import ReconnectingClientFactory
29 from twisted.protocols.basic import LineReceiver
30 from twisted.web.client import getPage
31
32 from xml.dom.minidom import parse
33
34 from urllib import urlencode 
35 import re, time, os
36
37 import gettext
38 from Tools.Directories import resolveFilename, SCOPE_PLUGINS
39 try:
40         _ = gettext.translation('FritzCall', resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/locale"), [config.osd.language.getText()]).gettext
41 except IOError:
42         pass
43
44
45 my_global_session = None
46
47 config.plugins.FritzCall = ConfigSubsection()
48 config.plugins.FritzCall.enable = ConfigEnableDisable(default = False)
49 # config.plugins.FritzCall.hostname = ConfigIP(default = [192, 168, 178, 1])
50 config.plugins.FritzCall.hostname = ConfigText(default = "fritz.box", fixed_size = False)
51 config.plugins.FritzCall.afterStandby = ConfigSelection(choices = [("none", _("show nothing")), ("inList", _("show as list")), ("each", _("show each call"))])
52 config.plugins.FritzCall.filter = ConfigEnableDisable(default = False)
53 config.plugins.FritzCall.filtermsn = ConfigText(default = "", fixed_size = False)
54 config.plugins.FritzCall.filtermsn.setUseableChars('0123456789,')
55 config.plugins.FritzCall.showOutgoing = ConfigEnableDisable(default = False)
56 config.plugins.FritzCall.timeout = ConfigInteger(default = 15, limits = (0,60))
57 config.plugins.FritzCall.lookup = ConfigEnableDisable(default = False)
58 config.plugins.FritzCall.internal = ConfigEnableDisable(default = False)
59 config.plugins.FritzCall.fritzphonebook = ConfigEnableDisable(default = False)
60 config.plugins.FritzCall.phonebook = ConfigEnableDisable(default = False)
61 config.plugins.FritzCall.addcallers = ConfigEnableDisable(default = False)
62 config.plugins.FritzCall.phonebookLocation = ConfigSelection(choices = [("/etc/enigma2/PhoneBook.txt", _("Flash")), ("/media/usb/PhoneBook.txt", _("USB Stick")), ("/media/cf/PhoneBook.txt", _("CF Drive")), ("/media/hdd/PhoneBook.txt", _("Harddisk"))])
63 config.plugins.FritzCall.password = ConfigPassword(default = "", fixed_size = False)
64 config.plugins.FritzCall.showType = ConfigEnableDisable(default = True)
65 config.plugins.FritzCall.showShortcut = ConfigEnableDisable(default = False)
66 config.plugins.FritzCall.showVanity = ConfigEnableDisable(default = False)
67 config.plugins.FritzCall.prefix = ConfigText(default = "", fixed_size = False)
68 config.plugins.FritzCall.prefix.setUseableChars('0123456789')
69
70 countryCodes = [
71         ("0049", _("Germany")),
72         ("0031", _("The Netherlands")),
73         ("0033", _("France")),
74         ("0039", _("Italy")),
75         ("0041", _("Switzerland")),
76         ("0043", _("Austria"))
77         ]
78 config.plugins.FritzCall.country = ConfigSelection(choices = countryCodes)
79
80 FBF_ALL_CALLS = "."
81 FBF_IN_CALLS = "1"
82 FBF_MISSED_CALLS = "2"
83 FBF_OUT_CALLS = "3"
84 fbfCallsChoices = {FBF_ALL_CALLS: _("All calls"),
85                                    FBF_IN_CALLS: _("Incoming calls"),
86                                    FBF_MISSED_CALLS: _("Missed calls"),
87                                    FBF_OUT_CALLS: _("Outgoing calls")
88                                    }
89 config.plugins.FritzCall.fbfCalls = ConfigSelection(choices = fbfCallsChoices)
90
91 config.plugins.FritzCall.name = ConfigText(default = "", fixed_size = False)
92 config.plugins.FritzCall.number= ConfigText(default = "", fixed_size = False)
93 config.plugins.FritzCall.number.setUseableChars('0123456789')
94
95
96 def html2utf8(in_html):
97         try:
98                 import htmlentitydefs
99
100                 # TODO: first convert some WML codes; does not work?!?!
101                 # in_html = in_html.replace("ß;", "").replace("ä", "").replace("ö", "").replace("ü", "").replace("Ä", "").replace("Ö", "").replace("Ü", "")
102
103                 htmlentitynamemask = re.compile('(&(\D{1,5}?);)')
104                 entitydict = {}
105                 entities = htmlentitynamemask.finditer(in_html)
106                 for x in entities:
107                         entitydict[x.group(1)] = x.group(2)
108                 for key, name in entitydict.items():
109                         try:
110                                 entitydict[key] = htmlentitydefs.name2codepoint[name]
111                         except KeyError:
112                                 print "[FritzCallhtml2utf8] KeyError " + key + "/" + name
113                                 pass
114
115                 htmlentitynumbermask = re.compile('(&#(\d{1,5}?);)')
116                 entities = htmlentitynumbermask.finditer(in_html)
117                 for x in entities:
118                         entitydict[x.group(1)] = x.group(2)
119                 for key, codepoint in entitydict.items():
120                         try:
121                                 in_html = in_html.replace(key, (unichr(int(codepoint)).encode('utf8', "replace")))
122                         except ValueError:
123                                 print "[FritzCallhtml2utf8] ValueError " + key + "/" + str(codepoint)
124                                 pass
125         except ImportError:
126                 try:
127                         return in_html.replace("&", "&").replace("ß", "").replace("ä", "").replace("ö", "").replace("ü", "").replace("Ä", "").replace("Ö", "").replace("Ü", "")
128                 except UnicodeDecodeError:
129                         pass
130         return in_html
131
132
133 class FritzCallFBF:
134         def __init__(self):
135                 print "[FritzCallFBF] __init__"
136                 self.callScreen= None
137                 self.loggedIn = False
138                 self.Callback = None
139                 self.loginCallback = None
140                 self.timestamp = 0
141                 self.callList = []
142                 self.callType = config.plugins.FritzCall.fbfCalls.value
143
144         def notify(self, text):
145                 print "[FritzCallFBF] notify"
146                 if self.callScreen:
147                         print "[FritzCallFBF] notify: try to close callScreen"
148                         self.callScreen.close()
149                         self.callScreen = None
150                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
151
152         def errorLogin(self, error):
153                 text = _("FRITZ!Box Login failed! - Error: %s") %error
154                 self.notify(text)
155
156         def _gotPageLogin(self, html):
157 #               print "[FritzCallPhonebook] _gotPageLogin"
158                 # workaround: exceptions in gotPage-callback were ignored
159                 if self.callScreen:
160                         self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("login verification"))
161                 try:
162                         print "[FritzCallFBF] _gotPageLogin: verify login"
163                         found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;Das angegebene Kennwort', html, re.S)
164                         if found:
165                                 text = _("FRITZ!Box Login failed! - Wrong Password!")
166                                 self.notify(text)
167                         else:
168                                 if self.callScreen:
169                                         self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("login ok"))
170                                 self.loggedIn = True
171                                 self.loginCallback()
172                 except:
173                         import traceback, sys
174                         traceback.print_exc(file=sys.stdout)
175                         #raise e
176
177         def login(self):
178                 print "[FritzCallFBF] Login"
179                 if config.plugins.FritzCall.password.value != "":
180                         if self.callScreen:
181                                 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("login"))
182                         parms = "login:command/password=%s" %(config.plugins.FritzCall.password.value)
183                         url = "http://%s/cgi-bin/webcm" %(config.plugins.FritzCall.hostname.value)
184                         getPage(url, method="POST", headers = {'Content-Type': "application/x-www-form-urlencoded",'Content-Length': str(len(parms))}, postdata=parms).addCallback(self._gotPageLogin).addErrback(self.errorLogin)
185                 else:
186                         self.loginCallback()
187                         self.loginCallback = None
188
189         def errorLoad(self, error):
190                 text = _("Could not load phonebook from FRITZ!Box - Error: %s") %error
191                 self.notify(text)
192
193         def _gotPageLoad(self, html):
194                 print "[FritzCallFBF] _gotPageLoad"
195                 # workaround: exceptions in gotPage-callback were ignored
196                 try:
197                         self.parseFritzBoxPhonebook(html)
198                 except:
199                         import traceback, sys
200                         traceback.print_exc(file=sys.stdout)
201                         #raise e
202
203         def loadFritzBoxPhonebook(self):
204                 print "[FritzCallFBF] loadFritzBoxPhonebook"
205                 if config.plugins.FritzCall.fritzphonebook.value:
206                         print "[FritzCallFBF] loadFritzBoxPhonebook: logging in"
207                         self.loginCallback = self._loadFritzBoxPhonebook
208                         self.login()
209
210         def _loadFritzBoxPhonebook(self):
211                         parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de','var:pagename':'fonbuch','var:menu':'fon'})
212                         url = "http://%s/cgi-bin/webcm?%s" %(config.plugins.FritzCall.hostname.value, parms)
213
214                         getPage(url).addCallback(self._gotPageLoad).addErrback(self.errorLoad)
215
216         def parseFritzBoxPhonebook(self, html):
217                 print "[FritzCallFBF] parseFritzBoxPhonebook"
218
219                 table = html2utf8(html.replace("\xa0"," ").decode("ISO-8859-1", "replace"))
220                 if re.search('TrFonName', table):
221                         #===============================================================================
222                         #                                New Style: 7170 / 7270 (FW 54.04.58, 54.04.63-11941) 
223                         #       We expect one line with TrFonName followed by several lines with
224                         #       TrFonNr(Type,Number,Shortcut,Vanity), which all belong to the name in TrFonName.
225                         #===============================================================================
226                         # entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]+"\);</SCRIPT>\s+[<SCRIPT type=text/javascript>TrFonNr\("[^"]+", "[^"]+", "[^"]+", "[^"]+"\);</SCRIPT>\s+]+)<SCRIPT type=text/javascript>document.write(TrFon1());</SCRIPT>', re.DOTALL)
227                         # entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]+"\);.*?[.*?TrFonNr\("[^"]+", "[^"]+", "[^"]+", "[^"]+"\);.*?]+).*?document.write(TrFon1());', re.DOTALL)
228                         entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]*"\);.*?)TrFon1\(\)', re.S)
229                         entries = entrymask.finditer(html)
230                         for entry in entries:
231                                 # print entry.group(1)
232                                 found = re.match('TrFonName\("[^"]*", "([^"]+)", "[^"]*"\);', entry.group(1))
233                                 if found:
234                                         name = found.group(1)
235                                 else:
236                                         continue
237                                 detailmask = re.compile('TrFonNr\("([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\);', re.S)
238                                 details = detailmask.finditer(entry.group(1))
239                                 for found in details:
240                                         thisname = name
241
242                                         type = found.group(1)
243                                         if config.plugins.FritzCall.showType.value:
244                                                 if type == "mobile":
245                                                         thisname = thisname + " (" +_("mobile") + ")"
246                                                 elif type == "home":
247                                                         thisname = thisname + " (" +_("home") + ")"
248                                                 elif type == "work":
249                                                         thisname = thisname + " (" +_("work") + ")"
250
251                                         if config.plugins.FritzCall.showShortcut.value and found.group(3):
252                                                 thisname = thisname + ", " + _("Shortcut") + ": " + found.group(3)
253                                         if config.plugins.FritzCall.showVanity.value and found.group(4):
254                                                 thisname = thisname + ", " + _("Vanity") + ": " + found.group(4)
255
256                                         thisnumber = found.group(2).strip()
257                                         thisname = html2utf8(thisname.strip())
258                                         if thisnumber:
259                                                 print "[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" %(thisname, thisnumber)
260                                                 phonebook.phonebook[thisnumber] = thisname
261                                         else:
262                                                 print "[FritzCallFBF] ignoring empty number for %s" %thisname
263                                         continue
264
265                 elif re.search('TrFon', table):
266                         #===============================================================================
267                         #                               Old Style: 7050 (FW 14.04.33)
268                         #       We expect one line with TrFon(No,Name,Number,Shortcut,Vanity)
269                         #===============================================================================                                
270                         entrymask = re.compile('TrFon\("[^"]*", "([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\)', re.S)
271                         entries = entrymask.finditer(html)
272                         for found in entries:
273                                 name = found.group(1).strip()
274                                 thisnumber = found.group(2).strip()
275                                 if config.plugins.FritzCall.showShortcut.value and found.group(3):
276                                         name = name + ", " + _("Shortcut") + ": " + found.group(3)
277                                 if config.plugins.FritzCall.showVanity.value and found.group(4):
278                                         name = name + ", " +_("Vanity") +": " + found.group(4)
279                                 if thisnumber:
280                                         name = html2utf8(name)
281                                         print "[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" %(name, thisnumber)
282                                         phonebook.phonebook[thisnumber] = name
283                                 else:
284                                         print "[FritzCallFBF] ignoring empty number for %s" %name
285                                 continue
286                 else:
287                         self.notify(_("Could not parse FRITZ!Box Phonebook entry"))
288
289         def errorCalls(self, error):
290                 text = _("Could not load calls from FRITZ!Box - Error: %s") %error
291                 self.notify(text)
292
293         def _gotPageCalls(self, csv = ""):
294                 def _resolveNumber(number):
295                         if number.isdigit():
296                                 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0]=="0": number = number[1:]
297                                 # strip CbC prefix
298                                 if config.plugins.FritzCall.country.value == '0049':
299                                         if re.match('^0100\d\d', number):
300                                                 number = number[6:]
301                                         elif re.match('^010\d\d', number):
302                                                 number = number[5:]
303                                 if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
304                                         number = config.plugins.FritzCall.prefix.value + number
305                                 name = phonebook.search(number)
306                                 if name:
307                                         found = re.match('(.*?)\n.*', name)
308                                         if found:
309                                                 name = found.group(1)
310                                         number = name
311                         elif number == "":
312                                 number = _("UNKNOWN")
313                         # if len(number) > 20: number = number[:20]
314                         return number
315
316                 if csv:
317                         print "[FritzCallFBF] _gotPageCalls: got csv, setting callList"
318                         if self.callScreen:
319                                 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("done"))
320                         # check for error: wrong password or password not set... TODO
321                         found = re.search('Melden Sie sich mit dem Kennwort der FRITZ!Box an', csv)
322                         if found:
323                                 text = _("You need to set the password of the FRITZ!Box\nin the configuration dialog to display calls\n\nIt could be a communication issue, just try again.")
324                                 # self.session.open(MessageBox, text, MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
325                                 self.notify(text)
326                                 return
327
328                         csv = csv.decode('iso-8859-1','replace').encode('utf-8','replace')
329                         lines = csv.splitlines()
330                         self.callList = lines
331                 elif self.callList:
332                         print "[FritzCallFBF] _gotPageCalls: got no csv, but have callList"
333                         if self.callScreen:
334                                 self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("done, using last list"))
335                         lines = self.callList
336                 else:
337                         print "[FritzCallFBF] _gotPageCalls: got no csv, no callList, leaving"
338                         return
339                         
340                 callList = []
341                 for line in lines:
342                         # print line
343                         # Typ;Datum;Name;Rufnummer;Nebenstelle;Eigene Rufnummer;Dauer
344                         found = re.match("^(" + self.callType + ");([^;]*);([^;]*);([^;]*);([^;]*);([^;]*)", line)
345                         if found:
346                                 direct = found.group(1)
347                                 date = found.group(2)
348                                 if direct != FBF_OUT_CALLS and found.group(3):
349                                         remote = found.group(3)
350                                 else:
351                                         remote = _resolveNumber(found.group(4))
352                                 found1 = re.match('Internet: (.*)', found.group(6))
353                                 if found1:
354                                         here = _resolveNumber(found1.group(1))
355                                 else:
356                                         here = _resolveNumber(found.group(6))
357                                 
358                                 # strip CbC prefix for Germany
359                                 number = found.group(4)
360                                 if config.plugins.FritzCall.country.value == '0049':
361                                         if re.match('^0100\d\d', number):
362                                                 number = number[6:]
363                                         elif re.match('^010\d\d', number):
364                                                 number = number[5:]
365                                 if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
366                                         number = config.plugins.FritzCall.prefix.value + number
367                                 callList.append((number, date, here, direct, remote))
368
369                 # print "[FritzCallFBF] _gotPageCalls result:\n" + text
370
371                 if self.Callback is not None:
372                         # print "[FritzCallFBF] _gotPageCalls call callback with\n" + text
373                         self.Callback(callList)
374                         self.Callback = None
375                 self.callScreen = None
376
377         def getCalls(self, callScreen, callback, type):
378                 #
379                 # call sequence must be:
380                 # - login
381                 # - getPage -> _gotPageLogin
382                 # - loginCallback (_getCalls)
383                 # - getPage -> _getCalls1
384                 print "[FritzCallFBF] getCalls"
385                 self.callScreen = callScreen
386                 self.callType = type
387                 self.Callback = callback
388                 if (time.time() - self.timestamp) > 180: 
389                         print "[FritzCallFBF] getCalls: outdated data, login and get new ones"
390                         self.timestamp = time.time()
391                         self.loginCallback = self._getCalls
392                         self.login()
393                 elif not self.callList:
394                         print "[FritzCallFBF] getCalls: time is ok, but no callList"
395                         self._getCalls1()
396                 else:
397                         print "[FritzCallFBF] getCalls: time is ok, callList is ok"
398                         self._gotPageCalls()
399
400         def _getCalls(self):
401                 #
402                 # we need this to fill Anrufliste.csv
403                 # http://repeater1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=foncalls
404                 #
405                 print "[FritzCallFBF] _getCalls"
406                 if self.callScreen:
407                         self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("preparing"))
408                 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de','var:pagename':'foncalls','var:menu':'fon'})
409                 url = "http://%s/cgi-bin/webcm?%s" %(config.plugins.FritzCall.hostname.value, parms)
410                 getPage(url).addCallback(self._getCalls1).addErrback(self.errorCalls)
411
412         def _getCalls1(self, html = ""):
413                 #
414                 # finally we should have successfully lgged in and filled the csv
415                 #
416                 print "[FritzCallFBF] _getCalls1"
417                 if self.callScreen:
418                         self.callScreen.updateStatus(_("Getting calls from FRITZ!Box...") + _("finishing"))
419                 parms = urlencode({'getpage':'../html/de/FRITZ!Box_Anrufliste.csv'})
420                 url = "http://%s/cgi-bin/webcm?%s" %(config.plugins.FritzCall.hostname.value, parms)
421                 getPage(url).addCallback(self._gotPageCalls).addErrback(self.errorCalls)
422
423         def dial(self, number):
424                 ''' initiate a call to number '''
425                 #
426                 # TODO: implement dial out
427                 # does not work... if anybody wants to make it work, feel free
428                 # I not convinced of FBF's style to establish a connection: first get the connection, then ring the local phone?!?!
429                 #  
430                 return
431                 # http://fritz.box/cgi-bin/webcm
432                 # getpage=../html/de/menus/menu2.html
433                 # var:lang=de
434                 # var:pagename=foncalls
435                 # var:menu=home
436                 # var:pagemaster=
437                 # var:showsetup=
438                 # var:showall=
439                 # var:showDialing=08001235005
440                 # telcfg:settings/UseJournal=1
441                 # telcfg:command/Dial=08001235005
442                 self.login()
443                 url = "http://%s/cgi-bin/webcm" %config.plugins.FritzCall.hostname.value
444                 parms = urlencode({
445                         'getpage':'../html/de/menus/menu2.html',
446                         'errorpage':'../html/de/menus/menu2.html',
447                         'var:lang':'de',
448                         'var:pagename':'foncalls',
449                         'var:errorpagename':'foncalls',
450                         'var:menu':'home',
451                         'var:pagemaster':'',
452                         'var:settings/time':'0,0',
453                         'var:showsetup':'',
454                         'var:showall':'',
455                         'var:showDialing':number,
456                         'var:tabFoncall':'',
457                         'var:TestPort':'',
458                         'var:kurzwahl':'',
459                         'var:kwCode':'',
460                         'var:kwVanity':'',
461                         'var:kwNumber':'',
462                         'var:kwName':'',
463                         'telcfg:settings/UseJournal':'1',
464                         'telcfg:command/Dial':number
465                         })
466                 print "[FritzCallFBF] dial url: '" + url + "' parms: '" + parms + "'"
467                 getPage(url, method="POST", headers = {'Content-Type': "application/x-www-form-urlencoded",'Content-Length': str(len(parms))}, postdata=parms)
468
469
470
471 fritzbox = FritzCallFBF()
472
473 class FritzDisplayCalls(Screen, HelpableScreen):
474
475         # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
476         skin = """
477                 <screen name="FritzDisplayCalls" position="100,90" size="570,420" title="%s" >
478                         <widget name="statusbar" position="0,0" size="570,22" font="Regular;21" />
479                         <widget name="entries" position="0,22" size="570,358" scrollbarMode="showOnDemand" />
480                         <ePixmap position="5,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
481                         <ePixmap position="145,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
482                         <ePixmap position="285,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
483                         <ePixmap position="425,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
484                         <widget name="key_red" position="5,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
485                         <widget name="key_green" position="145,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
486                         <widget name="key_yellow" position="285,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
487                         <widget name="key_blue" position="425,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
488                 </screen>""" % _("Phone calls")
489
490         def __init__(self, session, text = ""):
491                 self.skin = FritzDisplayCalls.skin
492                 Screen.__init__(self, session)
493                 HelpableScreen.__init__(self)
494
495                 # TRANSLATORS: keep it short, this is a button
496                 self["key_red"] = Button(_("All"))
497                 # TRANSLATORS: keep it short, this is a button
498                 self["key_green"] = Button(_("Missed"))
499                 # TRANSLATORS: keep it short, this is a button
500                 self["key_yellow"] = Button(_("Incoming"))
501                 # TRANSLATORS: keep it short, this is a button
502                 self["key_blue"] = Button(_("Outgoing"))
503
504                 self["setupActions"] = ActionMap(["OkCancelActions", "ColorActions"],
505                 {
506                         "red": self.displayAllCalls,
507                         "green": self.displayMissedCalls,
508                         "yellow": self.displayInCalls,
509                         "blue": self.displayOutCalls,
510                         "cancel": self.ok,
511                         "ok": self.showEntry,}, -2)
512
513                 # TRANSLATORS: this is a help text, keep it short
514                 self.helpList.append((self["setupActions"], "OkCancelActions", [("ok", _("Show details of entry"))]))
515                 # TRANSLATORS: this is a help text, keep it short
516                 self.helpList.append((self["setupActions"], "OkCancelActions", [("cancel", _("Quit"))]))
517                 # TRANSLATORS: this is a help text, keep it short
518                 self.helpList.append((self["setupActions"], "ColorActions", [("red", _("Display all calls"))]))
519                 # TRANSLATORS: this is a help text, keep it short
520                 self.helpList.append((self["setupActions"], "ColorActions", [("green", _("Display missed calls"))]))
521                 # TRANSLATORS: this is a help text, keep it short
522                 self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("Display incoming calls"))]))
523                 # TRANSLATORS: this is a help text, keep it short
524                 self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("Display outgoing calls"))]))
525
526                 self["statusbar"] = Label(_("Getting calls from FRITZ!Box..."))
527                 self["entries"] = MenuList([], True, content = eListboxPythonMultiContent)
528                 self["entries"].l.setFont(0, gFont("Console", 16))
529                 self["entries"].l.setItemHeight(20)
530
531                 print "[FritzDisplayCalls] init: '''%s'''" %config.plugins.FritzCall.fbfCalls.value
532                 self.displayCalls()
533
534         def ok(self):
535                 self.close()
536
537         def displayAllCalls(self):
538                 print "[FritzDisplayCalls] displayAllCalls"
539                 config.plugins.FritzCall.fbfCalls.value = FBF_ALL_CALLS
540                 config.plugins.FritzCall.fbfCalls.save()
541                 self.displayCalls()
542
543         def displayMissedCalls(self):
544                 print "[FritzDisplayCalls] displayMissedCalls"
545                 config.plugins.FritzCall.fbfCalls.value = FBF_MISSED_CALLS
546                 config.plugins.FritzCall.fbfCalls.save()
547                 self.displayCalls()
548
549         def displayInCalls(self):
550                 print "[FritzDisplayCalls] displayInCalls"
551                 config.plugins.FritzCall.fbfCalls.value = FBF_IN_CALLS
552                 config.plugins.FritzCall.fbfCalls.save()
553                 self.displayCalls()
554
555         def displayOutCalls(self):
556                 print "[FritzDisplayCalls] displayOutCalls"
557                 config.plugins.FritzCall.fbfCalls.value = FBF_OUT_CALLS
558                 config.plugins.FritzCall.fbfCalls.save()
559                 self.displayCalls()
560
561         def displayCalls(self):
562                 print "[FritzDisplayCalls] displayCalls"
563                 self.header = fbfCallsChoices[config.plugins.FritzCall.fbfCalls.value]
564                 fritzbox.getCalls(self, self.gotCalls, config.plugins.FritzCall.fbfCalls.value)
565
566         def gotCalls(self, callList):
567                 print "[FritzDisplayCalls] gotCalls"
568                 self.updateStatus(self.header + " (" + str(len(callList)) + ")")
569                 sortlist = []
570                 for (number, date, remote, direct, here) in callList:
571                         while (len(remote) + len(here)) > 40:
572                                 if len(remote) > len(here):
573                                         remote = remote[:-1]
574                                 else:
575                                         here = here[:-1]
576                         found = re.match("(\d\d.\d\d.)\d\d( \d\d:\d\d)", date)
577                         if found: date = found.group(1) + found.group(2)
578                         if direct == FBF_OUT_CALLS:
579                                 message = date + " " + remote + " -> " + here
580                         else:
581                                 message = date + " " + here + " -> " + remote
582                         sortlist.append([number, (eListboxPythonMultiContent.TYPE_TEXT, 0, 0, 560, 20, 0, RT_HALIGN_LEFT, message)])
583                 self["entries"].setList(sortlist)
584
585         def showEntry(self):
586                 print "[FritzDisplayCalls] showEntry"
587                 cur = self["entries"].getCurrent()
588                 if cur:
589                         if cur[0]:
590                                 print "[FritzDisplayCalls] showEntry %s" % (cur[0])
591                                 # TODO: offer to call number
592                                 fullname = phonebook.search(cur[0])
593                                 if fullname:
594                                         self.session.open(MessageBox,
595                                                                         cur[0] + "\n\n" + fullname.replace(", ","\n"),
596                                                                         type = MessageBox.TYPE_INFO)
597                                 else:
598                                         self.actualNumber = cur[0]
599                                         self.session.openWithCallback(
600                                                 self.addConfirmed,
601                                                 MessageBox,
602                                                 _("Do you want to add a phonebook entry for\n\n%s?") %cur[0]
603                                                 )
604                         else:
605                                 self.session.open(MessageBox,
606                                                   _("UNKNOWN"),
607                                                   type = MessageBox.TYPE_INFO)
608
609         def addConfirmed(self, ret):
610                 if ret:
611                         phonebook.displayPhonebook(self.session).add(self.actualNumber)
612
613         def updateStatus(self, text):
614                 self["statusbar"].setText(text)
615
616
617 class FritzCallPhonebook:
618         def __init__(self):
619                 self.phonebook = {}
620                 self.reload()
621
622         def reload(self):
623                 print "[FritzCallPhonebook] reload"
624                 self.phonebook = {}
625
626                 if not config.plugins.FritzCall.enable.value:
627                         return
628
629                 if config.plugins.FritzCall.phonebook.value and os.path.exists(config.plugins.FritzCall.phonebookLocation.value):
630                         phonebookTxtCorrupt = False
631                         for line in open(config.plugins.FritzCall.phonebookLocation.value):
632                                 if re.match("^\d+#.*$", line):
633                                         try:
634                                                 number, name = line.split("#")
635                                                 if not self.phonebook.has_key(number):
636                                                         self.phonebook[number] = name
637                                         except ValueError:
638                                                 print "[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" %line
639                                                 phonebookTxtCorrupt = True
640                                 else:
641                                         print "[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" %line
642                                         phonebookTxtCorrupt = True
643
644                         if phonebookTxtCorrupt:
645                                 # dump phonebook to PhoneBook.txt
646                                 print "[FritzCallPhonebook] dump Phonebook.txt"
647                                 os.rename(config.plugins.FritzCall.phonebookLocation.value,
648                                                 config.plugins.FritzCall.phonebookLocation.value + ".bck")
649                                 fNew = open(config.plugins.FritzCall.phonebookLocation.value, 'w')
650                                 for (number, name) in self.phonebook.iteritems():
651                                         fNew.write(number + "#" + name)
652                                 fNew.close()
653
654                 if config.plugins.FritzCall.fritzphonebook.value:
655                         fritzbox.loadFritzBoxPhonebook()
656
657         def search(self, number):
658                 # print "[FritzCallPhonebook] Searching for %s" %number
659                 name = None
660                 if config.plugins.FritzCall.phonebook.value or config.plugins.FritzCall.fritzphonebook.value:
661                         if self.phonebook.has_key(number):
662                                 name = self.phonebook[number].replace(", ", "\n").strip()
663                 return name
664
665         def add(self, number, name):
666                 print "[FritzCallPhonebook] add"
667                 #===============================================================================
668                 #               It could happen, that two reverseLookups are running in parallel,
669                 #               so check first, whether we have already added the number to the phonebook.
670                 #===============================================================================
671                 self.phonebook[number] = name;
672                 if number <> 0 and config.plugins.FritzCall.phonebook.value and config.plugins.FritzCall.addcallers.value:
673                         try:
674                                 f = open(config.plugins.FritzCall.phonebookLocation.value, 'a')
675                                 name = name.strip() + "\n"
676                                 string = "%s#%s" %(number, name)
677                                 f.write(string)
678                                 f.close()
679                                 print "[FritzCallPhonebook] added %s with %s to Phonebook.txt" %(number, name)
680                                 return True
681
682                         except IOError:
683                                 return False
684
685         def remove(self, number):
686                 print "[FritzCallPhonebook] remove"
687                 if number in self.phonebook:
688                         print "[FritzCallPhonebook] remove entry in phonebook"
689                         del self.phonebook[number]
690                         if config.plugins.FritzCall.phonebook.value:
691                                 try:
692                                         print "[FritzCallPhonebook] remove entry in Phonebook.txt"
693                                         fOld = open(config.plugins.FritzCall.phonebookLocation.value, 'r')
694                                         fNew = open(config.plugins.FritzCall.phonebookLocation.value + str(os.getpid()), 'w')
695                                         line = fOld.readline()
696                                         while (line):
697                                                 if not re.match("^"+number+"#.*$", line):
698                                                         fNew.write(line)
699                                                 line = fOld.readline()
700                                         fOld.close()
701                                         fNew.close()
702                                         os.remove(config.plugins.FritzCall.phonebookLocation.value)
703                                         os.rename(config.plugins.FritzCall.phonebookLocation.value + str(os.getpid()),
704                                                         config.plugins.FritzCall.phonebookLocation.value)
705                                         print "[FritzCallPhonebook] removed %s from Phonebook.txt" %number
706                                         return True
707         
708                                 except IOError:
709                                         pass
710                 return False
711
712         class displayPhonebook(Screen, HelpableScreen, NumericalTextInput):
713                 # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
714                 skin = """
715                         <screen name="FritzDisplayPhonebook" position="100,90" size="570,420" title="%s" >
716                                 <widget name="entries" position="5,5" size="560,370" scrollbarMode="showOnDemand" />
717                                 <ePixmap position="5,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
718                                 <ePixmap position="145,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
719                                 <ePixmap position="285,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
720                                 <ePixmap position="425,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
721                                 <widget name="key_red" position="5,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
722                                 <widget name="key_green" position="145,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
723                                 <widget name="key_yellow" position="285,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
724                                 <widget name="key_blue" position="425,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
725                         </screen>""" % _("Phonebook")
726
727                 def __init__(self, session):
728                         Screen.__init__(self, session)
729                         NumericalTextInput.__init__(self)
730                         HelpableScreen.__init__(self)
731                 
732                         # TRANSLATORS: keep it short, this is a button
733                         self["key_red"] = Button(_("Delete"))
734                         # TRANSLATORS: keep it short, this is a button
735                         self["key_green"] = Button(_("New"))
736                         # TRANSLATORS: keep it short, this is a button
737                         self["key_yellow"] = Button(_("Edit"))
738                         # TRANSLATORS: keep it short, this is a button
739                         self["key_blue"] = Button(_("Search"))
740         
741                         self["setupActions"] = ActionMap(["OkCancelActions", "ColorActions"],
742                         {
743                                 "red": self.delete,
744                                 "green": self.add,
745                                 "yellow": self.edit,
746                                 "blue": self.search,
747                                 "cancel": self.exit,
748                                 "ok": self.showEntry,}, -2)
749
750                         # TRANSLATORS: this is a help text, keep it short
751                         self.helpList.append((self["setupActions"], "OkCancelActions", [("ok", _("Show details of entry"))]))
752                         # TRANSLATORS: this is a help text, keep it short
753                         self.helpList.append((self["setupActions"], "OkCancelActions", [("cancel", _("Quit"))]))
754                         # TRANSLATORS: this is a help text, keep it short
755                         self.helpList.append((self["setupActions"], "ColorActions", [("red", _("Delete entry"))]))
756                         # TRANSLATORS: this is a help text, keep it short
757                         self.helpList.append((self["setupActions"], "ColorActions", [("green", _("Add entry to phonebook"))]))
758                         # TRANSLATORS: this is a help text, keep it short
759                         self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("Edit selected entry"))]))
760                         # TRANSLATORS: this is a help text, keep it short
761                         self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("Search (case insensitive)"))]))
762
763                         self["entries"] = MenuList([], True, content = eListboxPythonMultiContent)
764                         self["entries"].l.setFont(0, gFont("Console", 16))
765                         self["entries"].l.setItemHeight(20)
766                         print "[FritzCallPhonebook] displayPhonebook init"
767                         self.display()
768
769                 def display(self, filter=""):
770                         print "[FritzCallPhonebook] displayPhonebook/display"
771                         self.sortlist = []
772                         sortlistHelp = sorted((name.lower(), name, number) for (number, name) in phonebook.phonebook.iteritems())
773                         for (low, name, number) in sortlistHelp:
774                                 if number == "01234567890":
775                                         continue
776                                 try:
777                                         low = low.decode("utf-8")
778                                         if filter:
779                                                 filter = filter.lower()
780                                                 if low.find(filter) == -1:
781                                                         continue
782                                         name = name.strip().decode("utf-8")
783                                         number = number.strip().decode("utf-8")
784                                         found = re.match("([^,]*),.*", name)   # strip address information from the name part
785                                         if found:
786                                                 shortname = found.group(1)
787                                         else:
788                                                 shortname = name
789                                         if len(shortname) > 35:
790                                                 shortname = shortname[:35]
791                                         message = u"%-35s  %-18s" %(shortname, number)
792                                         message = message.encode("utf-8")
793                                         # print "[FritzCallPhonebook] displayPhonebook/display: add " + message
794                                         self.sortlist.append([(number.encode("utf-8","replace"),
795                                                                    name.encode("utf-8","replace")),
796                                                                    (eListboxPythonMultiContent.TYPE_TEXT, 0, 0, 560, 20, 0, RT_HALIGN_LEFT, message)])
797                                 except UnicodeDecodeError:  # this should definitely not happen
798                                         self.session.open(MessageBox, _("Corrupt phonebook entry\nfor number %d\nDeleting.") %number, type = MessageBox.TYPE_ERROR)
799                                         phonebook.remove(number)
800                                         continue
801                                 
802                         self["entries"].setList(self.sortlist)
803
804                 def showEntry(self):
805                         cur = self["entries"].getCurrent()
806                         if cur and cur[0]:
807                                 print "[FritzCallPhonebook] displayPhonebook/showEntry (%s,%s)" % (cur[0][0],cur[0][1])
808                                 fullname = phonebook.search(cur[0][0])
809                                 if fullname:
810                                         self.session.open(MessageBox,
811                                                           cur[0][0] + "\n\n" + fullname.replace(", ","\n"),
812                                                           type = MessageBox.TYPE_INFO)
813                                 else:
814                                         self.session.open(MessageBox,
815                                                           cur[0][0],
816                                                           type = MessageBox.TYPE_INFO)
817
818                 def delete(self):
819                         cur = self["entries"].getCurrent()
820                         if cur and cur[0]:
821                                 print "[FritzCallPhonebook] displayPhonebook/delete " + cur[0][0]
822                                 self.session.openWithCallback(
823                                         self.deleteConfirmed,
824                                         MessageBox,
825                                         _("Do you really want to delete entry for\n\n%(number)s\n\n%(name)s?") 
826                                         % { 'number':str(cur[0][0]), 'name':str(cur[0][1]).replace(", ","\n") }
827                                 )
828                         else:
829                                 self.session.open(MessageBox,_("No entry selected"), MessageBox.TYPE_INFO)
830
831                 def deleteConfirmed(self, ret):
832                         print "[FritzCallPhonebook] displayPhonebook/deleteConfirmed"
833                         #
834                         # if ret: delete number from sortlist, delete number from phonebook.phonebook and write it to disk
835                         #
836                         cur = self["entries"].getCurrent()
837                         if cur:
838                                 if ret:
839                                         # delete number from sortlist, delete number from phonebook.phonebook and write it to disk
840                                         print "[FritzCallPhonebook] displayPhonebook/deleteConfirmed: remove " +cur[0][0]
841                                         phonebook.remove(cur[0][0])
842                                         self.display()
843                                 # else:
844                                         # self.session.open(MessageBox, _("Not deleted."), MessageBox.TYPE_INFO)
845                         else:
846                                 self.session.open(MessageBox,_("No entry selected"), MessageBox.TYPE_INFO)
847
848                 def add(self, number = ""):
849                         class addScreen(Screen, ConfigListScreen):
850                                 '''ConfiglistScreen with two ConfigTexts for Name and Number'''
851                                 # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
852                                 skin = """
853                                         <screen position="100,150" size="570,130" title="%s" >
854                                         <widget name="config" position="5,5" size="560,75" scrollbarMode="showOnDemand" />
855                                         <ePixmap position="145,85" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
856                                         <ePixmap position="285,85" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
857                                         <widget name="key_red" position="145,85" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
858                                         <widget name="key_green" position="285,85" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
859                                         </screen>"""  % _("Add entry to phonebook")
860
861                                 def __init__(self, session, parent, number = ""):
862                                         #
863                                         # setup screen with two ConfigText and OK and ABORT button
864                                         # 
865                                         Screen.__init__(self, session)
866                                         self.session = session
867                                         self.parent = parent
868                                         # TRANSLATORS: keep it short, this is a button
869                                         self["key_red"] = Button(_("Cancel"))
870                                         # TRANSLATORS: keep it short, this is a button
871                                         self["key_green"] = Button(_("OK"))
872                                         self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
873                                         {
874                                                 "cancel": self.cancel,
875                                                 "red": self.cancel,
876                                                 "green": self.add,
877                                                 "ok": self.add,
878                                         }, -2)
879
880                                         self.list = [ ]
881                                         ConfigListScreen.__init__(self, self.list, session = session)
882                                         config.plugins.FritzCall.name.value = ""
883                                         config.plugins.FritzCall.number.value = number
884                                         self.list.append(getConfigListEntry(_("Name"), config.plugins.FritzCall.name))
885                                         self.list.append(getConfigListEntry(_("Number"), config.plugins.FritzCall.number))
886                                         self["config"].list = self.list
887                                         self["config"].l.setList(self.list)
888
889
890                                 def add(self):
891                                         # get texts from Screen
892                                         # add (number,name) to sortlist and phonebook.phonebook and disk
893                                         self.number = config.plugins.FritzCall.number.value
894                                         self.name = config.plugins.FritzCall.name.value
895                                         if not self.number or not self.name:
896                                                 self.session.open(MessageBox, _("Entry incomplete."), type = MessageBox.TYPE_ERROR)
897                                                 return
898                                         # add (number,name) to sortlist and phonebook.phonebook and disk
899                                         oldname = phonebook.search(self.number)
900                                         if oldname:
901                                                 self.session.openWithCallback(
902                                                         self.overwriteConfirmed,
903                                                         MessageBox,
904                                                         _("Do you really want to overwrite entry for\n%(number)s\n\n%(name)s\n\nwith\n\n%(newname)s?")
905                                                         % {
906                                                         'number':self.number,
907                                                         'name': oldname,
908                                                         'newname':self.name.replace(", ","\n")
909                                                         }
910                                                         )
911                                                 self.close()
912                                                 return
913                                         phonebook.add(self.number, self.name)
914                                         self.close()
915                                         self.parent.display()
916
917                                 def overwriteConfirmed(self, ret):
918                                         if ret:
919                                                 phonebook.add(self.number, self.name)
920                                                 self.parent.display()
921                                         self.close()
922
923                                 def cancel(self):
924                                         self.close()
925
926                         print "[FritzCallPhonebook] displayPhonebook/add"
927                         self.session.open(addScreen, self, number)
928                         # self.session.open(MessageBox, "Not yet implemented.", type = MessageBox.TYPE_INFO)
929                         # return
930
931                 def edit(self):
932                         # Edit selected Timer
933                         class editScreen(Screen, ConfigListScreen):
934                                 '''ConfiglistScreen with two ConfigTexts for Name and Number'''
935                                 # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
936                                 skin = """
937                                         <screen position="100,150" size="570,130" title="%s" >
938                                         <widget name="config" position="5,5" size="560,75" scrollbarMode="showOnDemand" />
939                                         <ePixmap position="145,85" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
940                                         <ePixmap position="285,85" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
941                                         <widget name="key_red" position="145,85" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
942                                         <widget name="key_green" position="285,85" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
943                                         </screen>""" % _("Edit phonebook entry").decode("utf-8")
944
945                                 def __init__(self, session, parent, name, number):
946                                         #
947                                         # setup screen with two ConfigText and OK and ABORT button
948                                         # 
949                                         Screen.__init__(self, session)
950                                         self.session = session
951                                         self.parent = parent
952                                         # TRANSLATORS: keep it short, this is a button
953                                         self["key_red"] = Button(_("Cancel"))
954                                         # TRANSLATORS: keep it short, this is a button
955                                         self["key_green"] = Button(_("OK"))
956                                         self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
957                                         {
958                                                 "cancel": self.cancel,
959                                                 "red": self.cancel,
960                                                 "green": self.edit,
961                                                 "ok": self.edit,
962                                         }, -2)
963
964                                         self.list = [ ]
965                                         ConfigListScreen.__init__(self, self.list, session = session)
966                                         # config.plugins.FritzCall.name.value = config.plugins.FritzCall.name.value.replace(", ","\n")
967                                         self.name = name
968                                         self.number = number
969                                         config.plugins.FritzCall.name.value = name
970                                         config.plugins.FritzCall.number.value = number
971                                         self.list.append(getConfigListEntry(_("Name"), config.plugins.FritzCall.name))
972                                         self.list.append(getConfigListEntry(_("Number"), config.plugins.FritzCall.number))
973                                         self["config"].list = self.list
974                                         self["config"].l.setList(self.list)
975
976
977                                 def edit(self):
978                                         print "[FritzCallPhonebook] displayPhonebook/edit: add (%s,%s)" %(config.plugins.FritzCall.number.value,config.plugins.FritzCall.name.value)
979                                         self.newname = config.plugins.FritzCall.name.value.replace("\n",", ")
980                                         self.newnumber = config.plugins.FritzCall.number.value
981                                         if not self.number or not self.name:
982                                                 self.session.open(MessageBox, _("Entry incomplete."), type = MessageBox.TYPE_ERROR)
983                                                 return
984                                         if self.number != self.newnumber:
985                                                 if phonebook.search(self.newnumber):
986                                                         self.session.openWithCallback(
987                                                                 self.overwriteConfirmed,
988                                                                 MessageBox,
989                                                                 _("Do you really want to overwrite entry for\n%(number)s\n\n%(name)s\n\nwith\n\n%(newname)s?")
990                                                                 % {
991                                                                 'number':self.newnumber,
992                                                                 'name':phonebook.search(self.newnumber).replace(", ","\n"),
993                                                                 'newname': self.newname
994                                                                 }
995                                                                 )
996                                                         self.close()
997                                                         return
998                                                 else:
999                                                         phonebook.remove(self.number)
1000                                         phonebook.add(self.newnumber, self.newname)
1001                                         self.close()
1002                                         self.parent.display()
1003
1004                                 def overwriteConfirmed(self, ret):
1005                                         if ret:
1006                                                 phonebook.add(self.newnumber, self.newname)
1007                                                 self.parent.display()
1008                                         self.close()
1009                                                 
1010
1011                                 def cancel(self):
1012                                         self.close()
1013
1014                         print "[FritzCallPhonebook] displayPhonebook/edit"
1015                         # self.session.open(MessageBox, "Not yet implemented.", type = MessageBox.TYPE_INFO)
1016                         # return
1017                         cur = self["entries"].getCurrent()
1018                         if cur is None:
1019                                 self.session.open(MessageBox,_("No entry selected"), MessageBox.TYPE_INFO)
1020                         else:
1021                                 (number, name) = cur[0]
1022                                 self.session.open(editScreen, self, str(name), str(number))
1023
1024                 def search(self):
1025                         print "[FritzCallPhonebook] displayPhonebook/search"
1026                         self.help_window = self.session.instantiateDialog(NumericalTextInputHelpDialog, self)
1027                         self.help_window.show()
1028                         self.session.openWithCallback(self.doSearch, InputBox, _("Enter Search Terms"), _("Search phonebook"))
1029
1030                 def doSearch(self, searchTerms):
1031                         if not searchTerms: searchTerms = ""
1032                         print "[FritzCallPhonebook] displayPhonebook/doSearch: " + searchTerms
1033                         if self.help_window:
1034                                 self.session.deleteDialog(self.help_window)
1035                                 self.help_window = None
1036                         self.display(searchTerms)
1037
1038                 def exit(self):
1039                         self.close()
1040
1041 phonebook = FritzCallPhonebook()
1042
1043
1044 class FritzCallSetup(Screen, ConfigListScreen, HelpableScreen):
1045         # TRANSLATORS: this is a window title. Avoid the use of non ascii chars
1046         skin = """
1047                 <screen position="100,90" size="570,420" title="%s" >
1048                 <widget name="config" position="5,10" size="560,300" scrollbarMode="showOnDemand" />
1049                 <widget name="consideration" position="20,320" font="Regular;20" halign="center" size="510,50" />
1050                 <ePixmap position="5,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
1051                 <ePixmap position="145,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
1052                 <ePixmap position="285,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
1053                 <ePixmap position="425,375" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
1054                 <widget name="key_red" position="5,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1055                 <widget name="key_green" position="145,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1056                 <widget name="key_yellow" position="285,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1057                 <widget name="key_blue" position="425,375" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1058                 </screen>""" % _("FritzCall Setup")
1059
1060         def __init__(self, session, args = None):
1061
1062                 Screen.__init__(self, session)
1063                 HelpableScreen.__init__(self)
1064                 self.session = session
1065
1066                 self["consideration"] = Label(_("You need to enable the monitoring on your FRITZ!Box by dialing #96*5*!"))
1067                 self.list = []
1068
1069                 # Initialize Buttons
1070                 # TRANSLATORS: keep it short, this is a button
1071                 self["key_red"] = Button(_("Cancel"))
1072                 # TRANSLATORS: keep it short, this is a button
1073                 self["key_green"] = Button(_("OK"))
1074                 # TRANSLATORS: keep it short, this is a button
1075                 self["key_yellow"] = Button(_("Phone calls"))
1076                 # TRANSLATORS: keep it short, this is a button
1077                 self["key_blue"] = Button(_("Phonebook"))
1078
1079                 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
1080                 {
1081                         "red": self.cancel,
1082                         "green": self.save,
1083                         "yellow": self.displayCalls,
1084                         "blue": self.displayPhonebook,
1085                         "cancel": self.cancel,
1086                         "save": self.save,
1087                         "ok": self.save,
1088                 }, -2)
1089
1090                 # TRANSLATORS: this is a help text, keep it short
1091                 self.helpList.append((self["setupActions"], "SetupActions", [("ok", _("save and quit"))]))
1092                 # TRANSLATORS: this is a help text, keep it short
1093                 self.helpList.append((self["setupActions"], "SetupActions", [("save", _("save and quit"))]))
1094                 # TRANSLATORS: this is a help text, keep it short
1095                 self.helpList.append((self["setupActions"], "SetupActions", [("cancel", _("quit"))]))
1096                 # TRANSLATORS: this is a help text, keep it short
1097                 self.helpList.append((self["setupActions"], "ColorActions", [("red", _("quit"))]))
1098                 # TRANSLATORS: this is a help text, keep it short
1099                 self.helpList.append((self["setupActions"], "ColorActions", [("green", _("save and quit"))]))
1100                 # TRANSLATORS: this is a help text, keep it short
1101                 self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("display calls"))]))
1102                 # TRANSLATORS: this is a help text, keep it short
1103                 self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("display phonebook"))]))
1104
1105                 ConfigListScreen.__init__(self, self.list, session = session)
1106                 self.createSetup()
1107
1108
1109         def keyLeft(self):
1110                 ConfigListScreen.keyLeft(self)
1111                 self.createSetup()
1112
1113         def keyRight(self):
1114                 ConfigListScreen.keyRight(self)
1115                 self.createSetup()
1116
1117         def createSetup(self):
1118                 self.list = [ ]
1119                 self.list.append(getConfigListEntry(_("Call monitoring"), config.plugins.FritzCall.enable))
1120                 if config.plugins.FritzCall.enable.value:
1121                         self.list.append(getConfigListEntry(_("FRITZ!Box FON address (Name or IP)"), config.plugins.FritzCall.hostname))
1122
1123                         self.list.append(getConfigListEntry(_("Show after Standby"), config.plugins.FritzCall.afterStandby))
1124
1125                         self.list.append(getConfigListEntry(_("Show Calls for specific MSN"), config.plugins.FritzCall.filter))
1126                         if config.plugins.FritzCall.filter.value:
1127                                 self.list.append(getConfigListEntry(_("MSN to show (separated by ,)"), config.plugins.FritzCall.filtermsn))
1128
1129                         self.list.append(getConfigListEntry(_("Show Outgoing Calls"), config.plugins.FritzCall.showOutgoing))
1130                         if config.plugins.FritzCall.showOutgoing.value:
1131                                 self.list.append(getConfigListEntry(_("Areacode to add to Outgoing Calls (if necessary)"), config.plugins.FritzCall.prefix))
1132                         self.list.append(getConfigListEntry(_("Timeout for Call Notifications (seconds)"), config.plugins.FritzCall.timeout))
1133                         self.list.append(getConfigListEntry(_("Reverse Lookup Caller ID (select country below)"), config.plugins.FritzCall.lookup))
1134                         if config.plugins.FritzCall.lookup.value:
1135                                 self.list.append(getConfigListEntry(_("Country"), config.plugins.FritzCall.country))
1136
1137                         self.list.append(getConfigListEntry(_("Password Accessing FRITZ!Box"), config.plugins.FritzCall.password))
1138                         self.list.append(getConfigListEntry(_("Read PhoneBook from FRITZ!Box"), config.plugins.FritzCall.fritzphonebook))
1139                         if config.plugins.FritzCall.fritzphonebook.value:
1140                                 self.list.append(getConfigListEntry(_("Append type of number"), config.plugins.FritzCall.showType))
1141                                 self.list.append(getConfigListEntry(_("Append shortcut number"), config.plugins.FritzCall.showShortcut))
1142                                 self.list.append(getConfigListEntry(_("Append vanity name"), config.plugins.FritzCall.showVanity))
1143
1144                         self.list.append(getConfigListEntry(_("Use internal PhoneBook"), config.plugins.FritzCall.phonebook))
1145                         if config.plugins.FritzCall.phonebook.value:
1146                                 self.list.append(getConfigListEntry(_("PhoneBook Location"), config.plugins.FritzCall.phonebookLocation))
1147                                 if config.plugins.FritzCall.lookup.value:
1148                                         self.list.append(getConfigListEntry(_("Automatically add new Caller to PhoneBook"), config.plugins.FritzCall.addcallers))
1149
1150                         self.list.append(getConfigListEntry(_("Strip Leading 0"), config.plugins.FritzCall.internal))
1151                         # self.list.append(getConfigListEntry(_("Default display mode for FRITZ!Box calls"), config.plugins.FritzCall.fbfCalls))
1152
1153                 self["config"].list = self.list
1154                 self["config"].l.setList(self.list)
1155
1156         def save(self):
1157 #               print "[FritzCallSetup] save"
1158                 for x in self["config"].list:
1159                         x[1].save()
1160                 if fritz_call is not None:
1161                         fritz_call.connect()
1162                         print "[FritzCallSetup] called phonebook.reload()"
1163                         phonebook.reload()
1164
1165                 self.close()
1166
1167         def cancel(self):
1168 #               print "[FritzCallSetup] cancel"
1169                 for x in self["config"].list:
1170                         x[1].cancel()
1171                 self.close()
1172
1173         def displayCalls(self):
1174                 self.session.open(FritzDisplayCalls)
1175
1176         def displayPhonebook(self):
1177                 self.session.open(phonebook.displayPhonebook)
1178
1179
1180 standbyMode = False
1181
1182 class FritzCallList:
1183         def __init__(self):
1184                 self.callList = [ ]
1185         
1186         def add(self, event, date, number, caller, phone):
1187                 print "[FritzCallList] add"
1188                 if len(self.callList) > 10:
1189                         if self.callList[0] != "Start":
1190                                 self.callList[0] = "Start"
1191                         del self.callList[1]
1192
1193                 self.callList.append((event, number, date, caller, phone))
1194         
1195         def display(self):
1196                 print "[FritzCallList] display"
1197                 global standbyMode
1198                 global my_global_session
1199                 standbyMode = False
1200                 # Standby.inStandby.onClose.remove(self.display) object does not exist anymore...
1201                 # build screen from call list
1202                 text = "\n"
1203                 if self.callList[0] == "Start":
1204                         text = text + _("Last 10 calls:\n")
1205                         del self.callList[0]
1206
1207                 for call in self.callList:
1208                         (event, number, date, caller, phone) = call
1209                         if event == "RING":
1210                                 direction = "->"
1211                         else:
1212                                 direction = "<-"
1213
1214                         # shorten the date info
1215                         found = re.match(".*(\d\d.\d\d.)\d\d( \d\d:\d\d)", date)
1216                         if found: date = found.group(1) + found.group(2)
1217
1218                         # our phone could be of the form "0123456789 (home)", then we only take "home"
1219                         found = re.match(".*\((.*)\)", phone)
1220                         if found: phone = found.group(1)
1221
1222                         #  if we have an unknown number, show the number
1223                         if caller == _("UNKNOWN") and number != "":
1224                                 caller = number
1225                         else:
1226                                 # strip off the address part of the remote number, if there is any
1227                                 found = re.match("(.*)\n.*", caller)
1228                                 if found: caller = found.group(1)
1229
1230                         while (len(caller) + len(phone)) > 40:
1231                                 if len(caller) > len(phone):
1232                                         caller = caller[:-1]
1233                                 else:
1234                                         phone = phone[:-1]
1235
1236                         text = text + "%s %s %s %s\n" %(date, caller, direction, phone)
1237
1238                 print "[FritzCallList] display: '%s %s %s %s'" %(date, caller, direction, phone)
1239                 # display screen
1240                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO)
1241                 # my_global_session.open(FritzDisplayCalls, text) # TODO please HELP: from where can I get a session?
1242                 self.callList = [ ]
1243                 self.text = ""
1244
1245 callList = FritzCallList()
1246
1247 def notifyCall(event, date, number, caller, phone):
1248         if Standby.inStandby is None or config.plugins.FritzCall.afterStandby.value == "each":
1249                 # TODO: mute audio and/or stop tv/video
1250                 if event == "RING":
1251                         text = _("Incoming Call on %(date)s from\n---------------------------------------------\n%(number)s\n%(caller)s\n---------------------------------------------\nto: %(phone)s") % { 'date':date, 'number':number, 'caller':caller, 'phone':phone }
1252                 else:
1253                         text = _("Outgoing Call on %(date)s to\n---------------------------------------------\n%(number)s\n%(caller)s\n---------------------------------------------\nfrom: %(phone)s") % { 'date':date, 'number':number, 'caller':caller, 'phone':phone }
1254                 print "[FritzCall] notifyCall:\n%s" %text
1255                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
1256         elif config.plugins.FritzCall.afterStandby.value == "inList":
1257                 #
1258                 # if not yet done, register function to show call list
1259                 global standbyMode
1260                 if not standbyMode :
1261                         standbyMode = True
1262                         Standby.inStandby.onHide.append(callList.display)
1263                 # add text/timeout to call list
1264                 callList.add(event, date, number, caller, phone)
1265                 print "[FritzCall] notifyCall: added to callList"
1266         else: # this is the "None" case
1267                 print "[FritzCall] notifyCall: standby and no show"
1268
1269
1270 #===============================================================================
1271 #               We need a separate class for each invocation of reverseLookup to retain
1272 #               the necessary data for the notification
1273 #===============================================================================
1274
1275 countries = { }
1276
1277 class FritzReverseLookupAndNotifier:
1278         def __init__(self, event, number, caller, phone, date):
1279                 print "[FritzReverseLookupAndNotifier] reverse Lookup for %s!" %number
1280                 self.event = event
1281                 self.number = number
1282                 self.caller = caller
1283                 self.phone = phone
1284                 self.date = date
1285                 self.currentWebsite = None
1286                 self.nextWebsiteNo = 0
1287
1288                 if not countries:
1289                         dom = parse(resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/reverselookup.xml"))
1290                         for top in dom.getElementsByTagName("reverselookup"):
1291                                 for country in top.getElementsByTagName("country"):
1292                                         code = country.getAttribute("code").replace("+","00")
1293                                         countries[code] = country.getElementsByTagName("website")
1294
1295                 self.countrycode = config.plugins.FritzCall.country.value
1296
1297                 if number[0] != "0":
1298                         self.caller = _("UNKNOWN")
1299                         self.notifyAndReset()
1300                         return
1301
1302                 if self.number[:2] == "00":
1303                         if countries.has_key(self.number[:3]):   #      e.g. USA
1304                                 self.countrycode = self.number[:3]
1305                         elif countries.has_key(self.number[:4]):
1306                                 self.countrycode = self.number[:4]
1307                         elif countries.has_key(self.number[:5]):
1308                                 self.countrycode = self.number[:5]
1309                         else:
1310                                 print "[FritzReverseLookupAndNotifier] Country cannot be reverse handled"
1311                                 self.caller = _("UNKNOWN")
1312                                 self.notifyAndReset()
1313                                 return
1314
1315                 if countries.has_key(self.countrycode):
1316                         print "[FritzReverseLookupAndNotifier] Found website for reverse lookup"
1317                         self.websites = countries[self.countrycode]
1318                         self.nextWebsiteNo = 1
1319                         self.handleWebsite(self.websites[0])
1320                 else:
1321                         print "[FritzReverseLookupAndNotifier] Country cannot be reverse handled"
1322                         self.caller = _("UNKNOWN")
1323                         self.notifyAndReset()
1324                         return
1325
1326         def handleWebsite(self, website):
1327                 print "[FritzReverseLookupAndNotifier] handleWebsite: " + website.getAttribute("name")
1328                 if self.number[:2] == "00":
1329                         number = website.getAttribute("prefix") + self.number.replace(self.countrycode,"")
1330                 else:
1331                         number = self.number
1332
1333                 url = website.getAttribute("url")
1334                 if re.search('$AREACODE',url) or re.search('$PFXAREACODE',url):
1335                         print "[FritzReverseLookupAndNotifier] handleWebsite: (PFX)ARECODE cannot be handled"
1336                         self.caller = _("UNKNOWN")
1337                         self.notifyAndReset()
1338                         return
1339                 #
1340                 # Apparently, there is no attribute called (pfx)areacode anymore
1341                 # So, this below will not work.
1342                 #
1343                 if re.search('\\$AREACODE',url) and website.hasAttribute("areacode"):
1344                         areaCodeLen = int(website.getAttribute("areacode"))
1345                         url = url.replace("$AREACODE","%(areacode)s").replace("$NUMBER","%(number)s")
1346                         url = url %{ 'areacode':number[:areaCodeLen], 'number':number[areaCodeLen:] }
1347                 elif re.search('\\$PFXAREACODE',url) and website.hasAttribute("pfxareacode"):
1348                         areaCodeLen = int(website.getAttribute("pfxareacode"))
1349                         url = url.replace("$PFXAREACODE","%(pfxareacode)s").replace("$NUMBER","%(number)s")
1350                         url = url %{ 'pfxareacode':number[:areaCodeLen], 'number':number[areaCodeLen:] }
1351                 elif re.search('\\$NUMBER',url): 
1352                         url = url.replace("$NUMBER","%s") %number
1353                 else:
1354                         print "[FritzReverseLookupAndNotifier] handleWebsite: cannot handle websites with no $NUMBER in url"
1355                         self.caller = _("UNKNOWN")
1356                         self.notifyAndReset()
1357                         return
1358                 print "[FritzReverseLookupAndNotifier] Url to query: " + url
1359                 url = url.encode("UTF-8", "replace")
1360                 self.currentWebsite = website
1361                 getPage(url, method="GET").addCallback(self._gotPage).addErrback(self._gotError)
1362
1363         def _gotPage(self, page):
1364                 print "[FritzReverseLookupAndNotifier] _gotPage"
1365                 found = re.match('.*content=".*?charset=([^"]+)"',page,re.S)
1366                 if found:
1367                         print "[FritzReverseLookupAndNotifier] Charset: " + found.group(1)
1368                         page = page.replace("\xa0"," ").decode(found.group(1), "replace")
1369                 else:
1370                         page = page.replace("\xa0"," ").decode("ISO-8859-1", "replace")
1371
1372                 for entry in self.currentWebsite.getElementsByTagName("entry"):
1373                         # print "[FritzReverseLookupAndNotifier] _gotPage: try entry"
1374                         details = []
1375                         for what in ["name", "street", "city", "zipcode"]:
1376                                 pat = "(.*)" + self.getPattern(entry, what)
1377                                 # print "[FritzReverseLookupAndNotifier] _gotPage: look for '''%s''' with '''%s'''" %( what, pat )
1378                                 found = re.match(pat, page, re.S|re.M)
1379                                 if found:
1380                                         # print "[FritzReverseLookupAndNotifier] _gotPage: found for '''%s''': '''%s'''" %( what, found.group(2) )
1381                                         item = found.group(2).replace("&nbsp;"," ").replace("</b>","").replace(",","")
1382                                         item = html2utf8(item)
1383                                         newitem = item.replace("  ", " ")
1384                                         while newitem != item:
1385                                                 item = newitem
1386                                                 newitem = item.replace("  ", " ")
1387                                         details.append(item.strip())
1388                                         # print "[FritzReverseLookupAndNotifier] _gotPage: got '''%s''': '''%s'''" %( what, item.strip() )
1389                                 else:
1390                                         break
1391
1392                         if len(details) != 4:
1393                                 continue
1394                         else:
1395                                 name = details[0]
1396                                 address =  details[1] + ", " + details[3] + " " + details[2]
1397                                 print "[FritzReverseLookupAndNotifier] _gotPage: Reverse lookup succeeded:\nName: %s\nAddress: %s" %(name, address)
1398                                 self.caller = "%s, %s" %(name, address)
1399                                 if self.number != 0 and config.plugins.FritzCall.addcallers.value and self.event == "RING":
1400                                         phonebook.add(self.number, self.caller)
1401
1402                                 self.caller = self.caller.replace(", ", "\n").encode("UTF-8", "replace")
1403                                 self.notifyAndReset()
1404                                 return True
1405                                 break
1406                 else:
1407                         self._gotError("[FritzReverseLookupAndNotifier] _gotPage: Nothing found at %s" %self.currentWebsite.getAttribute("name"))
1408                         
1409         def _gotError(self, error = ""):
1410                 print "[FritzReverseLookupAndNotifier] _gotError - Error: %s" %error
1411                 if self.nextWebsiteNo >= len(self.websites):
1412                         print "[FritzReverseLookupAndNotifier] _gotError: I give up"
1413                         self.caller = _("UNKNOWN")
1414                         self.notifyAndReset()
1415                         return
1416                 else:
1417                         print "[FritzReverseLookupAndNotifier] _gotError: try next website"
1418                         self.nextWebsiteNo = self.nextWebsiteNo+1
1419                         self.handleWebsite(self.websites[self.nextWebsiteNo-1])
1420
1421         def getPattern(self, website, which):
1422                 pat1 = website.getElementsByTagName(which)
1423                 if len(pat1) > 1:
1424                         print "Something strange: more than one %s for website %s" %(which, website.getAttribute("name"))
1425                 return pat1[0].childNodes[0].data
1426
1427         def notifyAndReset(self, timeout=config.plugins.FritzCall.timeout.value):
1428                 notifyCall(self.event, self.date, self.number, self.caller, self.phone)
1429                 # kill that object...
1430
1431
1432 class FritzProtocol(LineReceiver):
1433         def __init__(self):
1434                 print "[FritzProtocol] __init__"
1435                 self.resetValues()
1436
1437         def resetValues(self):
1438                 print "[FritzProtocol] resetValues"
1439                 self.number = '0'
1440                 self.caller = None
1441                 self.phone = None
1442                 self.date = '0'
1443
1444         def notifyAndReset(self, timeout=config.plugins.FritzCall.timeout.value):
1445                 notifyCall(self.event, self.date, self.number, self.caller, self.phone)
1446                 self.resetValues()
1447
1448         def lineReceived(self, line):
1449                 print "[FritzProtocol] lineReceived: %s" %line
1450 #15.07.06 00:38:54;CALL;1;4;<from/extern>;<to/our msn>;
1451 #15.07.06 00:38:58;DISCONNECT;1;0;
1452 #15.07.06 00:39:22;RING;0;<from/extern>;<to/our msn>;
1453 #15.07.06 00:39:27;DISCONNECT;0;0;
1454                 a = line.split(';')
1455                 (self.date, self.event) = a[0:2]
1456
1457                 if self.event == "RING" or (self.event == "CALL" and config.plugins.FritzCall.showOutgoing.value):
1458                         phone = a[4]
1459
1460                         if self.event == "RING":
1461                                 number = a[3] 
1462                         else:
1463                                 number = a[5]
1464                                 
1465                         print "[FritzProtocol] lineReceived phone: '''%s''' number: '''%s'''" % (phone, number)
1466
1467                         filtermsns = config.plugins.FritzCall.filtermsn.value.split(",")
1468                         for i in range(len(filtermsns)):
1469                                 filtermsns[i] = filtermsns[i].strip()
1470                         if not (config.plugins.FritzCall.filter.value and phone not in filtermsns):
1471                                 print "[FritzProtocol] lineReceived no filter hit"
1472                                 phonename = phonebook.search(phone)                # do we have a name for the number of our side?
1473                                 if phonename is not None:
1474                                         self.phone = "%s (%s)" %(phone, phonename)
1475                                 else:
1476                                         self.phone = phone
1477
1478                                 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0]=="0":
1479                                         print "[FritzProtocol] lineReceived: strip leading 0"
1480                                         self.number = number[1:]
1481                                 else:
1482                                         self.number = number
1483                                         if self.event == "CALL" and self.number[0] != '0':                                        # should only happen for outgoing
1484                                                 print "[FritzProtocol] lineReceived: add local prefix"
1485                                                 self.number = config.plugins.FritzCall.prefix.value + self.number
1486
1487                                 # check, whether we are in Germany and number has call-by-call prefix. If so strip it
1488                                 if self.event == "CALL" and config.plugins.FritzCall.country.value == '0049':
1489                                         if re.match('^0100\d\d', self.number):
1490                                                 print "[FritzProtocol] lineReceived: strip CbC 0100.. prefix"
1491                                                 self.number = self.number[6:]
1492                                         elif re.match('^010\d\d', self.number):
1493                                                 print "[FritzProtocol] lineReceived: strip CbC 010.. prefix"
1494                                                 self.number = self.number[5:]
1495
1496                                 if self.number is not "":
1497                                         print "[FritzProtocol] lineReceived phonebook.search: %s" %self.number
1498                                         self.caller = phonebook.search(self.number)
1499                                         print "[FritzProtocol] lineReceived phonebook.search reault: %s" %self.caller
1500                                         if (self.caller is None) and config.plugins.FritzCall.lookup.value:
1501                                                 FritzReverseLookupAndNotifier(self.event, self.number, self.caller, self.phone, self.date)
1502                                                 return                                                  # reverselookup is supposed to handle the message itself 
1503
1504                                 if self.caller is None:
1505                                         self.caller = _("UNKNOWN")
1506
1507                                 self.notifyAndReset()
1508
1509 class FritzClientFactory(ReconnectingClientFactory):
1510         initialDelay = 20
1511         maxDelay = 500
1512
1513         def __init__(self):
1514                 self.hangup_ok = False
1515
1516         def startedConnecting(self, connector):
1517                 Notifications.AddNotification(MessageBox, _("Connecting to FRITZ!Box..."), type=MessageBox.TYPE_INFO, timeout=2)
1518
1519         def buildProtocol(self, addr):
1520                 Notifications.AddNotification(MessageBox, _("Connected to FRITZ!Box!"), type=MessageBox.TYPE_INFO, timeout=4)
1521                 self.resetDelay()
1522                 return FritzProtocol()
1523
1524         def clientConnectionLost(self, connector, reason):
1525                 if not self.hangup_ok:
1526                         Notifications.AddNotification(MessageBox, _("Connection to FRITZ!Box! lost\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
1527                 ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
1528
1529         def clientConnectionFailed(self, connector, reason):
1530                 Notifications.AddNotification(MessageBox, _("Connecting to FRITZ!Box failed\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
1531                 ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
1532
1533 class FritzCall:
1534         def __init__(self):
1535                 self.dialog = None
1536                 self.d = None
1537                 self.connect()
1538
1539         def connect(self):
1540                 self.abort()
1541                 if config.plugins.FritzCall.enable.value:
1542                         f = FritzClientFactory()
1543                         self.d = (f, reactor.connectTCP(config.plugins.FritzCall.hostname.value, 1012, f))
1544
1545         def shutdown(self):
1546                 self.abort()
1547
1548         def abort(self):
1549                 if self.d is not None:
1550                         self.d[0].hangup_ok = True
1551                         self.d[0].stopTrying()
1552                         self.d[1].disconnect()
1553                         self.d = None
1554
1555 def displayCalls(session, servicelist):
1556         session.open(FritzDisplayCalls)
1557
1558 def displayPhonebook(session, servicelist):
1559         session.open(phonebook.displayPhonebook)
1560
1561 def main(session):
1562         session.open(FritzCallSetup)
1563
1564 fritz_call = None
1565
1566 def autostart(reason, **kwargs):
1567         global fritz_call
1568
1569         # ouch, this is a hack
1570         if kwargs.has_key("session"):
1571                 global my_global_session
1572                 my_global_session = kwargs["session"]
1573                 return
1574
1575         print "[FRITZ!Call] - Autostart"
1576         if reason == 0:
1577                 fritz_call = FritzCall()
1578         elif reason == 1:
1579                 fritz_call.shutdown()
1580                 fritz_call = None
1581
1582 def Plugins(**kwargs):
1583         what = _("Display FRITZ!box-Fon calls on screen")
1584         what_calls = _("Phone calls")
1585         what_phonebook = _("Phonebook")
1586         return [ PluginDescriptor(name="FritzCall", description=what, where = PluginDescriptor.WHERE_PLUGINMENU, icon = "plugin.png", fnc=main),
1587                 PluginDescriptor(name=what_calls, description=what_calls, where = PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=displayCalls),
1588                 PluginDescriptor(name=what_phonebook, description=what_phonebook, where = PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=displayPhonebook),
1589                 PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart) ]