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