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