ADD: user exit for notification: FritzCallUserAction.sh in same dir as
[enigma2-plugins.git] / fritzcall / src / plugin.py
1 # -*- coding: utf-8 -*-
2 '''
3 $Author: michael $
4 $Revision: 628 $
5 $Date: 2010-12-30 11:13:18 +0100 (Do, 30 Dez 2010) $
6 $Id: plugin.py 628 2010-12-30 10:13:18Z michael $
7 '''
8 from Screens.Screen import Screen
9 from Screens.MessageBox import MessageBox
10 from Screens.NumericalTextInputHelpDialog import NumericalTextInputHelpDialog
11 from Screens.InputBox import InputBox
12 from Screens import Standby
13 from Screens.HelpMenu import HelpableScreen
14
15 from enigma import eTimer, eSize, ePoint #@UnresolvedImport # pylint: disable=E0611
16 from enigma import eDVBVolumecontrol
17 from enigma import eBackgroundFileEraser
18 #BgFileEraser = eBackgroundFileEraser.getInstance()
19 #BgFileEraser.erase("blabla.txt")
20
21 from Components.ActionMap import ActionMap
22 from Components.Label import Label
23 from Components.Button import Button
24 from Components.Pixmap import Pixmap
25 from Components.Sources.List import List
26 from Components.config import config, ConfigSubsection, ConfigSelection, ConfigEnableDisable, getConfigListEntry, ConfigText, ConfigInteger
27 from Components.ConfigList import ConfigListScreen
28 from Components.Harddisk import harddiskmanager
29 try:
30         from Components.config import ConfigPassword
31 except ImportError:
32         ConfigPassword = ConfigText
33
34 from Plugins.Plugin import PluginDescriptor
35 from Tools import Notifications
36 from Tools.NumericalTextInput import NumericalTextInput
37 from Tools.Directories import resolveFilename, SCOPE_PLUGINS, SCOPE_SKIN_IMAGE, SCOPE_CONFIG, SCOPE_MEDIA
38 from Tools.LoadPixmap import LoadPixmap
39 from GlobalActions import globalActionMap # for muting
40
41 from twisted.internet import reactor #@UnresolvedImport
42 from twisted.internet.protocol import ReconnectingClientFactory #@UnresolvedImport
43 from twisted.protocols.basic import LineReceiver #@UnresolvedImport
44 from twisted.web.client import getPage #@UnresolvedImport
45
46 from urllib import urlencode 
47 import re, time, os, hashlib, traceback
48
49 from nrzuname import ReverseLookupAndNotifier, html2unicode
50 import FritzOutlookCSV, FritzLDIF
51 from . import _, initDebug, debug #@UnresolvedImport # pylint: disable=E0611,F0401
52
53 from enigma import getDesktop
54 DESKTOP_WIDTH = getDesktop(0).size().width()
55 DESKTOP_HEIGHT = getDesktop(0).size().height()
56
57 #
58 # this is pure magic.
59 # It returns the first value, if HD (1280x720),
60 # the second if SD (720x576),
61 # else something scaled accordingly
62 # if one of the parameters is -1, scale proportionally
63 #
64 def scaleH(y2, y1):
65         if y2 == -1:
66                 y2 = y1*1280/720
67         elif y1 == -1:
68                 y1 = y2*720/1280
69         return scale(y2, y1, 1280, 720, DESKTOP_WIDTH)
70 def scaleV(y2, y1):
71         if y2 == -1:
72                 y2 = y1*720/576
73         elif y1 == -1:
74                 y1 = y2*576/720
75         return scale(y2, y1, 720, 576, DESKTOP_HEIGHT)
76 def scale(y2, y1, x2, x1, x):
77         return (y2 - y1) * (x - x1) / (x2 - x1) + y1
78
79 my_global_session = None
80
81 config.plugins.FritzCall = ConfigSubsection()
82 config.plugins.FritzCall.debug = ConfigEnableDisable(default=False)
83 #config.plugins.FritzCall.muteOnCall = ConfigSelection(choices=[(None, _("no")), ("ring", _("on ring")), ("connect", _("on connect"))])
84 #config.plugins.FritzCall.muteOnCall = ConfigSelection(choices=[(None, _("no")), ("ring", _("on ring"))])
85 config.plugins.FritzCall.muteOnCall = ConfigEnableDisable(default=False)
86 config.plugins.FritzCall.hostname = ConfigText(default="fritz.box", fixed_size=False)
87 config.plugins.FritzCall.afterStandby = ConfigSelection(choices=[("none", _("show nothing")), ("inList", _("show as list")), ("each", _("show each call"))])
88 config.plugins.FritzCall.filter = ConfigEnableDisable(default=False)
89 config.plugins.FritzCall.filtermsn = ConfigText(default="", fixed_size=False)
90 config.plugins.FritzCall.filtermsn.setUseableChars('0123456789,')
91 config.plugins.FritzCall.filterCallList = ConfigEnableDisable(default=True)
92 config.plugins.FritzCall.showOutgoing = ConfigEnableDisable(default=False)
93 config.plugins.FritzCall.timeout = ConfigInteger(default=15, limits=(0, 60))
94 config.plugins.FritzCall.lookup = ConfigEnableDisable(default=False)
95 config.plugins.FritzCall.internal = ConfigEnableDisable(default=False)
96 config.plugins.FritzCall.fritzphonebook = ConfigEnableDisable(default=False)
97 config.plugins.FritzCall.phonebook = ConfigEnableDisable(default=False)
98 config.plugins.FritzCall.addcallers = ConfigEnableDisable(default=False)
99 config.plugins.FritzCall.enable = ConfigEnableDisable(default=False)
100 config.plugins.FritzCall.password = ConfigPassword(default="", fixed_size=False)
101 config.plugins.FritzCall.extension = ConfigText(default='1', fixed_size=False)
102 config.plugins.FritzCall.extension.setUseableChars('0123456789')
103 config.plugins.FritzCall.showType = ConfigEnableDisable(default=True)
104 config.plugins.FritzCall.showShortcut = ConfigEnableDisable(default=False)
105 config.plugins.FritzCall.showVanity = ConfigEnableDisable(default=False)
106 config.plugins.FritzCall.prefix = ConfigText(default="", fixed_size=False)
107 config.plugins.FritzCall.prefix.setUseableChars('0123456789')
108 config.plugins.FritzCall.connectionVerbose = ConfigEnableDisable(default=True)
109
110
111 def getMountedDevs():
112         def handleMountpoint(loc):
113                 # debug("[FritzCall] handleMountpoint: %s" %repr(loc))
114                 mp = loc[0]
115                 while mp[-1] == '/':
116                         mp = mp[:-1]
117                 #=======================================================================
118                 # if os.path.exists(os.path.join(mp, "PhoneBook.txt")):
119                 #       if os.access(os.path.join(mp, "PhoneBook.txt"), os.W_OK):
120                 #               desc = ' *'
121                 #       else:
122                 #               desc = ' -'
123                 # else:
124                 #       desc = ''
125                 # desc = loc[1] + desc
126                 #=======================================================================
127                 desc = loc[1]
128                 return (mp, desc + " (" + mp + ")")
129
130         mountedDevs = [(resolveFilename(SCOPE_CONFIG), _("Flash")),
131                                    (resolveFilename(SCOPE_MEDIA, "cf"), _("Compact Flash")),
132                                    (resolveFilename(SCOPE_MEDIA, "usb"), _("USB Device"))]
133         mountedDevs += map(lambda p: (p.mountpoint, (_(p.description) if p.description else "")), harddiskmanager.getMountedPartitions(True))
134         mediaDir = resolveFilename(SCOPE_MEDIA)
135         for p in os.listdir(mediaDir):
136                 if os.path.join(mediaDir, p) not in [path[0] for path in mountedDevs]:
137                         mountedDevs.append((os.path.join(mediaDir, p), _("Media directory")))
138         debug("[FritzCall] getMountedDevs1: %s" %repr(mountedDevs))
139         mountedDevs = filter(lambda path: os.path.isdir(path[0]) and os.access(path[0], os.W_OK|os.X_OK), mountedDevs)
140         # put this after the write/executable check, that is far too slow...
141         netDir = resolveFilename(SCOPE_MEDIA, "net")
142         if os.path.isdir(netDir):
143                 mountedDevs += map(lambda p: (os.path.join(netDir, p), _("Network mount")), os.listdir(netDir))
144         mountedDevs = map(handleMountpoint, mountedDevs)
145         return mountedDevs
146 config.plugins.FritzCall.phonebookLocation = ConfigSelection(choices=getMountedDevs())
147
148 countryCodes = [
149         ("0049", _("Germany")),
150         ("0031", _("The Netherlands")),
151         ("0033", _("France")),
152         ("0039", _("Italy")),
153         ("0041", _("Switzerland")),
154         ("0043", _("Austria"))
155         ]
156 config.plugins.FritzCall.country = ConfigSelection(choices=countryCodes)
157
158 FBF_ALL_CALLS = "."
159 FBF_IN_CALLS = "1"
160 FBF_MISSED_CALLS = "2"
161 FBF_OUT_CALLS = "3"
162 fbfCallsChoices = {FBF_ALL_CALLS: _("All calls"),
163                                    FBF_IN_CALLS: _("Incoming calls"),
164                                    FBF_MISSED_CALLS: _("Missed calls"),
165                                    FBF_OUT_CALLS: _("Outgoing calls")
166                                    }
167 config.plugins.FritzCall.fbfCalls = ConfigSelection(choices=fbfCallsChoices)
168
169 config.plugins.FritzCall.name = ConfigText(default="", fixed_size=False)
170 config.plugins.FritzCall.number = ConfigText(default="", fixed_size=False)
171 config.plugins.FritzCall.number.setUseableChars('0123456789')
172
173 phonebook = None
174 fritzbox = None
175
176 avon = {}
177
178 def initAvon():
179         avonFileName = resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/avon.dat")
180         if os.path.exists(avonFileName):
181                 for line in open(avonFileName):
182                         line = line.decode("iso-8859-1").encode('utf-8')
183                         if line[0] == '#':
184                                 continue
185                         parts = line.split(':')
186                         if len(parts) == 2:
187                                 avon[parts[0].replace('-','').replace('*','').replace('/','')] = parts[1]
188
189 def resolveNumberWithAvon(number, countrycode):
190         if not number or number[0] != '0':
191                 return ""
192                 
193         countrycode = countrycode.replace('00','+')
194         if number[:2] == '00':
195                 normNumber = '+' + number[2:]
196         elif number[:1] == '0':
197                 normNumber = countrycode + number[1:]
198         else: # this should can not happen, but safety first
199                 return ""
200         
201         # debug('normNumer: ' + normNumber)
202         for i in reversed(range(min(10, len(number)))):
203                 if avon.has_key(normNumber[:i]):
204                         return '[' + avon[normNumber[:i]].strip() + ']'
205         return ""
206
207 def handleReverseLookupResult(name):
208         found = re.match("NA: ([^;]*);VN: ([^;]*);STR: ([^;]*);HNR: ([^;]*);PLZ: ([^;]*);ORT: ([^;]*)", name)
209         if found:
210                 ( name, firstname, street, streetno, zipcode, city ) = (found.group(1),
211                                                                                                 found.group(2),
212                                                                                                 found.group(3),
213                                                                                                 found.group(4),
214                                                                                                 found.group(5),
215                                                                                                 found.group(6)
216                                                                                                 )
217                 if firstname:
218                         name += ' ' + firstname
219                 if street or streetno or zipcode or city:
220                         name += ', '
221                 if street:
222                         name += street
223                 if streetno:
224                         name += ' ' + streetno
225                 if (street or streetno) and (zipcode or city):
226                         name += ', '
227                 if zipcode and city:
228                         name += zipcode + ' ' + city
229                 elif zipcode:
230                         name += zipcode
231                 elif city:
232                         name += city
233         return name
234
235 from xml.dom.minidom import parse
236 cbcInfos = {}
237 def initCbC():
238         callbycallFileName = resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/callbycall_world.xml")
239         if os.path.exists(callbycallFileName):
240                 dom = parse(callbycallFileName)
241                 for top in dom.getElementsByTagName("callbycalls"):
242                         for cbc in top.getElementsByTagName("country"):
243                                 code = cbc.getAttribute("code").replace("+","00")
244                                 cbcInfos[code] = cbc.getElementsByTagName("callbycall")
245         else:
246                 debug("[FritzCall] initCbC: callbycallFileName does not exist?!?!")
247
248 def stripCbCPrefix(number, countrycode):
249         if number and number[:2] != "00" and cbcInfos.has_key(countrycode):
250                 for cbc in cbcInfos[countrycode]:
251                         if len(cbc.getElementsByTagName("length"))<1 or len(cbc.getElementsByTagName("prefix"))<1:
252                                 debug("[FritzCall] stripCbCPrefix: entries for " + countrycode + " %s invalid")
253                                 return number
254                         length = int(cbc.getElementsByTagName("length")[0].childNodes[0].data)
255                         prefix = cbc.getElementsByTagName("prefix")[0].childNodes[0].data
256                         # if re.match('^'+prefix, number):
257                         if number[:len(prefix)] == prefix:
258                                 return number[length:]
259         return number
260
261 class FritzAbout(Screen):
262
263         def __init__(self, session):
264                 textFieldWidth = scaleV(350, 250)
265                 width = 5 + 150 + 20 + textFieldWidth + 5 + 175 + 5
266                 height = 5 + 175 + 5 + 25 + 5
267                 self.skin = """
268                         <screen name="FritzAbout" position="center,center" size="%d,%d" title="About FritzCall" >
269                                 <widget name="text" position="175,%d" size="%d,%d" font="Regular;%d" />
270                                 <ePixmap position="5,37" size="150,110" pixmap="%s" transparent="1" alphatest="blend" />
271                                 <ePixmap position="%d,5" size="175,175" pixmap="%s" transparent="1" alphatest="blend" />
272                                 <widget name="url" position="20,185" size="%d,25" font="Regular;%d" />
273                         </screen>""" % (
274                                                         width, height, # size
275                                                         (height-scaleV(150,130)) / 2, # text vertical position
276                                                         textFieldWidth,
277                                                         scaleV(150,130), # text height
278                                                         scaleV(24,21), # text font size
279                                                         resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/fritz.png"), # 150x110
280                                                         5 + 150 + 5 + textFieldWidth + 5, # qr code horizontal offset
281                                                         resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/website.png"), # 175x175
282                                                         width-40, # url width
283                                                         scaleV(24,21) # url font size
284                                                         )
285                 Screen.__init__(self, session)
286                 self["aboutActions"] = ActionMap(["OkCancelActions"],
287                 {
288                 "cancel": self.exit,
289                 "ok": self.exit,
290                 }, -2)
291                 self["text"] = Label(
292                                                         "FritzCall Plugin" + "\n\n" +
293                                                         "$Author: michael $"[1:-2] + "\n" +
294                                                         "$Revision: 628 $"[1:-2] + "\n" + 
295                                                         "$Date: 2010-12-30 11:13:18 +0100 (Do, 30 Dez 2010) $"[1:23] + "\n"
296                                                         )
297                 self["url"] = Label("http://wiki.blue-panel.com/index.php/FritzCall")
298                 self.onLayoutFinish.append(self.setWindowTitle)
299
300         def setWindowTitle(self):
301                 # TRANSLATORS: this is a window title.
302                 self.setTitle(_("About FritzCall"))
303
304         def exit(self):
305                 self.close()
306
307 FBF_boxInfo = 0
308 FBF_upTime = 1
309 FBF_ipAddress = 2
310 FBF_wlanState = 3
311 FBF_dslState = 4
312 FBF_tamActive = 5
313 FBF_dectActive = 6
314 FBF_faxActive = 7
315 FBF_rufumlActive = 8
316
317 class FritzCallFBF:
318         def __init__(self):
319                 debug("[FritzCallFBF] __init__")
320                 self._callScreen = None
321                 self._md5LoginTimestamp = None
322                 self._md5Sid = '0000000000000000'
323                 self._callTimestamp = 0
324                 self._callList = []
325                 self._callType = config.plugins.FritzCall.fbfCalls.value
326                 self._phoneBookID = '0'
327                 self.info = None # (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive)
328                 self.getInfo(None)
329                 self.blacklist = ([], [])
330                 self.readBlacklist()
331
332         def _notify(self, text):
333                 debug("[FritzCallFBF] notify: " + text)
334                 self._md5LoginTimestamp = None
335                 if self._callScreen:
336                         debug("[FritzCallFBF] notify: try to close callScreen")
337                         self._callScreen.close()
338                         self._callScreen = None
339                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
340                         
341         def _login(self, callback=None):
342                 debug("[FritzCallFBF] _login")
343                 if self._callScreen:
344                         self._callScreen.updateStatus(_("login"))
345                 if self._md5LoginTimestamp and ((time.time() - self._md5LoginTimestamp) < float(9.5*60)) and self._md5Sid != '0000000000000000': # new login after 9.5 minutes inactivity 
346                         debug("[FritzCallFBF] _login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
347                         self._md5LoginTimestamp = time.time()
348                         callback(None)
349                 else:
350                         debug("[FritzCallFBF] _login: not logged in or outdated login")
351                         # http://fritz.box/cgi-bin/webcm?getpage=../html/login_sid.xml
352                         parms = urlencode({'getpage':'../html/login_sid.xml'})
353                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
354                         debug("[FritzCallFBF] _login: '" + url + "' parms: '" + parms + "'")
355                         getPage(url,
356                                 method="POST",
357                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
358                                                 }, postdata=parms).addCallback(lambda x: self._md5Login(callback,x)).addErrback(lambda x:self._oldLogin(callback,x))
359
360         def _oldLogin(self, callback, error): 
361                 debug("[FritzCallFBF] _oldLogin: " + repr(error))
362                 self._md5LoginTimestamp = None
363                 if config.plugins.FritzCall.password.value != "":
364                         parms = "login:command/password=%s" % (config.plugins.FritzCall.password.value)
365                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
366                         debug("[FritzCallFBF] _oldLogin: '" + url + "' parms: '" + parms + "'")
367                         getPage(url,
368                                 method="POST",
369                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
370                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
371                                                 }, postdata=parms).addCallback(self._gotPageLogin).addCallback(callback).addErrback(self._errorLogin)
372                 elif callback:
373                         debug("[FritzCallFBF] _oldLogin: no password, calling " + repr(callback))
374                         callback(None)
375
376         def _md5Login(self, callback, sidXml):
377                 def buildResponse(challenge, text):
378                         debug("[FritzCallFBF] _md5Login7buildResponse: challenge: " + challenge + ' text: ' + text)
379                         text = (challenge + '-' + text).decode('utf-8','ignore').encode('utf-16-le')
380                         for i in range(len(text)):
381                                 if ord(text[i]) > 255:
382                                         text[i] = '.'
383                         md5 = hashlib.md5()
384                         md5.update(text)
385                         debug("[FritzCallFBF] md5Login/buildResponse: " + md5.hexdigest())
386                         return challenge + '-' + md5.hexdigest()
387
388                 debug("[FritzCallFBF] _md5Login")
389                 found = re.match('.*<SID>([^<]*)</SID>', sidXml, re.S)
390                 if found:
391                         self._md5Sid = found.group(1)
392                         debug("[FritzCallFBF] _md5Login: SID "+ self._md5Sid)
393                 else:
394                         debug("[FritzCallFBF] _md5Login: no sid! That must be an old firmware.")
395                         self._oldLogin(callback, 'No error')
396                         return
397
398                 debug("[FritzCallFBF] _md5Login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
399                 self._md5LoginTimestamp = time.time()
400                 if sidXml.find('<iswriteaccess>0</iswriteaccess>') != -1:
401                         debug("[FritzCallFBF] _md5Login: logging in")
402                         found = re.match('.*<Challenge>([^<]*)</Challenge>', sidXml, re.S)
403                         if found:
404                                 challenge = found.group(1)
405                                 debug("[FritzCallFBF] _md5Login: challenge " + challenge)
406                         else:
407                                 challenge = None
408                                 debug("[FritzCallFBF] _md5Login: login necessary and no challenge! That is terribly wrong.")
409                         parms = urlencode({
410                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
411                                                         'login:command/response': buildResponse(challenge, config.plugins.FritzCall.password.value),
412                                                         })
413                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
414                         debug("[FritzCallFBF] _md5Login: '" + url + "' parms: '" + parms + "'")
415                         getPage(url,
416                                 method="POST",
417                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
418                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
419                                                 }, postdata=parms).addCallback(self._gotPageLogin).addCallback(callback).addErrback(self._errorLogin)
420                 elif callback: # we assume value 1 here, no login necessary
421                         debug("[FritzCallFBF] _md5Login: no login necessary")
422                         callback(None)
423
424         def _gotPageLogin(self, html):
425                 if self._callScreen:
426                         self._callScreen.updateStatus(_("login verification"))
427                 debug("[FritzCallFBF] _gotPageLogin: verify login")
428                 start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
429                 if start != -1:
430                         start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
431                         text = _("FRITZ!Box - Error logging in\n\n") + html[start : html.find('</p>', start)]
432                         self._notify(text)
433                 else:
434                         if self._callScreen:
435                                 self._callScreen.updateStatus(_("login ok"))
436
437                 found = re.match('.*<input type="hidden" name="sid" value="([^\"]*)"', html, re.S)
438                 if found:
439                         self._md5Sid = found.group(1)
440                         debug("[FritzCallFBF] _gotPageLogin: found sid: " + self._md5Sid)
441
442         def _errorLogin(self, error):
443                 global fritzbox
444                 debug("[FritzCallFBF] _errorLogin: %s" % (error))
445                 text = _("FRITZ!Box - Error logging in: %s\nDisabling plugin.") % error.getErrorMessage()
446                 # config.plugins.FritzCall.enable.value = False
447                 fritzbox = None
448                 self._notify(text)
449
450         def _logout(self):
451                 if self._md5LoginTimestamp:
452                         self._md5LoginTimestamp = None
453                         parms = urlencode({
454                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
455                                                         'login:command/logout':'bye bye Fritz'
456                                                         })
457                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
458                         debug("[FritzCallFBF] logout: '" + url + "' parms: '" + parms + "'")
459                         getPage(url,
460                                 method="POST",
461                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
462                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
463                                                 }, postdata=parms).addErrback(self._errorLogout)
464
465         def _errorLogout(self, error):
466                 debug("[FritzCallFBF] _errorLogout: %s" % (error))
467                 text = _("FRITZ!Box - Error logging out: %s") % error.getErrorMessage()
468                 self._notify(text)
469
470         def loadFritzBoxPhonebook(self):
471                 debug("[FritzCallFBF] loadFritzBoxPhonebook")
472                 if config.plugins.FritzCall.fritzphonebook.value:
473                         self._phoneBookID = '0'
474                         debug("[FritzCallFBF] loadFritzBoxPhonebook: logging in")
475                         self._login(self._loadFritzBoxPhonebook)
476
477         def _loadFritzBoxPhonebook(self, html):
478                 if html:
479                         #===================================================================
480                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
481                         # if found:
482                         #       self._errorLoad('Login: ' + found.group(1))
483                         #       return
484                         #===================================================================
485                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
486                         if start != -1:
487                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
488                                 self._errorLoad('Login: ' + html[start, html.find('</p>', start)])
489                                 return
490                 parms = urlencode({
491                                                 'getpage':'../html/de/menus/menu2.html',
492                                                 'var:lang':'de',
493                                                 'var:pagename':'fonbuch',
494                                                 'var:menu':'fon',
495                                                 'sid':self._md5Sid,
496                                                 'telcfg:settings/Phonebook/Books/Select':self._phoneBookID, # this selects always the first phonbook
497                                                 })
498                 url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
499                 debug("[FritzCallFBF] _loadFritzBoxPhonebook: '" + url + "' parms: '" + parms + "'")
500                 getPage(url,
501                         method="POST",
502                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
503                         headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
504                                         }, postdata=parms).addCallback(self._parseFritzBoxPhonebook).addErrback(self._errorLoad)
505
506         def _parseFritzBoxPhonebook(self, html):
507                 debug("[FritzCallFBF] _parseFritzBoxPhonebook")
508
509                 # first, let us get the charset
510                 found = re.match('.*<meta http-equiv=content-type content="text/html; charset=([^"]*)">', html, re.S)
511                 if found:
512                         charset = found.group(1)
513                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: found charset: " + charset)
514                         html = html2unicode(html.decode(charset), charset).encode('utf-8') # this looks silly, but has to be
515                 else: # this is kind of emergency conversion...
516                         try:
517                                 debug("[FritzCallFBF] _parseFritzBoxPhonebook: try charset utf-8")
518                                 charset = 'utf-8'
519                                 html = html2unicode(html.decode('utf-8'), 'utf-8').encode('utf-8') # this looks silly, but has to be
520                         except UnicodeDecodeError:
521                                 debug("[FritzCallFBF] _parseFritzBoxPhonebook: try charset iso-8859-1")
522                                 charset = 'iso-8859-1'
523                                 html = html2unicode(html.decode('iso-8859-1'), 'iso-8859-1').encode('utf-8') # this looks silly, but has to be
524
525                 # if re.search('document.write\(TrFon1\(\)', html):
526                 if html.find('document.write(TrFon1()') != -1:
527                         #===============================================================================
528                         #                                New Style: 7270 (FW 54.04.58, 54.04.63-11941, 54.04.70, 54.04.74-14371, 54.04.76, PHONE Labor 54.04.80-16624)
529                         #                                                       7170 (FW 29.04.70) 22.03.2009
530                         #                                                       7141 (FW 40.04.68) 22.03.2009
531                         #       We expect one line with TrFonName followed by several lines with
532                         #       TrFonNr(Type,Number,Shortcut,Vanity), which all belong to the name in TrFonName.
533                         #===============================================================================
534                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: discovered newer firmware")
535                         found = re.match('.*<input type="hidden" name="telcfg:settings/Phonebook/Books/Name(\d+)" value="[Dd]reambox" id="uiPostPhonebookName\d+" disabled>', html, re.S)
536                         if found:
537                                 phoneBookID = found.group(1)
538                                 debug("[FritzCallFBF] _parseFritzBoxPhonebook: found dreambox phonebook with id: " + phoneBookID)
539                                 if self._phoneBookID != phoneBookID:
540                                         self._phoneBookID = phoneBookID
541                                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: reload phonebook")
542                                         self._loadFritzBoxPhonebook(self._phoneBookID) # reload with dreambox phonebook
543                                         return
544
545                         entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]*"(?:, "[^"]*")?\);.*?)document.write\(TrFon1\(\)', re.S)
546                         entries = entrymask.finditer(html)
547                         for entry in entries:
548                                 # TrFonName (id, name, category)
549                                 # TODO: replace re.match?
550                                 found = re.match('TrFonName\("[^"]*", "([^"]+)", "[^"]*"(?:, "[^"]*")?\);', entry.group(1))
551                                 if found:
552                                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: name: %s" %found.group(1))
553                                         name = found.group(1).replace(',','').strip()
554                                 else:
555                                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: could not find name")
556                                         continue
557                                 # TrFonNr (type, rufnr, code, vanity)
558                                 detailmask = re.compile('TrFonNr\("([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\);', re.S)
559                                 details = detailmask.finditer(entry.group(1))
560                                 for found in details:
561                                         thisnumber = found.group(2).strip()
562                                         if not thisnumber:
563                                                 debug("[FritzCallFBF] Ignoring entry with empty number for '''%s'''" % (name))
564                                                 continue
565                                         else:
566                                                 thisname = name
567                                                 callType = found.group(1)
568                                                 if config.plugins.FritzCall.showType.value:
569                                                         if callType == "mobile":
570                                                                 thisname = thisname + " (" + _("mobile") + ")"
571                                                         elif callType == "home":
572                                                                 thisname = thisname + " (" + _("home") + ")"
573                                                         elif callType == "work":
574                                                                 thisname = thisname + " (" + _("work") + ")"
575
576                                                 if config.plugins.FritzCall.showShortcut.value and found.group(3):
577                                                         thisname = thisname + ", " + _("Shortcut") + ": " + found.group(3)
578                                                 if config.plugins.FritzCall.showVanity.value and found.group(4):
579                                                         thisname = thisname + ", " + _("Vanity") + ": " + found.group(4)
580
581                                                 debug("[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (thisname.strip(), thisnumber))
582                                                 # Beware: strings in phonebook.phonebook have to be in utf-8!
583                                                 phonebook.phonebook[thisnumber] = thisname
584
585                 # elif re.search('document.write\(TrFon\(', html):
586                 elif html.find('document.write(TrFon(') != -1:
587                         #===============================================================================
588                         #                               Old Style: 7050 (FW 14.04.33)
589                         #       We expect one line with TrFon(No,Name,Number,Shortcut,Vanity)
590                         #   Encoding should be plain Ascii...
591                         #===============================================================================                                
592                         entrymask = re.compile('TrFon\("[^"]*", "([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\)', re.S)
593                         entries = entrymask.finditer(html)
594                         for found in entries:
595                                 name = found.group(1).strip().replace(',','')
596                                 # debug("[FritzCallFBF] pos: %s name: %s" %(found.group(0),name))
597                                 thisnumber = found.group(2).strip()
598                                 if config.plugins.FritzCall.showShortcut.value and found.group(3):
599                                         name = name + ", " + _("Shortcut") + ": " + found.group(3)
600                                 if config.plugins.FritzCall.showVanity.value and found.group(4):
601                                         name = name + ", " + _("Vanity") + ": " + found.group(4)
602                                 if thisnumber:
603                                         # name = name.encode('utf-8')
604                                         debug("[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (name, thisnumber))
605                                         # Beware: strings in phonebook.phonebook have to be in utf-8!
606                                         phonebook.phonebook[thisnumber] = name
607                                 else:
608                                         debug("[FritzCallFBF] ignoring empty number for %s" % name)
609                                 continue
610                 elif self._md5Sid == '0000000000000000': # retry, it could be a race condition
611                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: retry loading phonebook")
612                         self.loadFritzBoxPhonebook()
613                 else:
614                         self._notify(_("Could not parse FRITZ!Box Phonebook entry"))
615
616         def _errorLoad(self, error):
617                 debug("[FritzCallFBF] _errorLoad: %s" % (error))
618                 text = _("FRITZ!Box - Could not load phonebook: %s") % error.getErrorMessage()
619                 self._notify(text)
620
621         def getCalls(self, callScreen, callback, callType):
622                 #
623                 # call sequence must be:
624                 # - login
625                 # - getPage -> _gotPageLogin
626                 # - loginCallback (_getCalls)
627                 # - getPage -> _getCalls1
628                 debug("[FritzCallFBF] getCalls")
629                 self._callScreen = callScreen
630                 self._callType = callType
631                 if (time.time() - self._callTimestamp) > 180: 
632                         debug("[FritzCallFBF] getCalls: outdated data, login and get new ones: " + time.ctime(self._callTimestamp) + " time: " + time.ctime())
633                         self._callTimestamp = time.time()
634                         self._login(lambda x:self._getCalls(callback, x))
635                 elif not self._callList:
636                         debug("[FritzCallFBF] getCalls: time is ok, but no callList")
637                         self._getCalls1(callback)
638                 else:
639                         debug("[FritzCallFBF] getCalls: time is ok, callList is ok")
640                         self._gotPageCalls(callback)
641
642         def _getCalls(self, callback, html):
643                 if html:
644                         #===================================================================
645                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
646                         # if found:
647                         #       self._errorCalls('Login: ' + found.group(1))
648                         #       return
649                         #===================================================================
650                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
651                         if start != -1:
652                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
653                                 self._errorCalls('Login: ' + html[start, html.find('</p>', start)])
654                                 return
655                 #
656                 # we need this to fill Anrufliste.csv
657                 # http://repeater1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=foncalls
658                 #
659                 debug("[FritzCallFBF] _getCalls")
660                 if html:
661                         #===================================================================
662                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
663                         # if found:
664                         #       text = _("FRITZ!Box - Error logging in: %s") + found.group(1)
665                         #       self._notify(text)
666                         #       return
667                         #===================================================================
668                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
669                         if start != -1:
670                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
671                                 self._notify(_("FRITZ!Box - Error logging in: %s") + html[start, html.find('</p>', start)])
672                                 return
673
674                 if self._callScreen:
675                         self._callScreen.updateStatus(_("preparing"))
676                 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de', 'var:pagename':'foncalls', 'var:menu':'fon', 'sid':self._md5Sid})
677                 url = "http://%s/cgi-bin/webcm?%s" % (config.plugins.FritzCall.hostname.value, parms)
678                 getPage(url).addCallback(lambda x:self._getCalls1(callback)).addErrback(self._errorCalls) #@UnusedVariable # pylint: disable=W0613
679
680         def _getCalls1(self, callback):
681                 #
682                 # finally we should have successfully lgged in and filled the csv
683                 #
684                 debug("[FritzCallFBF] _getCalls1")
685                 if self._callScreen:
686                         self._callScreen.updateStatus(_("finishing"))
687                 parms = urlencode({'getpage':'../html/de/FRITZ!Box_Anrufliste.csv', 'sid':self._md5Sid})
688                 url = "http://%s/cgi-bin/webcm?%s" % (config.plugins.FritzCall.hostname.value, parms)
689                 getPage(url).addCallback(lambda x:self._gotPageCalls(callback, x)).addErrback(self._errorCalls)
690
691         def _gotPageCalls(self, callback, csv=""):
692                 def resolveNumber(number, default=None):
693                         if number.isdigit():
694                                 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0] == "0":
695                                         number = number[1:]
696                                 # strip CbC prefix
697                                 number = stripCbCPrefix(number, config.plugins.FritzCall.country.value)
698                                 if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
699                                         number = config.plugins.FritzCall.prefix.value + number
700                                 name = phonebook.search(number)
701                                 if name:
702                                         #===========================================================
703                                         # found = re.match('(.*?)\n.*', name)
704                                         # if found:
705                                         #       name = found.group(1)
706                                         #===========================================================
707                                         end = name.find('\n')
708                                         if end != -1:
709                                                 name = name[:end]
710                                         number = name
711                                 elif default:
712                                         number = default
713                                 else:
714                                         name = resolveNumberWithAvon(number, config.plugins.FritzCall.country.value)
715                                         if name:
716                                                 number = number + ' ' + name
717                         elif number == "":
718                                 number = _("UNKNOWN")
719                         # if len(number) > 20: number = number[:20]
720                         return number
721
722                 if csv:
723                         debug("[FritzCallFBF] _gotPageCalls: got csv, setting callList")
724                         if self._callScreen:
725                                 self._callScreen.updateStatus(_("done"))
726                         if csv.find('Melden Sie sich mit dem Kennwort der FRITZ!Box an') != -1:
727                                 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.")
728                                 # self.session.open(MessageBox, text, MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
729                                 self._notify(text)
730                                 return
731
732                         csv = csv.decode('iso-8859-1', 'replace').encode('utf-8', 'replace')
733                         lines = csv.splitlines()
734                         self._callList = lines
735                 elif self._callList:
736                         debug("[FritzCallFBF] _gotPageCalls: got no csv, but have callList")
737                         if self._callScreen:
738                                 self._callScreen.updateStatus(_("done, using last list"))
739                         lines = self._callList
740                 else:
741                         debug("[FritzCallFBF] _gotPageCalls: got no csv, no callList, laving")
742                         return
743                         
744                 callListL = []
745                 if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
746                         filtermsns = map(lambda x: x.strip(), config.plugins.FritzCall.filtermsn.value.split(","))
747                         debug("[FritzCallFBF] _gotPageCalls: filtermsns %s" % (repr(filtermsns)))
748
749                 # Typ;Datum;Name;Rufnummer;Nebenstelle;Eigene Rufnummer;Dauer
750                 # 0  ;1    ;2   ;3        ;4          ;5               ;6
751                 lines = map(lambda line: line.split(';'), lines)
752                 lines = filter(lambda line: (len(line)==7 and (line[0]=="Typ" or self._callType == '.' or line[0] == self._callType)), lines)
753
754                 for line in lines:
755                         # debug("[FritzCallFBF] _gotPageCalls: line %s" % (line))
756                         direct = line[0]
757                         date = line[1]
758                         length = line[6]
759                         if config.plugins.FritzCall.phonebook.value and line[2]:
760                                 remote = resolveNumber(line[3], line[2] + " (FBF)")
761                         else:
762                                 remote = resolveNumber(line[3], line[2])
763                         here = line[5]
764                         start = here.find('Internet: ')
765                         if start != -1:
766                                 start += len('Internet: ')
767                                 here = here[start:]
768                         else:
769                                 here = line[5]
770                         if direct != "Typ" and config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
771                                 # debug("[FritzCallFBF] _gotPageCalls: check %s" % (here))
772                                 if here not in filtermsns:
773                                         # debug("[FritzCallFBF] _gotPageCalls: skip %s" % (here))
774                                         continue
775                         here = resolveNumber(here, line[4])
776
777                         number = stripCbCPrefix(line[3], config.plugins.FritzCall.country.value)
778                         if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
779                                 number = config.plugins.FritzCall.prefix.value + number
780                         callListL.append((number, date, direct, remote, length, here))
781
782                 if callback:
783                         # debug("[FritzCallFBF] _gotPageCalls call callback with\n" + text
784                         callback(callListL)
785                 self._callScreen = None
786
787         def _errorCalls(self, error):
788                 debug("[FritzCallFBF] _errorCalls: %s" % (error))
789                 text = _("FRITZ!Box - Could not load calls: %s") % error.getErrorMessage()
790                 self._notify(text)
791
792         def dial(self, number):
793                 ''' initiate a call to number '''
794                 self._login(lambda x: self._dial(number, x))
795                 
796         def _dial(self, number, html):
797                 if html:
798                         #===================================================================
799                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
800                         # if found:
801                         #       self._errorDial('Login: ' + found.group(1))
802                         #       return
803                         #===================================================================
804                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
805                         if start != -1:
806                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
807                                 self._errorDial('Login: ' + html[start, html.find('</p>', start)])
808                                 return
809                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
810                 parms = urlencode({
811                         'getpage':'../html/de/menus/menu2.html',
812                         'var:pagename':'fonbuch',
813                         'var:menu':'home',
814                         'telcfg:settings/UseClickToDial':'1',
815                         'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
816                         'telcfg:command/Dial':number,
817                         'sid':self._md5Sid
818                         })
819                 debug("[FritzCallFBF] dial url: '" + url + "' parms: '" + parms + "'")
820                 getPage(url,
821                         method="POST",
822                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
823                         headers={
824                                         'Content-Type': "application/x-www-form-urlencoded",
825                                         'Content-Length': str(len(parms))},
826                         postdata=parms).addCallback(self._okDial).addErrback(self._errorDial)
827
828         def _okDial(self, html): #@UnusedVariable # pylint: disable=W0613
829                 debug("[FritzCallFBF] okDial")
830
831         def _errorDial(self, error):
832                 debug("[FritzCallFBF] errorDial: $s" % error)
833                 text = _("FRITZ!Box - Dialling failed: %s") % error.getErrorMessage()
834                 self._notify(text)
835
836         def changeWLAN(self, statusWLAN):
837                 ''' get status info from FBF '''
838                 debug("[FritzCallFBF] changeWLAN start")
839                 if not statusWLAN or (statusWLAN != '1' and statusWLAN != '0'):
840                         return
841                 self._login(lambda x: self._changeWLAN(statusWLAN, x))
842                 
843         def _changeWLAN(self, statusWLAN, html):
844                 if html:
845                         #===================================================================
846                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
847                         # if found:
848                         #       self._errorChangeWLAN('Login: ' + found.group(1))
849                         #       return
850                         #===================================================================
851                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
852                         if start != -1:
853                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
854                                 self._errorChangeWLAN('Login: ' + html[start, html.find('</p>', start)])
855                                 return
856                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
857                 parms = urlencode({
858                         'getpage':'../html/de/menus/menu2.html',
859                         'var:lang':'de',
860                         'var:pagename':'wlan',
861                         'var:menu':'wlan',
862                         'wlan:settings/ap_enabled':str(statusWLAN),
863                         'sid':self._md5Sid
864                         })
865                 debug("[FritzCallFBF] changeWLAN url: '" + url + "' parms: '" + parms + "'")
866                 getPage(url,
867                         method="POST",
868                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
869                         headers={
870                                         'Content-Type': "application/x-www-form-urlencoded",
871                                         'Content-Length': str(len(parms))},
872                         postdata=parms).addCallback(self._okChangeWLAN).addErrback(self._errorChangeWLAN)
873
874         def _okChangeWLAN(self, html): #@UnusedVariable # pylint: disable=W0613
875                 debug("[FritzCallFBF] _okChangeWLAN")
876
877         def _errorChangeWLAN(self, error):
878                 debug("[FritzCallFBF] _errorChangeWLAN: $s" % error)
879                 text = _("FRITZ!Box - Failed changing WLAN: %s") % error.getErrorMessage()
880                 self._notify(text)
881
882         def changeMailbox(self, whichMailbox):
883                 ''' switch mailbox on/off '''
884                 debug("[FritzCallFBF] changeMailbox start: " + str(whichMailbox))
885                 self._login(lambda x: self._changeMailbox(whichMailbox, x))
886
887         def _changeMailbox(self, whichMailbox, html):
888                 if html:
889                         #===================================================================
890                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
891                         # if found:
892                         #       self._errorChangeMailbox('Login: ' + found.group(1))
893                         #       return
894                         #===================================================================
895                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
896                         if start != -1:
897                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
898                                 self._errorChangeMailbox('Login: ' + html[start, html.find('</p>', start)])
899                                 return
900                 debug("[FritzCallFBF] _changeMailbox")
901                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
902                 if whichMailbox == -1:
903                         for i in range(5):
904                                 if self.info[FBF_tamActive][i+1]:
905                                         state = '0'
906                                 else:
907                                         state = '1'
908                                 parms = urlencode({
909                                         'tam:settings/TAM'+str(i)+'/Active':state,
910                                         'sid':self._md5Sid
911                                         })
912                                 debug("[FritzCallFBF] changeMailbox url: '" + url + "' parms: '" + parms + "'")
913                                 getPage(url,
914                                         method="POST",
915                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
916                                         headers={
917                                                         'Content-Type': "application/x-www-form-urlencoded",
918                                                         'Content-Length': str(len(parms))},
919                                         postdata=parms).addCallback(self._okChangeMailbox).addErrback(self._errorChangeMailbox)
920                 elif whichMailbox > 4:
921                         debug("[FritzCallFBF] changeMailbox invalid mailbox number")
922                 else:
923                         if self.info[FBF_tamActive][whichMailbox+1]:
924                                 state = '0'
925                         else:
926                                 state = '1'
927                         parms = urlencode({
928                                 'tam:settings/TAM'+str(whichMailbox)+'/Active':state,
929                                 'sid':self._md5Sid
930                                 })
931                         debug("[FritzCallFBF] changeMailbox url: '" + url + "' parms: '" + parms + "'")
932                         getPage(url,
933                                 method="POST",
934                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
935                                 headers={
936                                                 'Content-Type': "application/x-www-form-urlencoded",
937                                                 'Content-Length': str(len(parms))},
938                                 postdata=parms).addCallback(self._okChangeMailbox).addErrback(self._errorChangeMailbox)
939
940         def _okChangeMailbox(self, html): #@UnusedVariable # pylint: disable=W0613
941                 debug("[FritzCallFBF] _okChangeMailbox")
942
943         def _errorChangeMailbox(self, error):
944                 debug("[FritzCallFBF] _errorChangeMailbox: $s" % error)
945                 text = _("FRITZ!Box - Failed changing Mailbox: %s") % error.getErrorMessage()
946                 self._notify(text)
947
948         def getInfo(self, callback):
949                 ''' get status info from FBF '''
950                 debug("[FritzCallFBF] getInfo")
951                 self._login(lambda x:self._getInfo(callback, x))
952                 
953         def _getInfo(self, callback, html):
954                 # http://192.168.178.1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:pagename=home&var:menu=home
955                 debug("[FritzCallFBF] _getInfo: verify login")
956                 if html:
957                         #===================================================================
958                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
959                         # if found:
960                         #       self._errorGetInfo('Login: ' + found.group(1))
961                         #       return
962                         #===================================================================
963                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
964                         if start != -1:
965                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
966                                 self._errorGetInfo('Login: ' + html[start, html.find('</p>', start)])
967                                 return
968
969                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
970                 parms = urlencode({
971                         'getpage':'../html/de/menus/menu2.html',
972                         'var:lang':'de',
973                         'var:pagename':'home',
974                         'var:menu':'home',
975                         'sid':self._md5Sid
976                         })
977                 debug("[FritzCallFBF] _getInfo url: '" + url + "' parms: '" + parms + "'")
978                 getPage(url,
979                         method="POST",
980                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
981                         headers={
982                                         'Content-Type': "application/x-www-form-urlencoded",
983                                         'Content-Length': str(len(parms))},
984                         postdata=parms).addCallback(lambda x:self._okGetInfo(callback,x)).addErrback(self._errorGetInfo)
985
986         def _okGetInfo(self, callback, html):
987                 def readInfo(html):
988                         if self.info:
989                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
990                         else:
991                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = (None, None, None, None, None, None, None, None, None)
992
993                         debug("[FritzCallFBF] _okGetInfo/readinfo")
994                         found = re.match('.*<table class="tborder" id="tProdukt">\s*<tr>\s*<td style="padding-top:2px;">([^<]*)</td>\s*<td style="padding-top:2px;text-align:right;">\s*([^\s]*)\s*</td>', html, re.S)
995                         if found:
996                                 boxInfo = found.group(1)+ ', ' + found.group(2)
997                                 boxInfo = boxInfo.replace('&nbsp;',' ')
998                                 # debug("[FritzCallFBF] _okGetInfo Boxinfo: " + boxInfo)
999                         else:
1000                                 found = re.match('.*<p class="ac">([^<]*)</p>', html, re.S)
1001                                 if found:
1002                                         # debug("[FritzCallFBF] _okGetInfo Boxinfo: " + found.group(1))
1003                                         boxInfo = found.group(1)
1004
1005                         if html.find('home_coninf.txt') != -1:
1006                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1007                                 parms = urlencode({
1008                                         'getpage':'../html/de/home/home_coninf.txt',
1009                                         'sid':self._md5Sid
1010                                         })
1011                                 # debug("[FritzCallFBF] get coninfo: url: '" + url + "' parms: '" + parms + "'")
1012                                 getPage(url,
1013                                         method="POST",
1014                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1015                                         headers={
1016                                                         'Content-Type': "application/x-www-form-urlencoded",
1017                                                         'Content-Length': str(len(parms))},
1018                                         postdata=parms).addCallback(lambda x:self._okSetConInfo(callback,x)).addErrback(self._errorGetInfo)
1019                         else:
1020                                 found = re.match('.*if \(isNaN\(jetzt\)\)\s*return "";\s*var str = "([^"]*)";', html, re.S)
1021                                 if found:
1022                                         # debug("[FritzCallFBF] _okGetInfo Uptime: " + found.group(1))
1023                                         upTime = found.group(1)
1024                                 else:
1025                                         found = re.match('.*str = g_pppSeit \+"([^<]*)<br>"\+mldIpAdr;', html, re.S)
1026                                         if found:
1027                                                 # debug("[FritzCallFBF] _okGetInfo Uptime: " + found.group(1))
1028                                                 upTime = found.group(1)
1029         
1030                                 found = re.match(".*IpAdrDisplay\('([.\d]+)'\)", html, re.S)
1031                                 if found:
1032                                         # debug("[FritzCallFBF] _okGetInfo IpAdrDisplay: " + found.group(1))
1033                                         ipAddress = found.group(1)
1034
1035                         if html.find('g_tamActive') != -1:
1036                                 entries = re.compile('if \("(\d)" == "1"\) {\s*g_tamActive \+= 1;\s*}', re.S).finditer(html)
1037                                 tamActive = [0, False, False, False, False, False]
1038                                 i = 1
1039                                 for entry in entries:
1040                                         state = entry.group(1)
1041                                         if state == '1':
1042                                                 tamActive[0] += 1
1043                                                 tamActive[i] = True
1044                                         i += 1
1045                                 # debug("[FritzCallFBF] _okGetInfo tamActive: " + str(tamActive))
1046                 
1047                         if html.find('home_dect.txt') != -1:
1048                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1049                                 parms = urlencode({
1050                                         'getpage':'../html/de/home/home_dect.txt',
1051                                         'sid':self._md5Sid
1052                                         })
1053                                 # debug("[FritzCallFBF] get coninfo: url: '" + url + "' parms: '" + parms + "'")
1054                                 getPage(url,
1055                                         method="POST",
1056                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1057                                         headers={
1058                                                         'Content-Type': "application/x-www-form-urlencoded",
1059                                                         'Content-Length': str(len(parms))},
1060                                         postdata=parms).addCallback(lambda x:self._okSetDect(callback,x)).addErrback(self._errorGetInfo)
1061                         else:
1062                                 if html.find('countDect2') != -1:
1063                                         entries = re.compile('if \("1" == "1"\) countDect2\+\+;', re.S).findall(html)
1064                                         dectActive = len(entries)
1065                                         # debug("[FritzCallFBF] _okGetInfo dectActive: " + str(dectActive))
1066
1067                         found = re.match('.*var g_intFaxActive = "0";\s*if \("1" != ""\) {\s*g_intFaxActive = "1";\s*}\s*', html, re.S)
1068                         if found:
1069                                 faxActive = True
1070                                 # debug("[FritzCallFBF] _okGetInfo faxActive")
1071
1072                         if html.find('cntRufumleitung') != -1:
1073                                 entries = re.compile('mode = "1";\s*ziel = "[^"]+";\s*if \(mode == "1" \|\| ziel != ""\)\s*{\s*g_RufumleitungAktiv = true;', re.S).findall(html)
1074                                 rufumlActive = len(entries)
1075                                 entries = re.compile('if \("([^"]*)"=="([^"]*)"\) isAllIncoming\+\+;', re.S).finditer(html)
1076                                 isAllIncoming = 0
1077                                 for entry in entries:
1078                                         # debug("[FritzCallFBF] _okGetInfo rufumlActive add isAllIncoming")
1079                                         if entry.group(1) == entry.group(2):
1080                                                 isAllIncoming += 1
1081                                 if isAllIncoming == 2 and rufumlActive > 0:
1082                                         rufumlActive -= 1
1083                                 # debug("[FritzCallFBF] _okGetInfo rufumlActive: " + str(rufumlActive))
1084
1085                         # /cgi-bin/webcm?getpage=../html/de/home/home_dsl.txt
1086                         # { "dsl_carrier_state": "5", "umts_enabled": "0", "ata_mode": "0", "isusbgsm": "", "dsl_ds_nrate": "3130", "dsl_us_nrate": "448", "hint_dsl_no_cable": "0", "wds_enabled": "0", "wds_hop": "0", "isata": "" } 
1087                         if html.find('home_dsl.txt') != -1:
1088                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1089                                 parms = urlencode({
1090                                         'getpage':'../html/de/home/home_dsl.txt',
1091                                         'sid':self._md5Sid
1092                                         })
1093                                 # debug("[FritzCallFBF] get dsl state: url: '" + url + "' parms: '" + parms + "'")
1094                                 getPage(url,
1095                                         method="POST",
1096                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1097                                         headers={
1098                                                         'Content-Type': "application/x-www-form-urlencoded",
1099                                                         'Content-Length': str(len(parms))},
1100                                         postdata=parms).addCallback(lambda x:self._okSetDslState(callback,x)).addErrback(self._errorGetInfo)
1101                         else:
1102                                 found = re.match('.*function DslStateDisplay \(state\){\s*var state = "(\d+)";', html, re.S)
1103                                 if found:
1104                                         # debug("[FritzCallFBF] _okGetInfo DslState: " + found.group(1))
1105                                         dslState = [ found.group(1), None ] # state, speed
1106                                         found = re.match('.*function DslStateDisplay \(state\){\s*var state = "\d+";.*?if \("3130" != "0"\) str = "([^"]*)";', html, re.S)
1107                                         if found:
1108                                                 # debug("[FritzCallFBF] _okGetInfo DslSpeed: " + found.group(1).strip())
1109                                                 dslState[1] = found.group(1).strip()
1110                 
1111                         # /cgi-bin/webcm?getpage=../html/de/home/home_wlan.txt
1112                         # { "ap_enabled": "1", "active_stations": "0", "encryption": "4", "wireless_stickandsurf_enabled": "0", "is_macfilter_active": "0", "wmm_enabled": "1", "wlan_state": [ "end" ] }
1113                         if html.find('home_wlan.txt') != -1:
1114                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1115                                 parms = urlencode({
1116                                         'getpage':'../html/de/home/home_wlan.txt',
1117                                         'sid':self._md5Sid
1118                                         })
1119                                 # debug("[FritzCallFBF] get wlan state: url: '" + url + "' parms: '" + parms + "'")
1120                                 getPage(url,
1121                                         method="POST",
1122                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1123                                         headers={
1124                                                         'Content-Type': "application/x-www-form-urlencoded",
1125                                                         'Content-Length': str(len(parms))},
1126                                         postdata=parms).addCallback(lambda x:self._okSetWlanState(callback,x)).addErrback(self._errorGetInfo)
1127                         else:
1128                                 found = re.match('.*function WlanStateLed \(state\){.*?return StateLed\("(\d+)"\);\s*}', html, re.S)
1129                                 if found:
1130                                         # debug("[FritzCallFBF] _okGetInfo WlanState: " + found.group(1))
1131                                         wlanState = [ found.group(1), 0, 0 ] # state, encryption, number of devices
1132                                         found = re.match('.*var (?:g_)?encryption = "(\d+)";', html, re.S)
1133                                         if found:
1134                                                 # debug("[FritzCallFBF] _okGetInfo WlanEncrypt: " + found.group(1))
1135                                                 wlanState[1] = found.group(1)
1136
1137                         return (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1138
1139                 debug("[FritzCallFBF] _okGetInfo")
1140                 info = readInfo(html)
1141                 debug("[FritzCallFBF] _okGetInfo info: " + str(info))
1142                 self.info = info
1143                 if callback:
1144                         callback(info)
1145
1146         def _okSetDect(self, callback, html):
1147                 # debug("[FritzCallFBF] _okSetDect: " + html)
1148                 # found = re.match('.*"connection_status":"(\d+)".*"connection_ip":"([.\d]+)".*"connection_detail":"([^"]+)".*"connection_uptime":"([^"]+)"', html, re.S)
1149                 if html.find('"dect_enabled": "1"') != -1:
1150                         # debug("[FritzCallFBF] _okSetDect: dect_enabled")
1151                         found = re.match('.*"dect_device_list":.*\[([^\]]*)\]', html, re.S)
1152                         if found:
1153                                 # debug("[FritzCallFBF] _okSetDect: dect_device_list: %s" %(found.group(1)))
1154                                 entries = re.compile('"1"', re.S).findall(found.group(1))
1155                                 dectActive = len(entries)
1156                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dummy, faxActive, rufumlActive) = self.info
1157                                 self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1158                                 debug("[FritzCallFBF] _okSetDect info: " + str(self.info))
1159                 if callback:
1160                         callback(self.info)
1161
1162         def _okSetConInfo(self, callback, html):
1163                 # debug("[FritzCallFBF] _okSetConInfo: " + html)
1164                 # found = re.match('.*"connection_status":"(\d+)".*"connection_ip":"([.\d]+)".*"connection_detail":"([^"]+)".*"connection_uptime":"([^"]+)"', html, re.S)
1165                 found = re.match('.*"connection_ip": "([.\d]+)".*"connection_uptime": "([^"]+)"', html, re.S)
1166                 if found:
1167                         # debug("[FritzCallFBF] _okSetConInfo: connection_ip: %s upTime: %s" %( found.group(1), found.group(2)))
1168                         ipAddress = found.group(1)
1169                         upTime = found.group(2)
1170                         (boxInfo, dummy, dummy, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
1171                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1172                         debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
1173                 else:
1174                         found = re.match('.*_ip": "([.\d]+)".*"connection_uptime": "([^"]+)"', html, re.S)
1175                         if found:
1176                                 # debug("[FritzCallFBF] _okSetConInfo: _ip: %s upTime: %s" %( found.group(1), found.group(2)))
1177                                 ipAddress = found.group(1)
1178                                 upTime = found.group(2)
1179                                 (boxInfo, dummy, dummy, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
1180                                 self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1181                                 debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
1182                 if callback:
1183                         callback(self.info)
1184
1185         def _okSetWlanState(self, callback, html):
1186                 # debug("[FritzCallFBF] _okSetWlanState: " + html)
1187                 found = re.match('.*"ap_enabled": "(\d+)"', html, re.S)
1188                 if found:
1189                         # debug("[FritzCallFBF] _okSetWlanState: ap_enabled: " + found.group(1))
1190                         wlanState = [ found.group(1), None, None ]
1191                         found = re.match('.*"encryption": "(\d+)"', html, re.S)
1192                         if found:
1193                                 # debug("[FritzCallFBF] _okSetWlanState: encryption: " + found.group(1))
1194                                 wlanState[1] = found.group(1)
1195                         found = re.match('.*"active_stations": "(\d+)"', html, re.S)
1196                         if found:
1197                                 # debug("[FritzCallFBF] _okSetWlanState: active_stations: " + found.group(1))
1198                                 wlanState[2] = found.group(1)
1199                         (boxInfo, upTime, ipAddress, dummy, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
1200                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1201                         debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
1202                 if callback:
1203                         callback(self.info)
1204
1205         def _okSetDslState(self, callback, html):
1206                 # debug("[FritzCallFBF] _okSetDslState: " + html)
1207                 found = re.match('.*"dsl_carrier_state": "(\d+)"', html, re.S)
1208                 if found:
1209                         # debug("[FritzCallFBF] _okSetDslState: dsl_carrier_state: " + found.group(1))
1210                         dslState = [ found.group(1), None ]
1211                         found = re.match('.*"dsl_ds_nrate": "(\d+)"', html, re.S)
1212                         if found:
1213                                 # debug("[FritzCallFBF] _okSetDslState: dsl_ds_nrate: " + found.group(1))
1214                                 dslState[1] = found.group(1)
1215                         found = re.match('.*"dsl_us_nrate": "(\d+)"', html, re.S)
1216                         if found:
1217                                 # debug("[FritzCallFBF] _okSetDslState: dsl_us_nrate: " + found.group(1))
1218                                 dslState[1] = dslState[1] + '/' + found.group(1)
1219                         (boxInfo, upTime, ipAddress, wlanState, dummy, tamActive, dectActive, faxActive, rufumlActive) = self.info
1220                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1221                         debug("[FritzCallFBF] _okSetDslState info: " + str(self.info))
1222                 if callback:
1223                         callback(self.info)
1224
1225         def _errorGetInfo(self, error):
1226                 debug("[FritzCallFBF] _errorGetInfo: %s" % (error))
1227                 text = _("FRITZ!Box - Error getting status: %s") % error.getErrorMessage()
1228                 self._notify(text)
1229                 # linkP = open("/tmp/FritzCall_errorGetInfo.htm", "w")
1230                 # linkP.write(error)
1231                 # linkP.close()
1232
1233         def reset(self):
1234                 self._login(self._reset)
1235
1236         def _reset(self, html):
1237                 # POSTDATA=getpage=../html/reboot.html&errorpage=../html/de/menus/menu2.html&var:lang=de&var:pagename=home&var:errorpagename=home&var:menu=home&var:pagemaster=&time:settings/time=1242207340%2C-120&var:tabReset=0&logic:command/reboot=../gateway/commands/saveconfig.html
1238                 if html:
1239                         #===================================================================
1240                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1241                         # if found:
1242                         #       self._errorReset('Login: ' + found.group(1))
1243                         #       return
1244                         #===================================================================
1245                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1246                         if start != -1:
1247                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1248                                 self._errorReset('Login: ' + html[start, html.find('</p>', start)])
1249                                 return
1250                 if self._callScreen:
1251                         self._callScreen.close()
1252                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1253                 parms = urlencode({
1254                         'getpage':'../html/reboot.html',
1255                         'var:lang':'de',
1256                         'var:pagename':'reset',
1257                         'var:menu':'system',
1258                         'logic:command/reboot':'../gateway/commands/saveconfig.html',
1259                         'sid':self._md5Sid
1260                         })
1261                 debug("[FritzCallFBF] _reset url: '" + url + "' parms: '" + parms + "'")
1262                 getPage(url,
1263                         method="POST",
1264                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1265                         headers={
1266                                         'Content-Type': "application/x-www-form-urlencoded",
1267                                         'Content-Length': str(len(parms))},
1268                         postdata=parms)
1269
1270         def _okReset(self, html): #@UnusedVariable # pylint: disable=W0613
1271                 debug("[FritzCallFBF] _okReset")
1272
1273         def _errorReset(self, error):
1274                 debug("[FritzCallFBF] _errorReset: %s" % (error))
1275                 text = _("FRITZ!Box - Error resetting: %s") % error.getErrorMessage()
1276                 self._notify(text)
1277
1278         def readBlacklist(self):
1279                 self._login(self._readBlacklist)
1280                 
1281         def _readBlacklist(self, html):
1282                 if html:
1283                         #===================================================================
1284                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1285                         # if found:
1286                         #       self._errorBlacklist('Login: ' + found.group(1))
1287                         #       return
1288                         #===================================================================
1289                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1290                         if start != -1:
1291                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1292                                 self._errorBlacklist('Login: ' + html[start, html.find('</p>', start)])
1293                                 return
1294                 # http://fritz.box/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=sperre
1295                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1296                 parms = urlencode({
1297                         'getpage':'../html/de/menus/menu2.html',
1298                         'var:lang':'de',
1299                         'var:pagename':'sperre',
1300                         'var:menu':'fon',
1301                         'sid':self._md5Sid
1302                         })
1303                 debug("[FritzCallFBF] _readBlacklist url: '" + url + "' parms: '" + parms + "'")
1304                 getPage(url,
1305                         method="POST",
1306                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1307                         headers={
1308                                         'Content-Type': "application/x-www-form-urlencoded",
1309                                         'Content-Length': str(len(parms))},
1310                         postdata=parms).addCallback(self._okBlacklist).addErrback(self._errorBlacklist)
1311
1312         def _okBlacklist(self, html):
1313                 debug("[FritzCallFBF] _okBlacklist")
1314                 entries = re.compile('<script type="text/javascript">document.write\(Tr(Out|In)\("\d+", "(\d+)", "\w*"\)\);</script>', re.S).finditer(html)
1315                 self.blacklist = ([], [])
1316                 for entry in entries:
1317                         if entry.group(1) == "In":
1318                                 self.blacklist[0].append(entry.group(2))
1319                         else:
1320                                 self.blacklist[1].append(entry.group(2))
1321                 debug("[FritzCallFBF] _okBlacklist: %s" % repr(self.blacklist))
1322
1323         def _errorBlacklist(self, error):
1324                 debug("[FritzCallFBF] _errorBlacklist: %s" % (error))
1325                 text = _("FRITZ!Box - Error getting blacklist: %s") % error.getErrorMessage()
1326                 self._notify(text)
1327
1328 #===============================================================================
1329 #       def hangup(self):
1330 #               ''' hangup call on port; not used for now '''
1331 #               url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1332 #               parms = urlencode({
1333 #                       'id':'uiPostForm',
1334 #                       'name':'uiPostForm',
1335 #                       'login:command/password': config.plugins.FritzCall.password.value,
1336 #                       'telcfg:settings/UseClickToDial':'1',
1337 #                       'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
1338 #                       'telcfg:command/Hangup':'',
1339 #                       'sid':self._md5Sid
1340 #                       })
1341 #               debug("[FritzCallFBF] hangup url: '" + url + "' parms: '" + parms + "'")
1342 #               getPage(url,
1343 #                       method="POST",
1344 #                       headers={
1345 #                                       'Content-Type': "application/x-www-form-urlencoded",
1346 #                                       'Content-Length': str(len(parms))},
1347 #                       postdata=parms)
1348 #===============================================================================
1349
1350 fritzbox = None
1351
1352 class FritzMenu(Screen, HelpableScreen):
1353         def __init__(self, session):
1354                 fontSize = scaleV(24, 21) # indeed this is font size +2
1355                 noButtons = 2 # reset, wlan
1356
1357                 if not fritzbox or not fritzbox.info:
1358                         return
1359
1360                 if fritzbox.info[FBF_tamActive]:
1361                         noButtons += 1 # toggle mailboxes
1362                 width = max(DESKTOP_WIDTH - scaleH(500, 250), noButtons*140+(noButtons+1)*10)
1363                 # boxInfo 2 lines, gap, internet 2 lines, gap, dsl/wlan each 1 line, gap, buttons
1364                 height = 5 + 2*fontSize + 10 + 2*fontSize + 10 + 2*fontSize + 10 + 40 + 5
1365                 if fritzbox.info[FBF_tamActive] is not None:
1366                         height += fontSize
1367                 if fritzbox.info[FBF_dectActive] is not None:
1368                         height += fontSize
1369                 if fritzbox.info[FBF_faxActive] is not None:
1370                         height += fontSize
1371                 if fritzbox.info[FBF_rufumlActive] is not None:
1372                         height += fontSize
1373                 buttonsGap = (width-noButtons*140)/(noButtons+1)
1374                 buttonsVPos = height-40-5
1375
1376                 varLinePos = 4
1377                 if fritzbox.info[FBF_tamActive] is not None:
1378                         mailboxLine = """
1379                                 <widget name="FBFMailbox" position="%d,%d" size="%d,%d" font="Regular;%d" />
1380                                 <widget name="mailbox_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1381                                 <widget name="mailbox_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1382                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
1383                                 <widget name="key_yellow" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1384                                 """ % (
1385                                                 40, 5+2*fontSize+10+varLinePos*fontSize+10, # position mailbox
1386                                                 width-40-20, fontSize, # size mailbox
1387                                                 fontSize-2,
1388                                                 "skin_default/buttons/button_green_off.png",
1389                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button mailbox
1390                                                 "skin_default/buttons/button_green.png",
1391                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button mailbox
1392                                                 noButtons*buttonsGap+(noButtons-1)*140, buttonsVPos,
1393                                                 noButtons*buttonsGap+(noButtons-1)*140, buttonsVPos,
1394                                 )
1395                         varLinePos += 1
1396                 else:
1397                         mailboxLine = ""
1398
1399                 if fritzbox.info[FBF_dectActive] is not None:
1400                         dectLine = """
1401                                 <widget name="FBFDect" position="%d,%d" size="%d,%d" font="Regular;%d" />
1402                                 <widget name="dect_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1403                                 <widget name="dect_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1404                                 """ % (
1405                                                 40, 5+2*fontSize+10+varLinePos*fontSize+10, # position dect
1406                                                 width-40-20, fontSize, # size dect
1407                                                 fontSize-2,
1408                                                 "skin_default/buttons/button_green_off.png",
1409                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1410                                                 "skin_default/buttons/button_green.png",
1411                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1412                                 )
1413                         varLinePos += 1
1414                 else:
1415                         dectLine = ""
1416
1417                 if fritzbox.info[FBF_faxActive] is not None:
1418                         faxLine = """
1419                                 <widget name="FBFFax" position="%d,%d" size="%d,%d" font="Regular;%d" />
1420                                 <widget name="fax_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1421                                 <widget name="fax_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1422                                 """ % (
1423                                                 40, 5+2*fontSize+10+varLinePos*fontSize+10, # position dect
1424                                                 width-40-20, fontSize, # size dect
1425                                                 fontSize-2,
1426                                                 "skin_default/buttons/button_green_off.png",
1427                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1428                                                 "skin_default/buttons/button_green.png",
1429                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1430                                 )
1431                         varLinePos += 1
1432                 else:
1433                         faxLine = ""
1434
1435                 if fritzbox.info[FBF_rufumlActive] is not None:
1436                         rufumlLine = """
1437                                 <widget name="FBFRufuml" position="%d,%d" size="%d,%d" font="Regular;%d" />
1438                                 <widget name="rufuml_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1439                                 <widget name="rufuml_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1440                                 """ % (
1441                                                 40, 5+2*fontSize+10+varLinePos*fontSize+10, # position dect
1442                                                 width-40-20, fontSize, # size dect
1443                                                 fontSize-2,
1444                                                 "skin_default/buttons/button_green_off.png",
1445                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1446                                                 "skin_default/buttons/button_green.png",
1447                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1448                                 )
1449                         varLinePos += 1
1450                 else:
1451                         rufumlLine = ""
1452         
1453                 self.skin = """
1454                         <screen name="FritzMenu" position="center,center" size="%d,%d" title="FRITZ!Box Fon Status" >
1455                                 <widget name="FBFInfo" position="%d,%d" size="%d,%d" font="Regular;%d" />
1456                                 <widget name="FBFInternet" position="%d,%d" size="%d,%d" font="Regular;%d" />
1457                                 <widget name="internet_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1458                                 <widget name="internet_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1459                                 <widget name="FBFDsl" position="%d,%d" size="%d,%d" font="Regular;%d" />
1460                                 <widget name="dsl_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1461                                 <widget name="dsl_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1462                                 <widget name="FBFWlan" position="%d,%d" size="%d,%d" font="Regular;%d" />
1463                                 <widget name="wlan_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1464                                 <widget name="wlan_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1465                                 %s
1466                                 %s
1467                                 %s
1468                                 %s
1469                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1470                                 <widget name="key_red" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1471                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1472                                 <widget name="key_green" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1473                         </screen>""" % (
1474                                                 width, height, # size
1475                                                 40, 5, # position info
1476                                                 width-2*40, 2*fontSize, # size info
1477                                                 fontSize-2,
1478                                                 40, 5+2*fontSize+10, # position internet
1479                                                 width-40, 2*fontSize, # size internet
1480                                                 fontSize-2,
1481                                                 "skin_default/buttons/button_green_off.png",
1482                                                 20, 5+2*fontSize+10+(fontSize-16)/2, # position button internet
1483                                                 "skin_default/buttons/button_green.png",
1484                                                 20, 5+2*fontSize+10+(fontSize-16)/2, # position button internet
1485                                                 40, 5+2*fontSize+10+2*fontSize+10, # position dsl
1486                                                 width-40-20, fontSize, # size dsl
1487                                                 fontSize-2,
1488                                                 "skin_default/buttons/button_green_off.png",
1489                                                 20, 5+2*fontSize+10+2*fontSize+10+(fontSize-16)/2, # position button dsl
1490                                                 "skin_default/buttons/button_green.png",
1491                                                 20, 5+2*fontSize+10+2*fontSize+10+(fontSize-16)/2, # position button dsl
1492                                                 40, 5+2*fontSize+10+3*fontSize+10, # position wlan
1493                                                 width-40-20, fontSize, # size wlan
1494                                                 fontSize-2,
1495                                                 "skin_default/buttons/button_green_off.png",
1496                                                 20, 5+2*fontSize+10+3*fontSize+10+(fontSize-16)/2, # position button wlan
1497                                                 "skin_default/buttons/button_green.png",
1498                                                 20, 5+2*fontSize+10+3*fontSize+10+(fontSize-16)/2, # position button wlan
1499                                                 mailboxLine,
1500                                                 dectLine,
1501                                                 faxLine,
1502                                                 rufumlLine,
1503                                                 buttonsGap, buttonsVPos, "skin_default/buttons/red.png", buttonsGap, buttonsVPos,
1504                                                 buttonsGap+140+buttonsGap, buttonsVPos, "skin_default/buttons/green.png", buttonsGap+140+buttonsGap, buttonsVPos,
1505                                                 )
1506
1507                 Screen.__init__(self, session)
1508                 HelpableScreen.__init__(self)
1509                 # TRANSLATORS: keep it short, this is a button
1510                 self["key_red"] = Button(_("Reset"))
1511                 # TRANSLATORS: keep it short, this is a button
1512                 self["key_green"] = Button(_("Toggle WLAN"))
1513                 self._mailboxActive = False
1514                 if fritzbox.info[FBF_tamActive] is not None:
1515                         # TRANSLATORS: keep it short, this is a button
1516                         self["key_yellow"] = Button(_("Toggle Mailbox"))
1517                         self["menuActions"] = ActionMap(["OkCancelActions", "ColorActions", "NumberActions", "EPGSelectActions"],
1518                                                                                         {
1519                                                                                         "cancel": self._exit,
1520                                                                                         "ok": self._exit,
1521                                                                                         "red": self._reset,
1522                                                                                         "green": self._toggleWlan,
1523                                                                                         "yellow": (lambda: self._toggleMailbox(-1)),
1524                                                                                         "0": (lambda: self._toggleMailbox(0)),
1525                                                                                         "1": (lambda: self._toggleMailbox(1)),
1526                                                                                         "2": (lambda: self._toggleMailbox(2)),
1527                                                                                         "3": (lambda: self._toggleMailbox(3)),
1528                                                                                         "4": (lambda: self._toggleMailbox(4)),
1529                                                                                         "info": self._getInfo,
1530                                                                                         }, -2)
1531                         # TRANSLATORS: keep it short, this is a help text
1532                         self.helpList.append((self["menuActions"], "ColorActions", [("yellow", _("Toggle all mailboxes"))]))
1533                         # TRANSLATORS: keep it short, this is a help text
1534                         self.helpList.append((self["menuActions"], "NumberActions", [("0", _("Toggle 1. mailbox"))]))
1535                         # TRANSLATORS: keep it short, this is a help text
1536                         self.helpList.append((self["menuActions"], "NumberActions", [("1", _("Toggle 2. mailbox"))]))
1537                         # TRANSLATORS: keep it short, this is a help text
1538                         self.helpList.append((self["menuActions"], "NumberActions", [("2", _("Toggle 3. mailbox"))]))
1539                         # TRANSLATORS: keep it short, this is a help text
1540                         self.helpList.append((self["menuActions"], "NumberActions", [("3", _("Toggle 4. mailbox"))]))
1541                         # TRANSLATORS: keep it short, this is a help text
1542                         self.helpList.append((self["menuActions"], "NumberActions", [("4", _("Toggle 5. mailbox"))]))
1543                         self["FBFMailbox"] = Label(_('Mailbox'))
1544                         self["mailbox_inactive"] = Pixmap()
1545                         self["mailbox_active"] = Pixmap()
1546                         self["mailbox_active"].hide()
1547                 else:
1548                         self["menuActions"] = ActionMap(["OkCancelActions", "ColorActions", "EPGSelectActions"],
1549                                                                                         {
1550                                                                                         "cancel": self._exit,
1551                                                                                         "ok": self._exit,
1552                                                                                         "green": self._toggleWlan,
1553                                                                                         "red": self._reset,
1554                                                                                         "info": self._getInfo,
1555                                                                                         }, -2)
1556
1557                 # TRANSLATORS: keep it short, this is a help text
1558                 self.helpList.append((self["menuActions"], "OkCancelActions", [("cancel", _("Quit"))]))
1559                 # TRANSLATORS: keep it short, this is a help text
1560                 self.helpList.append((self["menuActions"], "OkCancelActions", [("ok", _("Quit"))]))
1561                 # TRANSLATORS: keep it short, this is a help text
1562                 self.helpList.append((self["menuActions"], "ColorActions", [("green", _("Toggle WLAN"))]))
1563                 # TRANSLATORS: keep it short, this is a help text
1564                 self.helpList.append((self["menuActions"], "ColorActions", [("red", _("Reset"))]))
1565                 # TRANSLATORS: keep it short, this is a help text
1566                 self.helpList.append((self["menuActions"], "EPGSelectActions", [("info", _("Refresh status"))]))
1567
1568                 self["FBFInfo"] = Label(_('Getting status from FRITZ!Box Fon...'))
1569
1570                 self["FBFInternet"] = Label('Internet')
1571                 self["internet_inactive"] = Pixmap()
1572                 self["internet_active"] = Pixmap()
1573                 self["internet_active"].hide()
1574
1575                 self["FBFDsl"] = Label('DSL')
1576                 self["dsl_inactive"] = Pixmap()
1577                 self["dsl_inactive"].hide()
1578                 self["dsl_active"] = Pixmap()
1579                 self["dsl_active"].hide()
1580
1581                 self["FBFWlan"] = Label('WLAN ')
1582                 self["wlan_inactive"] = Pixmap()
1583                 self["wlan_inactive"].hide()
1584                 self["wlan_active"] = Pixmap()
1585                 self["wlan_active"].hide()
1586                 self._wlanActive = False
1587
1588                 if fritzbox.info[FBF_dectActive] is not None: 
1589                         self["FBFDect"] = Label('DECT')
1590                         self["dect_inactive"] = Pixmap()
1591                         self["dect_active"] = Pixmap()
1592                         self["dect_active"].hide()
1593
1594                 if fritzbox.info[FBF_faxActive] is not None: 
1595                         self["FBFFax"] = Label('Fax')
1596                         self["fax_inactive"] = Pixmap()
1597                         self["fax_active"] = Pixmap()
1598                         self["fax_active"].hide()
1599
1600                 if fritzbox.info[FBF_rufumlActive] is not None: 
1601                         self["FBFRufuml"] = Label(_('Call redirection'))
1602                         self["rufuml_inactive"] = Pixmap()
1603                         self["rufuml_active"] = Pixmap()
1604                         self["rufuml_active"].hide()
1605
1606                 self._timer = eTimer()
1607                 self._timer.callback.append(self._getInfo)
1608                 self.onShown.append(lambda: self._timer.start(5000))
1609                 self.onHide.append(self._timer.stop)
1610                 self._getInfo()
1611                 self.onLayoutFinish.append(self.setWindowTitle)
1612
1613         def setWindowTitle(self):
1614                 # TRANSLATORS: this is a window title.
1615                 self.setTitle(_("FRITZ!Box Fon Status"))
1616
1617         def _getInfo(self):
1618                 fritzbox.getInfo(self._fillMenu)
1619
1620         def _fillMenu(self, status):
1621                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = status
1622                 self._wlanActive = (wlanState[0] == '1')
1623                 self._mailboxActive = False
1624                 try:
1625                         if not self.has_key("FBFInfo"): # screen is closed already
1626                                 return
1627
1628                         if boxInfo:
1629                                 self["FBFInfo"].setText(boxInfo.replace(', ', '\n'))
1630                         else:
1631                                 self["FBFInfo"].setText('BoxInfo ' + _('Status not available'))
1632
1633                         if ipAddress:
1634                                 if upTime:
1635                                         self["FBFInternet"].setText('Internet ' + _('IP Address:') + ' ' + ipAddress + '\n' + _('Connected since') + ' ' + upTime)
1636                                 else:
1637                                         self["FBFInternet"].setText('Internet ' + _('IP Address:') + ' ' + ipAddress)
1638                                 self["internet_inactive"].hide()
1639                                 self["internet_active"].show()
1640                         else:
1641                                 self["internet_active"].hide()
1642                                 self["internet_inactive"].show()
1643
1644                         if dslState:
1645                                 if dslState[0] == '5':
1646                                         self["dsl_inactive"].hide()
1647                                         self["dsl_active"].show()
1648                                         if dslState[1]:
1649                                                 self["FBFDsl"].setText('DSL ' + dslState[1])
1650                                 else:
1651                                         self["dsl_active"].hide()
1652                                         self["dsl_inactive"].show()
1653                         else:
1654                                 self["FBFDsl"].setText('DSL ' + _('Status not available'))
1655                                 self["dsl_active"].hide()
1656                                 self["dsl_inactive"].hide()
1657
1658                         if wlanState:
1659                                 if wlanState[0 ] == '1':
1660                                         self["wlan_inactive"].hide()
1661                                         self["wlan_active"].show()
1662                                         message = 'WLAN'
1663                                         if wlanState[1] == '0':
1664                                                 message += ' ' + _('not encrypted')
1665                                         else:
1666                                                 message += ' ' + _('encrypted')
1667                                         if wlanState[2]:
1668                                                 if wlanState[2] == '0':
1669                                                         message = message + ', ' + _('no device active')
1670                                                 elif wlanState[2] == '1':
1671                                                         message = message + ', ' + _('one device active')
1672                                                 else:
1673                                                         message = message + ', ' + wlanState[2] + ' ' + _('devices active')
1674                                         self["FBFWlan"].setText(message)
1675                                 else:
1676                                         self["wlan_active"].hide()
1677                                         self["wlan_inactive"].show()
1678                                         self["FBFWlan"].setText('WLAN')
1679                         else:
1680                                 self["FBFWlan"].setText('WLAN ' + _('Status not available'))
1681                                 self["wlan_active"].hide()
1682                                 self["wlan_inactive"].hide()
1683
1684                         if fritzbox.info[FBF_tamActive]:
1685                                 if  not tamActive or tamActive[0] == 0:
1686                                         self._mailboxActive = False
1687                                         self["mailbox_active"].hide()
1688                                         self["mailbox_inactive"].show()
1689                                         self["FBFMailbox"].setText(_('No mailbox active'))
1690                                 else:
1691                                         self._mailboxActive = True
1692                                         message = '('
1693                                         for i in range(5):
1694                                                 if tamActive[i+1]:
1695                                                         message = message + str(i) + ','
1696                                         message = message[:-1] + ')'
1697                                         self["mailbox_inactive"].hide()
1698                                         self["mailbox_active"].show()
1699                                         if tamActive[0] == 1:
1700                                                 self["FBFMailbox"].setText(_('One mailbox active') + ' ' + message)
1701                                         else:
1702                                                 self["FBFMailbox"].setText(str(tamActive[0]) + ' ' + _('mailboxes active') + ' ' + message)
1703         
1704                         if fritzbox.info[FBF_dectActive] and dectActive:
1705                                 self["dect_inactive"].hide()
1706                                 self["dect_active"].show()
1707                                 if dectActive == 0:
1708                                         self["FBFDect"].setText(_('No DECT phone registered'))
1709                                 else:
1710                                         if dectActive == 1:
1711                                                 self["FBFDect"].setText(_('One DECT phone registered'))
1712                                         else:
1713                                                 self["FBFDect"].setText(str(dectActive) + ' ' + _('DECT phones registered'))
1714
1715                         if fritzbox.info[FBF_faxActive] and faxActive:
1716                                 self["fax_inactive"].hide()
1717                                 self["fax_active"].show()
1718                                 self["FBFFax"].setText(_('Software fax active'))
1719
1720                         if fritzbox.info[FBF_rufumlActive] is not None and rufumlActive is not None:
1721                                 if rufumlActive == 0:
1722                                         self["rufuml_active"].hide()
1723                                         self["rufuml_inactive"].show()
1724                                         self["FBFRufuml"].setText(_('No call redirection active'))
1725                                 else:
1726                                         self["rufuml_inactive"].hide()
1727                                         self["rufuml_active"].show()
1728                                         if rufumlActive == 1:
1729                                                 self["FBFRufuml"].setText(_('One call redirection active'))
1730                                         else:
1731                                                 self["FBFRufuml"].setText(str(rufumlActive) + ' ' + _('call redirections active'))
1732
1733                 except KeyError:
1734                         debug("[FritzCallFBF] _fillMenu: " + traceback.format_exc())
1735
1736         def _toggleWlan(self):
1737                 if self._wlanActive:
1738                         debug("[FritzMenu] toggleWlan off")
1739                         fritzbox.changeWLAN('0')
1740                 else:
1741                         debug("[FritzMenu] toggleWlan off")
1742                         fritzbox.changeWLAN('1')
1743
1744         def _toggleMailbox(self, which):
1745                 debug("[FritzMenu] toggleMailbox")
1746                 if fritzbox.info[FBF_tamActive]:
1747                         debug("[FritzMenu] toggleMailbox off")
1748                         fritzbox.changeMailbox(which)
1749
1750         def _reset(self):
1751                 fritzbox.reset()
1752                 self._exit()
1753
1754         def _exit(self):
1755                 self._timer.stop()
1756                 self.close()
1757
1758
1759 class FritzDisplayCalls(Screen, HelpableScreen):
1760
1761         def __init__(self, session, text=""): #@UnusedVariable # pylint: disable=W0613
1762                 self.width = DESKTOP_WIDTH * scaleH(75, 85)/100
1763                 self.height = DESKTOP_HEIGHT * 0.75
1764                 dateFieldWidth = scaleH(180, 105)
1765                 dirFieldWidth = 16
1766                 lengthFieldWidth = scaleH(55, 45)
1767                 scrollbarWidth = scaleH(35, 35)
1768                 entriesWidth = self.width -scaleH(40, 5) -5
1769                 hereFieldWidth = entriesWidth -dirFieldWidth -5 -dateFieldWidth -5 -lengthFieldWidth -scrollbarWidth
1770                 fieldWidth = entriesWidth -dirFieldWidth -5 -5 -scrollbarWidth
1771                 fontSize = scaleV(22, 20)
1772                 itemHeight = 2*fontSize+5
1773                 entriesHeight = self.height -scaleV(15, 10) -5 -fontSize -5 -5 -5 -40 -5
1774                 buttonGap = (self.width -4*140)/5
1775                 buttonV = self.height -40
1776                 debug("[FritzDisplayCalls] width: " + str(self.width))
1777                 self.skin = """
1778                         <screen name="FritzDisplayCalls" position="center,center" size="%d,%d" title="Phone calls" >
1779                                 <eLabel position="0,0" size="%d,2" backgroundColor="#aaaaaa" />
1780                                 <widget name="statusbar" position="%d,%d" size="%d,%d" font="Regular;%d" backgroundColor="#aaaaaa" transparent="1" />
1781                                 <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
1782                                 <widget source="entries" render="Listbox" position="%d,%d" size="%d,%d" scrollbarMode="showOnDemand" transparent="1">
1783                                         <convert type="TemplatedMultiContent">
1784                                                 {"template": [
1785                                                                 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER, text = 1), # index 0 is the number, index 1 is date
1786                                                                 MultiContentEntryPixmapAlphaTest(pos = (%d,%d), size = (%d,%d), png = 2), # index 1 i direction pixmap
1787                                                                 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=1, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER, text = 3), # index 2 is remote name/number
1788                                                                 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER, text = 4), # index 3 is length of call
1789                                                                 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_RIGHT|RT_VALIGN_CENTER, text = 5), # index 4 is my number/name for number
1790                                                         ],
1791                                                 "fonts": [gFont("Regular", %d), gFont("Regular", %d)],
1792                                                 "itemHeight": %d
1793                                                 }
1794                                         </convert>
1795                                 </widget>
1796                                 <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
1797                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1798                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1799                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1800                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1801                                 <widget name="key_red" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1802                                 <widget name="key_green" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1803                                 <widget name="key_yellow" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1804                                 <widget name="key_blue" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1805                         </screen>""" % (
1806                                                 # scaleH(90, 75), scaleV(100, 78), # position 
1807                                                 self.width, self.height, # size
1808                                                 self.width, # eLabel width
1809                                                 scaleH(40, 5), scaleV(10, 5), # statusbar position
1810                                                 self.width, fontSize+5, # statusbar size
1811                                                 scaleV(21, 21), # statusbar font size
1812                                                 scaleV(10, 5)+5+fontSize+5, # eLabel position vertical
1813                                                 self.width, # eLabel width
1814                                                 scaleH(40, 5), scaleV(10, 5)+5+fontSize+5+5, # entries position
1815                                                 entriesWidth, entriesHeight, # entries size
1816                                                 5+dirFieldWidth+5, fontSize+5, dateFieldWidth, fontSize, # date pos/size
1817                                                 5, (itemHeight-dirFieldWidth)/2, dirFieldWidth, dirFieldWidth, # dir pos/size
1818                                                 5+dirFieldWidth+5, 5, fieldWidth, fontSize, # caller pos/size
1819                                                 2+dirFieldWidth+2+dateFieldWidth+5, fontSize+5, lengthFieldWidth, fontSize, # length pos/size
1820                                                 2+dirFieldWidth+2+dateFieldWidth+5+lengthFieldWidth+5, fontSize+5, hereFieldWidth, fontSize, # my number pos/size
1821                                                 fontSize-4, fontSize, # fontsize
1822                                                 itemHeight, # itemHeight
1823                                                 buttonV-5, # eLabel position vertical
1824                                                 self.width, # eLabel width
1825                                                 buttonGap, buttonV, "skin_default/buttons/red.png", # widget red
1826                                                 2*buttonGap+140, buttonV, "skin_default/buttons/green.png", # widget green
1827                                                 3*buttonGap+2*140, buttonV, "skin_default/buttons/yellow.png", # widget yellow
1828                                                 4*buttonGap+3*140, buttonV, "skin_default/buttons/blue.png", # widget blue
1829                                                 buttonGap, buttonV, scaleV(22, 21), # widget red
1830                                                 2*buttonGap+140, buttonV, scaleV(22, 21), # widget green
1831                                                 3*buttonGap+2*140, buttonV, scaleV(22, 21), # widget yellow
1832                                                 4*buttonGap+3*140, buttonV, scaleV(22, 21), # widget blue
1833                                                                                                                 )
1834                 # debug("[FritzDisplayCalls] skin: " + self.skin)
1835                 Screen.__init__(self, session)
1836                 HelpableScreen.__init__(self)
1837
1838                 # TRANSLATORS: keep it short, this is a button
1839                 self["key_yellow"] = Button(_("All"))
1840                 # TRANSLATORS: keep it short, this is a button
1841                 self["key_red"] = Button(_("Missed"))
1842                 # TRANSLATORS: keep it short, this is a button
1843                 self["key_blue"] = Button(_("Incoming"))
1844                 # TRANSLATORS: keep it short, this is a button
1845                 self["key_green"] = Button(_("Outgoing"))
1846
1847                 self["setupActions"] = ActionMap(["OkCancelActions", "ColorActions"],
1848                 {
1849                         "yellow": (lambda: self.display(FBF_ALL_CALLS)),
1850                         "red": (lambda: self.display(FBF_MISSED_CALLS)),
1851                         "blue": (lambda: self.display(FBF_IN_CALLS)),
1852                         "green": (lambda: self.display(FBF_OUT_CALLS)),
1853                         "cancel": self.ok,
1854                         "ok": self.showEntry, }, - 2)
1855
1856                 # TRANSLATORS: keep it short, this is a help text
1857                 self.helpList.append((self["setupActions"], "OkCancelActions", [("ok", _("Show details of entry"))]))
1858                 # TRANSLATORS: keep it short, this is a help text
1859                 self.helpList.append((self["setupActions"], "OkCancelActions", [("cancel", _("Quit"))]))
1860                 # TRANSLATORS: keep it short, this is a help text
1861                 self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("Display all calls"))]))
1862                 # TRANSLATORS: keep it short, this is a help text
1863                 self.helpList.append((self["setupActions"], "ColorActions", [("red", _("Display missed calls"))]))
1864                 # TRANSLATORS: keep it short, this is a help text
1865                 self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("Display incoming calls"))]))
1866                 # TRANSLATORS: keep it short, this is a help text
1867                 self.helpList.append((self["setupActions"], "ColorActions", [("green", _("Display outgoing calls"))]))
1868
1869                 self["statusbar"] = Label(_("Getting calls from FRITZ!Box..."))
1870                 self.list = []
1871                 self["entries"] = List(self.list)
1872                 #=======================================================================
1873                 # fontSize = scaleV(22, 18)
1874                 # fontHeight = scaleV(24, 20)
1875                 # self["entries"].l.setFont(0, gFont("Regular", fontSize))
1876                 # self["entries"].l.setItemHeight(fontHeight)
1877                 #=======================================================================
1878                 debug("[FritzDisplayCalls] init: '''%s'''" % config.plugins.FritzCall.fbfCalls.value)
1879                 self.display()
1880                 self.onLayoutFinish.append(self.setWindowTitle)
1881
1882         def setWindowTitle(self):
1883                 # TRANSLATORS: this is a window title.
1884                 self.setTitle(_("Phone calls"))
1885
1886         def ok(self):
1887                 self.close()
1888
1889         def display(self, which=config.plugins.FritzCall.fbfCalls.value):
1890                 debug("[FritzDisplayCalls] display")
1891                 config.plugins.FritzCall.fbfCalls.value = which
1892                 config.plugins.FritzCall.fbfCalls.save()
1893                 fritzbox.getCalls(self, lambda x: self.gotCalls(x, which), which)
1894
1895         def gotCalls(self, listOfCalls, which):
1896                 debug("[FritzDisplayCalls] gotCalls")
1897                 self.updateStatus(fbfCallsChoices[which] + " (" + str(len(listOfCalls)) + ")")
1898
1899                 directout = LoadPixmap(resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/callout.png"))
1900                 directin = LoadPixmap(resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/callin.png"))
1901                 directfailed = LoadPixmap(resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/callinfailed.png"))
1902                 def pixDir(direct):
1903                         if direct == FBF_OUT_CALLS:
1904                                 direct = directout
1905                         elif direct == FBF_IN_CALLS:
1906                                 direct = directin
1907                         else:
1908                                 direct = directfailed
1909                         return direct
1910
1911                 # debug("[FritzDisplayCalls] gotCalls: %s" %repr(listOfCalls))
1912                 self.list = [(number, date[:6] + ' ' + date[9:14], pixDir(direct), remote, length, here) for (number, date, direct, remote, length, here) in listOfCalls]
1913                 self["entries"].setList(self.list)
1914                 if len(self.list) > 1:
1915                         self["entries"].setIndex(1)
1916
1917         def updateStatus(self, text):
1918                 if self.has_key("statusbar"):
1919                         self["statusbar"].setText(_("Getting calls from FRITZ!Box...") + ' ' + text)
1920
1921         def showEntry(self):
1922                 debug("[FritzDisplayCalls] showEntry")
1923                 cur = self["entries"].getCurrent()
1924                 if cur:
1925                         if cur[0]:
1926                                 debug("[FritzDisplayCalls] showEntry %s" % (cur[0]))
1927                                 number = cur[0]
1928                                 fullname = phonebook.search(cur[0])
1929                                 if fullname:
1930                                         # we have a name for this number
1931                                         name = fullname
1932                                         self.session.open(FritzOfferAction, self, number, name)
1933                                 elif cur[3]:
1934                                         name = cur[3]
1935                                         self.session.open(FritzOfferAction, self, number, name)
1936                                 else:
1937                                         # we don't
1938                                         fullname = resolveNumberWithAvon(number, config.plugins.FritzCall.country.value)
1939                                         if fullname:
1940                                                 name = fullname
1941                                                 self.session.open(FritzOfferAction, self, number, name)
1942                                         else:
1943                                                 self.session.open(FritzOfferAction, self, number)
1944                         else:
1945                                 # we do not even have a number...
1946                                 self.session.open(MessageBox,
1947                                                   _("UNKNOWN"),
1948                                                   type=MessageBox.TYPE_INFO)
1949
1950
1951 class FritzOfferAction(Screen):
1952
1953         def __init__(self, session, parent, number, name=""):
1954                 # the layout will completely be recalculated in finishLayout
1955                 self.skin = """
1956                         <screen name="FritzOfferAction" title="Do what?" >
1957                                 <widget name="text" size="%d,%d" font="Regular;%d" />
1958                                 <widget name="FacePixmap" size="%d,%d" alphatest="on" />
1959                                 <widget name="key_red_p" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1960                                 <widget name="key_green_p" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1961                                 <widget name="key_yellow_p" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1962                                 <widget name="key_red" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1963                                 <widget name="key_green" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1964                                 <widget name="key_yellow" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1965                         </screen>""" % (
1966                                                         DESKTOP_WIDTH, DESKTOP_HEIGHT, # set maximum size
1967                                                         scaleH(22,21), # text
1968                                                         DESKTOP_WIDTH, DESKTOP_HEIGHT, # set maximum size
1969                                                         "skin_default/buttons/red.png",
1970                                                         "skin_default/buttons/green.png",
1971                                                         "skin_default/buttons/yellow.png",
1972                                                         ) 
1973                 debug("[FritzOfferAction] init: %s, %s" %(number, name))
1974                 Screen.__init__(self, session)
1975         
1976                 # TRANSLATORS: keep it short, this is a button
1977                 self["key_red"] = Button(_("Lookup"))
1978                 # TRANSLATORS: keep it short, this is a button
1979                 self["key_green"] = Button(_("Call"))
1980                 # TRANSLATORS: keep it short, this is a button
1981                 self["key_yellow"] = Button(_("Save"))
1982                 # TRANSLATORS: keep it short, this is a button
1983                 # self["key_blue"] = Button(_("Search"))
1984
1985                 self["FritzOfferActions"] = ActionMap(["OkCancelActions", "ColorActions"],
1986                 {
1987                         "red": self._lookup,
1988                         "green": self._call,
1989                         "yellow": self._add,
1990                         "cancel": self._exit,
1991                         "ok": self._exit, }, - 2)
1992
1993                 self._session = session
1994                 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0] == "0":
1995                         number = number[1:]
1996                 self._number = number
1997                 self._name = name.replace("\n", ", ")
1998                 self["text"] = Label(number + "\n\n" + name.replace(", ", "\n"))
1999                 self._parent = parent
2000                 self._lookupState = 0
2001                 self["key_red_p"] = Pixmap()
2002                 self["key_green_p"] = Pixmap()
2003                 self["key_yellow_p"] = Pixmap()
2004                 self["FacePixmap"] = Pixmap()
2005                 self.onLayoutFinish.append(self._finishLayout)
2006                 self.onLayoutFinish.append(self.setWindowTitle)
2007
2008         def setWindowTitle(self):
2009                 # TRANSLATORS: this is a window title.
2010                 self.setTitle(_("Do what?"))
2011
2012         def _finishLayout(self):
2013                 # pylint: disable=W0142
2014                 debug("[FritzCall] FritzOfferAction/finishLayout number: %s/%s" % (self._number, self._name))
2015
2016                 faceFile = findFace(self._number, self._name)
2017                 picPixmap = LoadPixmap(faceFile)
2018                 if not picPixmap:       # that means most probably, that the picture is not 8 bit...
2019                         Notifications.AddNotification(MessageBox, _("Found picture\n\n%s\n\nBut did not load. Probably not PNG, 8-bit") %faceFile, type = MessageBox.TYPE_ERROR)
2020                         picPixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/icons/input_error.png"))
2021                 picSize = picPixmap.size()
2022                 self["FacePixmap"].instance.setPixmap(picPixmap)
2023
2024                 noButtons = 3
2025                 # recalculate window size
2026                 textSize = self["text"].getSize()
2027                 textSize = (textSize[0]+20, textSize[1]+20) # don't know, why, but size is too small
2028                 debug("[FritzCall] FritzOfferAction/finishLayout textsize: %s/%s" % textSize)
2029                 textSize = eSize(*textSize)
2030                 width = max(scaleH(620, 545), noButtons*145, picSize.width() + textSize.width() + 30)
2031                 height = max(picSize.height()+5, textSize.height()+5, scaleV(-1, 136)) + 5 + 40 + 5
2032                 buttonsGap = (width-noButtons*140)/(noButtons+1)
2033                 buttonsVPos = height-40-5
2034                 wSize = (width, height)
2035                 wSize = eSize(*wSize)
2036
2037                 # center the smaller vertically
2038                 hGap = (width-picSize.width()-textSize.width())/3
2039                 picPos = (hGap, (height-50-picSize.height())/2+5)
2040                 textPos = (hGap+picSize.width()+hGap, (height-50-textSize.height())/2+5)
2041
2042                 # resize screen
2043                 self.instance.resize(wSize)
2044                 # resize text
2045                 self["text"].instance.resize(textSize)
2046                 # resize pixmap
2047                 self["FacePixmap"].instance.resize(picSize)
2048                 # move buttons
2049                 buttonPos = (buttonsGap, buttonsVPos)
2050                 self["key_red_p"].instance.move(ePoint(*buttonPos))
2051                 self["key_red"].instance.move(ePoint(*buttonPos))
2052                 buttonPos = (buttonsGap+140+buttonsGap, buttonsVPos)
2053                 self["key_green_p"].instance.move(ePoint(*buttonPos))
2054                 self["key_green"].instance.move(ePoint(*buttonPos))
2055                 buttonPos = (buttonsGap+140+buttonsGap+140+buttonsGap, buttonsVPos)
2056                 self["key_yellow_p"].instance.move(ePoint(*buttonPos))
2057                 self["key_yellow"].instance.move(ePoint(*buttonPos))
2058                 # move text
2059                 self["text"].instance.move(ePoint(*textPos))
2060                 # move pixmap
2061                 self["FacePixmap"].instance.move(ePoint(*picPos))
2062                 # center window
2063                 self.instance.move(ePoint((DESKTOP_WIDTH-wSize.width())/2, (DESKTOP_HEIGHT-wSize.height())/2))
2064
2065         def _setTextAndResize(self, message):
2066                 # pylint: disable=W0142
2067                 self["text"].instance.resize(eSize(*(DESKTOP_WIDTH, DESKTOP_HEIGHT)))
2068                 self["text"].setText(self._number + "\n\n" + message)
2069                 self._finishLayout()
2070
2071         def _lookup(self):
2072                 phonebookLocation = config.plugins.FritzCall.phonebookLocation.value
2073                 if self._lookupState == 0:
2074                         self._lookupState = 1
2075                         self._setTextAndResize(_("Reverse searching..."))
2076                         ReverseLookupAndNotifier(self._number, self._lookedUp, "UTF-8", config.plugins.FritzCall.country.value)
2077                         return
2078                 if self._lookupState == 1 and os.path.exists(os.path.join(phonebookLocation, "PhoneBook.csv")):
2079                         self._setTextAndResize(_("Searching in Outlook export..."))
2080                         self._lookupState = 2
2081                         self._lookedUp(self._number, FritzOutlookCSV.findNumber(self._number, os.path.join(phonebookLocation, "PhoneBook.csv"))) #@UndefinedVariable
2082                         return
2083                 else:
2084                         self._lookupState = 2
2085                 if self._lookupState == 2 and os.path.exists(os.path.join(phonebookLocation, "PhoneBook.ldif")):
2086                         self._setTextAndResize(_("Searching in LDIF..."))
2087                         self._lookupState = 0
2088                         FritzLDIF.FindNumber(self._number, open(os.path.join(phonebookLocation, "PhoneBook.ldif")), self._lookedUp)
2089                         return
2090                 else:
2091                         self._lookupState = 0
2092                         self._lookup()
2093
2094         def _lookedUp(self, number, name):
2095                 name = handleReverseLookupResult(name)
2096                 if not name:
2097                         if self._lookupState == 1:
2098                                 name = _("No result from reverse lookup")
2099                         elif self._lookupState == 2:
2100                                 name = _("No result from Outlook export")
2101                         else:
2102                                 name = _("No result from LDIF")
2103                 self._name = name
2104                 self._number = number
2105                 debug("[FritzOfferAction] lookedUp: " + str(name.replace(", ", "\n")))
2106                 self._setTextAndResize(str(name.replace(", ", "\n")))
2107
2108         def _call(self):
2109                 debug("[FritzOfferAction] add: %s" %self._number)
2110                 fritzbox.dial(self._number)
2111                 self._exit()
2112
2113         def _add(self):
2114                 debug("[FritzOfferAction] add: %s, %s" %(self._number, self._name))
2115                 phonebook.FritzDisplayPhonebook(self._session).add(self._parent, self._number, self._name)
2116                 self._exit()
2117
2118         def _exit(self):
2119                 self.close()
2120
2121
2122 class FritzCallPhonebook:
2123         def __init__(self):
2124                 debug("[FritzCallPhonebook] init")
2125                 # Beware: strings in phonebook.phonebook have to be in utf-8!
2126                 self.phonebook = {}
2127                 self.reload()
2128
2129         def reload(self):
2130                 debug("[FritzCallPhonebook] reload")
2131                 # Beware: strings in phonebook.phonebook have to be in utf-8!
2132                 self.phonebook = {}
2133
2134                 if not config.plugins.FritzCall.enable.value:
2135                         return
2136
2137                 phonebookFilename = os.path.join(config.plugins.FritzCall.phonebookLocation.value, "PhoneBook.txt")
2138                 if config.plugins.FritzCall.phonebook.value and os.path.exists(phonebookFilename):
2139                         debug("[FritzCallPhonebook] reload: read " + phonebookFilename)
2140                         phonebookTxtCorrupt = False
2141                         self.phonebook = {}
2142                         for line in open(phonebookFilename):
2143                                 try:
2144                                         # Beware: strings in phonebook.phonebook have to be in utf-8!
2145                                         line = line.decode("utf-8")
2146                                 except UnicodeDecodeError: # this is just for the case, somebody wrote latin1 chars into PhoneBook.txt
2147                                         try:
2148                                                 line = line.decode("iso-8859-1")
2149                                                 debug("[FritzCallPhonebook] Fallback to ISO-8859-1 in %s" % line)
2150                                                 phonebookTxtCorrupt = True
2151                                         except UnicodeDecodeError:
2152                                                 debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2153                                                 phonebookTxtCorrupt = True
2154                                 line = line.encode("utf-8")
2155                                 elems = line.split('#')
2156                                 if len(elems) == 2:
2157                                         try:
2158                                                 self.phonebook[elems[0]] = elems[1]
2159                                         except ValueError: # how could this possibly happen?!?!
2160                                                 debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2161                                                 phonebookTxtCorrupt = True
2162                                 else:
2163                                         debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2164                                         phonebookTxtCorrupt = True
2165                                         
2166                                 #===============================================================
2167                                 # found = re.match("^(\d+)#(.*)$", line)
2168                                 # if found:
2169                                 #       try:
2170                                 #               self.phonebook[found.group(1)] = found.group(2)
2171                                 #       except ValueError: # how could this possibly happen?!?!
2172                                 #               debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2173                                 #               phonebookTxtCorrupt = True
2174                                 # else:
2175                                 #       debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2176                                 #       phonebookTxtCorrupt = True
2177                                 #===============================================================
2178
2179                         if phonebookTxtCorrupt:
2180                                 # dump phonebook to PhoneBook.txt
2181                                 debug("[FritzCallPhonebook] dump Phonebook.txt")
2182                                 try:
2183                                         os.rename(phonebookFilename, phonebookFilename + ".bck")
2184                                         fNew = open(phonebookFilename, 'w')
2185                                         # Beware: strings in phonebook.phonebook are utf-8!
2186                                         for (number, name) in self.phonebook.iteritems():
2187                                                 # Beware: strings in PhoneBook.txt have to be in utf-8!
2188                                                 fNew.write(number + "#" + name.encode("utf-8"))
2189                                         fNew.close()
2190                                 except (IOError, OSError):
2191                                         debug("[FritzCallPhonebook] error renaming or writing to %s" %phonebookFilename)
2192
2193 #===============================================================================
2194 #               #
2195 #               # read entries from Outlook export
2196 #               #
2197 #               # not reliable with coding yet
2198 #               # 
2199 #               # import csv exported from Outlook 2007 with csv(Windows)
2200 #               csvFilename = "/tmp/PhoneBook.csv"
2201 #               if config.plugins.FritzCall.phonebook.value and os.path.exists(csvFilename):
2202 #                       try:
2203 #                               readOutlookCSV(csvFilename, self.add)
2204 #                               os.rename(csvFilename, csvFilename + ".done")
2205 #                       except ImportError:
2206 #                               debug("[FritzCallPhonebook] CSV import failed" %line)
2207 #===============================================================================
2208
2209                 
2210 #===============================================================================
2211 #               #
2212 #               # read entries from LDIF
2213 #               #
2214 #               # import ldif exported from Thunderbird 2.0.0.19
2215 #               ldifFilename = "/tmp/PhoneBook.ldif"
2216 #               if config.plugins.FritzCall.phonebook.value and os.path.exists(ldifFilename):
2217 #                       try:
2218 #                               parser = MyLDIF(open(ldifFilename), self.add)
2219 #                               parser.parse()
2220 #                               os.rename(ldifFilename, ldifFilename + ".done")
2221 #                       except ImportError:
2222 #                               debug("[FritzCallPhonebook] LDIF import failed" %line)
2223 #===============================================================================
2224                 
2225                 if fritzbox and config.plugins.FritzCall.fritzphonebook.value:
2226                         fritzbox.loadFritzBoxPhonebook()
2227
2228         def search(self, number):
2229                 # debug("[FritzCallPhonebook] Searching for %s" %number)
2230                 name = ""
2231                 if not self.phonebook or not number:
2232                         return
2233
2234                 if config.plugins.FritzCall.prefix.value:
2235                         prefix = config.plugins.FritzCall.prefix.value
2236                         if number[0] != '0':
2237                                 number = prefix + number
2238                                 # debug("[FritzCallPhonebook] search: added prefix: %s" %number)
2239                         elif number[:len(prefix)] == prefix and self.phonebook.has_key(number[len(prefix):]):
2240                                 # debug("[FritzCallPhonebook] search: same prefix")
2241                                 name = self.phonebook[number[len(prefix):]]
2242                                 # debug("[FritzCallPhonebook] search: result: %s" %name)
2243                 else:
2244                         prefix = ""
2245                                 
2246                 if not name and self.phonebook.has_key(number):
2247                         name = self.phonebook[number]
2248                                 
2249                 return name.replace(", ", "\n").strip()
2250
2251         def add(self, number, name):
2252                 '''
2253                 
2254                 @param number: number of entry
2255                 @param name: name of entry, has to be in utf-8
2256                 '''
2257                 debug("[FritzCallPhonebook] add")
2258                 name = name.replace("\n", ", ").replace('#','') # this is just for safety reasons. add should only be called with newlines converted into commas
2259                 self.remove(number)
2260                 self.phonebook[number] = name
2261                 if number and number != 0:
2262                         if config.plugins.FritzCall.phonebook.value:
2263                                 try:
2264                                         name = name.strip() + "\n"
2265                                         string = "%s#%s" % (number, name)
2266                                         # Beware: strings in PhoneBook.txt have to be in utf-8!
2267                                         f = open(os.path.join(config.plugins.FritzCall.phonebookLocation.value, "PhoneBook.txt"), 'a')
2268                                         f.write(string)
2269                                         f.close()
2270                                         debug("[FritzCallPhonebook] added %s with %s to Phonebook.txt" % (number, name.strip()))
2271                                         return True
2272         
2273                                 except IOError:
2274                                         return False
2275
2276         def remove(self, number):
2277                 if number in self.phonebook:
2278                         debug("[FritzCallPhonebook] remove entry in phonebook")
2279                         del self.phonebook[number]
2280                         if config.plugins.FritzCall.phonebook.value:
2281                                 try:
2282                                         phonebookFilename = os.path.join(config.plugins.FritzCall.phonebookLocation.value, "PhoneBook.txt")
2283                                         debug("[FritzCallPhonebook] remove entry in Phonebook.txt")
2284                                         fOld = open(phonebookFilename, 'r')
2285                                         fNew = open(phonebookFilename + str(os.getpid()), 'w')
2286                                         line = fOld.readline()
2287                                         while (line):
2288                                                 elems = line.split('#')
2289                                                 if len(elems) == 2 and not elems[0] == number:
2290                                                         fNew.write(line)
2291                                                 line = fOld.readline()
2292                                         fOld.close()
2293                                         fNew.close()
2294                                         # os.remove(phonebookFilename)
2295                                         eBackgroundFileEraser.getInstance().erase(phonebookFilename)
2296                                         os.rename(phonebookFilename + str(os.getpid()), phonebookFilename)
2297                                         debug("[FritzCallPhonebook] removed %s from Phonebook.txt" % number)
2298                                         return True
2299         
2300                                 except (IOError, OSError):
2301                                         debug("[FritzCallPhonebook] error removing %s from %s" %(number, phonebookFilename))
2302                 return False
2303
2304         class FritzDisplayPhonebook(Screen, HelpableScreen, NumericalTextInput):
2305         
2306                 def __init__(self, session):
2307                         self.entriesWidth = DESKTOP_WIDTH * scaleH(75, 85)/100
2308                         self.height = DESKTOP_HEIGHT * 0.75
2309                         numberFieldWidth = scaleH(220, 160)
2310                         fieldWidth = self.entriesWidth -5 -numberFieldWidth -10
2311                         fontSize = scaleV(22, 18)
2312                         fontHeight = scaleV(24, 20)
2313                         buttonGap = (self.entriesWidth-4*140)/5
2314                         debug("[FritzDisplayPhonebook] width: " + str(self.entriesWidth))
2315                         self.skin = """
2316                                 <screen name="FritzDisplayPhonebook" position="center,center" size="%d,%d" title="Phonebook" >
2317                                         <eLabel position="0,0" size="%d,2" backgroundColor="#aaaaaa" />
2318                                         <widget source="entries" render="Listbox" position="%d,%d" size="%d,%d" scrollbarMode="showOnDemand" transparent="1">
2319                                                 <convert type="TemplatedMultiContent">
2320                                                         {"template": [
2321                                                                         MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT, text = 1), # index 0 is the name, index 1 is shortname
2322                                                                         MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT, text = 2), # index 2 is number
2323                                                                 ],
2324                                                         "fonts": [gFont("Regular", %d)],
2325                                                         "itemHeight": %d
2326                                                         }
2327                                                 </convert>
2328                                         </widget>
2329                                         <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
2330                                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2331                                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2332                                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2333                                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2334                                         <widget name="key_red" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
2335                                         <widget name="key_green" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
2336                                         <widget name="key_yellow" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
2337                                         <widget name="key_blue" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
2338                                 </screen>""" % (
2339                                                 # scaleH(90, 75), scaleV(100, 73), # position 
2340                                                 self.entriesWidth, self.height, # size
2341                                                 self.entriesWidth, # eLabel width
2342                                                 scaleH(40, 5), scaleV(20, 5), # entries position
2343                                                 self.entriesWidth-scaleH(40, 5), self.height-scaleV(20, 5)-5-5-40, # entries size
2344                                                 0, 0, fieldWidth, scaleH(24,20), # name pos/size
2345                                                 fieldWidth +5, 0, numberFieldWidth, scaleH(24,20), # dir pos/size
2346                                                 fontSize, # fontsize
2347                                                 fontHeight, # itemHeight
2348                                                 self.height-40-5, # eLabel position vertical
2349                                                 self.entriesWidth, # eLabel width
2350                                                 buttonGap, self.height-40, "skin_default/buttons/red.png", # ePixmap red
2351                                                 2*buttonGap+140, self.height-40, "skin_default/buttons/green.png", # ePixmap green
2352                                                 3*buttonGap+2*140, self.height-40, "skin_default/buttons/yellow.png", # ePixmap yellow
2353                                                 4*buttonGap+3*140, self.height-40, "skin_default/buttons/blue.png", # ePixmap blue
2354                                                 buttonGap, self.height-40, scaleV(22, 21), # widget red
2355                                                 2*buttonGap+140, self.height-40, scaleV(22, 21), # widget green
2356                                                 3*buttonGap+2*140, self.height-40, scaleV(22, 21), # widget yellow
2357                                                 4*buttonGap+3*140, self.height-40, scaleV(22, 21), # widget blue
2358                                                 )
2359         
2360                         # debug("[FritzDisplayCalls] skin: " + self.skin)
2361                         Screen.__init__(self, session)
2362                         NumericalTextInput.__init__(self)
2363                         HelpableScreen.__init__(self)
2364                 
2365                         # TRANSLATORS: keep it short, this is a button
2366                         self["key_red"] = Button(_("Delete"))
2367                         # TRANSLATORS: keep it short, this is a button
2368                         self["key_green"] = Button(_("New"))
2369                         # TRANSLATORS: keep it short, this is a button
2370                         self["key_yellow"] = Button(_("Edit"))
2371                         # TRANSLATORS: keep it short, this is a button
2372                         self["key_blue"] = Button(_("Search"))
2373         
2374                         self["setupActions"] = ActionMap(["OkCancelActions", "ColorActions"],
2375                         {
2376                                 "red": self.delete,
2377                                 "green": self.add,
2378                                 "yellow": self.edit,
2379                                 "blue": self.search,
2380                                 "cancel": self.exit,
2381                                 "ok": self.showEntry, }, - 2)
2382         
2383                         # TRANSLATORS: keep it short, this is a help text
2384                         self.helpList.append((self["setupActions"], "OkCancelActions", [("ok", _("Show details of entry"))]))
2385                         # TRANSLATORS: keep it short, this is a help text
2386                         self.helpList.append((self["setupActions"], "OkCancelActions", [("cancel", _("Quit"))]))
2387                         # TRANSLATORS: keep it short, this is a help text
2388                         self.helpList.append((self["setupActions"], "ColorActions", [("red", _("Delete entry"))]))
2389                         # TRANSLATORS: keep it short, this is a help text
2390                         self.helpList.append((self["setupActions"], "ColorActions", [("green", _("Add entry to phonebook"))]))
2391                         # TRANSLATORS: keep it short, this is a help text
2392                         self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("Edit selected entry"))]))
2393                         # TRANSLATORS: keep it short, this is a help text
2394                         self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("Search (case insensitive)"))]))
2395         
2396                         self["entries"] = List([])
2397                         debug("[FritzCallPhonebook] displayPhonebook init")
2398                         self.help_window = None
2399                         self.sortlist = []
2400                         self.onLayoutFinish.append(self.setWindowTitle)
2401                         self.display()
2402
2403                 def setWindowTitle(self):
2404                         # TRANSLATORS: this is a window title.
2405                         self.setTitle(_("Phonebook"))
2406
2407                 def display(self, filterNumber=""):
2408                         debug("[FritzCallPhonebook] displayPhonebook/display")
2409                         self.sortlist = []
2410                         # Beware: strings in phonebook.phonebook are utf-8!
2411                         sortlistHelp = sorted((name.lower(), name, number) for (number, name) in phonebook.phonebook.iteritems())
2412                         for (low, name, number) in sortlistHelp:
2413                                 if number == "01234567890":
2414                                         continue
2415                                 try:
2416                                         low = low.decode("utf-8")
2417                                 except UnicodeDecodeError:  # this should definitely not happen
2418                                         try:
2419                                                 low = low.decode("iso-8859-1")
2420                                         except UnicodeDecodeError:
2421                                                 debug("[FritzCallPhonebook] displayPhonebook/display: corrupt phonebook entry for %s" % number)
2422                                                 # self.session.open(MessageBox, _("Corrupt phonebook entry\nfor number %s\nDeleting.") %number, type = MessageBox.TYPE_ERROR)
2423                                                 phonebook.remove(number)
2424                                                 continue
2425                                 else:
2426                                         if filterNumber:
2427                                                 filterNumber = filterNumber.lower()
2428                                                 if low.find(filterNumber) == - 1:
2429                                                         continue
2430                                         name = name.strip().decode("utf-8")
2431                                         number = number.strip().decode("utf-8")
2432                                         comma = name.find(',')
2433                                         if comma != -1:
2434                                                 shortname = name[:comma]
2435                                         else:
2436                                                 shortname = name
2437                                         number = number.encode("utf-8", "replace")
2438                                         name = name.encode("utf-8", "replace")
2439                                         shortname = shortname.encode('utf-8', 'replace')
2440                                         self.sortlist.append((name, shortname, number))
2441                                 
2442                         self["entries"].setList(self.sortlist)