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