[FanControl2] too much deleted metainfo inserted again
[vuplus_dvbapp-plugin] / fritzcall / src / plugin.py
1 # -*- coding: utf-8 -*-
2 '''
3 $Author: michael $
4 $Revision: 640 $
5 $Date: 2011-01-15 16:17:12 +0100 (Sa, 15. Jan 2011) $
6 $Id: plugin.py 640 2011-01-15 15:17:12Z michael $
7 '''
8 from Screens.Screen import Screen
9 from Screens.MessageBox import MessageBox
10 from Screens.NumericalTextInputHelpDialog import NumericalTextInputHelpDialog
11 from Screens.InputBox import InputBox
12 from Screens import Standby
13 from Screens.HelpMenu import HelpableScreen
14
15 from enigma import eTimer, eSize, ePoint #@UnresolvedImport # pylint: disable=E0611
16 from enigma import eDVBVolumecontrol
17 from enigma import eBackgroundFileEraser
18 #BgFileEraser = eBackgroundFileEraser.getInstance()
19 #BgFileEraser.erase("blabla.txt")
20
21 from Components.ActionMap import ActionMap
22 from Components.Label import Label
23 from Components.Button import Button
24 from Components.Pixmap import Pixmap
25 from Components.Sources.List import List
26 from Components.config import config, ConfigSubsection, ConfigSelection, ConfigEnableDisable, getConfigListEntry, ConfigText, ConfigInteger
27 from Components.ConfigList import ConfigListScreen
28 from Components.Harddisk import harddiskmanager
29 try:
30         from Components.config import ConfigPassword
31 except ImportError:
32         ConfigPassword = ConfigText
33
34 from Plugins.Plugin import PluginDescriptor
35 from Tools import Notifications
36 from Tools.NumericalTextInput import NumericalTextInput
37 from Tools.Directories import resolveFilename, SCOPE_PLUGINS, SCOPE_SKIN_IMAGE, SCOPE_CONFIG, SCOPE_MEDIA
38 from Tools.LoadPixmap import LoadPixmap
39 from GlobalActions import globalActionMap # for muting
40
41 from twisted.internet import reactor #@UnresolvedImport
42 from twisted.internet.protocol import ReconnectingClientFactory #@UnresolvedImport
43 from twisted.protocols.basic import LineReceiver #@UnresolvedImport
44 from twisted.web.client import getPage #@UnresolvedImport
45
46 from urllib import urlencode 
47 import re, time, os, hashlib, traceback
48
49 from nrzuname import ReverseLookupAndNotifier, html2unicode
50 import FritzOutlookCSV, FritzLDIF
51 from . import _, initDebug, debug #@UnresolvedImport # pylint: disable=E0611,F0401
52
53 from enigma import getDesktop
54 DESKTOP_WIDTH = getDesktop(0).size().width()
55 DESKTOP_HEIGHT = getDesktop(0).size().height()
56
57 #
58 # this is pure magic.
59 # It returns the first value, if HD (1280x720),
60 # the second if SD (720x576),
61 # else something scaled accordingly
62 # if one of the parameters is -1, scale proportionally
63 #
64 def scaleH(y2, y1):
65         if y2 == -1:
66                 y2 = y1*1280/720
67         elif y1 == -1:
68                 y1 = y2*720/1280
69         return scale(y2, y1, 1280, 720, DESKTOP_WIDTH)
70 def scaleV(y2, y1):
71         if y2 == -1:
72                 y2 = y1*720/576
73         elif y1 == -1:
74                 y1 = y2*576/720
75         return scale(y2, y1, 720, 576, DESKTOP_HEIGHT)
76 def scale(y2, y1, x2, x1, x):
77         return (y2 - y1) * (x - x1) / (x2 - x1) + y1
78
79 my_global_session = None
80
81 config.plugins.FritzCall = ConfigSubsection()
82 config.plugins.FritzCall.debug = ConfigEnableDisable(default=False)
83 #config.plugins.FritzCall.muteOnCall = ConfigSelection(choices=[(None, _("no")), ("ring", _("on ring")), ("connect", _("on connect"))])
84 #config.plugins.FritzCall.muteOnCall = ConfigSelection(choices=[(None, _("no")), ("ring", _("on ring"))])
85 config.plugins.FritzCall.muteOnCall = ConfigEnableDisable(default=False)
86 config.plugins.FritzCall.hostname = ConfigText(default="fritz.box", fixed_size=False)
87 config.plugins.FritzCall.afterStandby = ConfigSelection(choices=[("none", _("show nothing")), ("inList", _("show as list")), ("each", _("show each call"))])
88 config.plugins.FritzCall.filter = ConfigEnableDisable(default=False)
89 config.plugins.FritzCall.filtermsn = ConfigText(default="", fixed_size=False)
90 config.plugins.FritzCall.filtermsn.setUseableChars('0123456789,')
91 config.plugins.FritzCall.filterCallList = ConfigEnableDisable(default=True)
92 config.plugins.FritzCall.showOutgoing = ConfigEnableDisable(default=False)
93 config.plugins.FritzCall.timeout = ConfigInteger(default=15, limits=(0, 60))
94 config.plugins.FritzCall.lookup = ConfigEnableDisable(default=False)
95 config.plugins.FritzCall.internal = ConfigEnableDisable(default=False)
96 config.plugins.FritzCall.fritzphonebook = ConfigEnableDisable(default=False)
97 config.plugins.FritzCall.phonebook = ConfigEnableDisable(default=False)
98 config.plugins.FritzCall.addcallers = ConfigEnableDisable(default=False)
99 config.plugins.FritzCall.enable = ConfigEnableDisable(default=False)
100 config.plugins.FritzCall.password = ConfigPassword(default="", fixed_size=False)
101 config.plugins.FritzCall.extension = ConfigText(default='1', fixed_size=False)
102 config.plugins.FritzCall.extension.setUseableChars('0123456789')
103 config.plugins.FritzCall.showType = ConfigEnableDisable(default=True)
104 config.plugins.FritzCall.showShortcut = ConfigEnableDisable(default=False)
105 config.plugins.FritzCall.showVanity = ConfigEnableDisable(default=False)
106 config.plugins.FritzCall.prefix = ConfigText(default="", fixed_size=False)
107 config.plugins.FritzCall.prefix.setUseableChars('0123456789')
108 config.plugins.FritzCall.connectionVerbose = ConfigEnableDisable(default=True)
109
110
111 def getMountedDevs():
112         def handleMountpoint(loc):
113                 # debug("[FritzCall] handleMountpoint: %s" %repr(loc))
114                 mp = loc[0]
115                 while mp[-1] == '/':
116                         mp = mp[:-1]
117                 #=======================================================================
118                 # if os.path.exists(os.path.join(mp, "PhoneBook.txt")):
119                 #       if os.access(os.path.join(mp, "PhoneBook.txt"), os.W_OK):
120                 #               desc = ' *'
121                 #       else:
122                 #               desc = ' -'
123                 # else:
124                 #       desc = ''
125                 # desc = loc[1] + desc
126                 #=======================================================================
127                 desc = loc[1]
128                 return (mp, desc + " (" + mp + ")")
129
130         mountedDevs = [(resolveFilename(SCOPE_CONFIG), _("Flash")),
131                                    (resolveFilename(SCOPE_MEDIA, "cf"), _("Compact Flash")),
132                                    (resolveFilename(SCOPE_MEDIA, "usb"), _("USB Device"))]
133         mountedDevs += map(lambda p: (p.mountpoint, (_(p.description) if p.description else "")), harddiskmanager.getMountedPartitions(True))
134         mediaDir = resolveFilename(SCOPE_MEDIA)
135         for p in os.listdir(mediaDir):
136                 if os.path.join(mediaDir, p) not in [path[0] for path in mountedDevs]:
137                         mountedDevs.append((os.path.join(mediaDir, p), _("Media directory")))
138         debug("[FritzCall] getMountedDevs1: %s" %repr(mountedDevs))
139         mountedDevs = filter(lambda path: os.path.isdir(path[0]) and os.access(path[0], os.W_OK|os.X_OK), mountedDevs)
140         # put this after the write/executable check, that is far too slow...
141         netDir = resolveFilename(SCOPE_MEDIA, "net")
142         if os.path.isdir(netDir):
143                 mountedDevs += map(lambda p: (os.path.join(netDir, p), _("Network mount")), os.listdir(netDir))
144         mountedDevs = map(handleMountpoint, mountedDevs)
145         return mountedDevs
146 config.plugins.FritzCall.phonebookLocation = ConfigSelection(choices=getMountedDevs())
147
148 countryCodes = [
149         ("0049", _("Germany")),
150         ("0031", _("The Netherlands")),
151         ("0033", _("France")),
152         ("0039", _("Italy")),
153         ("0041", _("Switzerland")),
154         ("0043", _("Austria"))
155         ]
156 config.plugins.FritzCall.country = ConfigSelection(choices=countryCodes)
157
158 FBF_ALL_CALLS = "."
159 FBF_IN_CALLS = "1"
160 FBF_MISSED_CALLS = "2"
161 FBF_OUT_CALLS = "3"
162 fbfCallsChoices = {FBF_ALL_CALLS: _("All calls"),
163                                    FBF_IN_CALLS: _("Incoming calls"),
164                                    FBF_MISSED_CALLS: _("Missed calls"),
165                                    FBF_OUT_CALLS: _("Outgoing calls")
166                                    }
167 config.plugins.FritzCall.fbfCalls = ConfigSelection(choices=fbfCallsChoices)
168
169 config.plugins.FritzCall.name = ConfigText(default="", fixed_size=False)
170 config.plugins.FritzCall.number = ConfigText(default="", fixed_size=False)
171 config.plugins.FritzCall.number.setUseableChars('0123456789')
172
173 phonebook = None
174 fritzbox = None
175
176 avon = {}
177
178 def initAvon():
179         avonFileName = resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/avon.dat")
180         if os.path.exists(avonFileName):
181                 for line in open(avonFileName):
182                         line = line.decode("iso-8859-1").encode('utf-8')
183                         if line[0] == '#':
184                                 continue
185                         parts = line.split(':')
186                         if len(parts) == 2:
187                                 avon[parts[0].replace('-','').replace('*','').replace('/','')] = parts[1]
188
189 def resolveNumberWithAvon(number, countrycode):
190         if not number or number[0] != '0':
191                 return ""
192                 
193         countrycode = countrycode.replace('00','+')
194         if number[:2] == '00':
195                 normNumber = '+' + number[2:]
196         elif number[:1] == '0':
197                 normNumber = countrycode + number[1:]
198         else: # this should can not happen, but safety first
199                 return ""
200         
201         # debug('normNumer: ' + normNumber)
202         for i in reversed(range(min(10, len(number)))):
203                 if avon.has_key(normNumber[:i]):
204                         return '[' + avon[normNumber[:i]].strip() + ']'
205         return ""
206
207 def handleReverseLookupResult(name):
208         found = re.match("NA: ([^;]*);VN: ([^;]*);STR: ([^;]*);HNR: ([^;]*);PLZ: ([^;]*);ORT: ([^;]*)", name)
209         if found:
210                 ( name, firstname, street, streetno, zipcode, city ) = (found.group(1),
211                                                                                                 found.group(2),
212                                                                                                 found.group(3),
213                                                                                                 found.group(4),
214                                                                                                 found.group(5),
215                                                                                                 found.group(6)
216                                                                                                 )
217                 if firstname:
218                         name += ' ' + firstname
219                 if street or streetno or zipcode or city:
220                         name += ', '
221                 if street:
222                         name += street
223                 if streetno:
224                         name += ' ' + streetno
225                 if (street or streetno) and (zipcode or city):
226                         name += ', '
227                 if zipcode and city:
228                         name += zipcode + ' ' + city
229                 elif zipcode:
230                         name += zipcode
231                 elif city:
232                         name += city
233         return name
234
235 from xml.dom.minidom import parse
236 cbcInfos = {}
237 def initCbC():
238         callbycallFileName = resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/callbycall_world.xml")
239         if os.path.exists(callbycallFileName):
240                 dom = parse(callbycallFileName)
241                 for top in dom.getElementsByTagName("callbycalls"):
242                         for cbc in top.getElementsByTagName("country"):
243                                 code = cbc.getAttribute("code").replace("+","00")
244                                 cbcInfos[code] = cbc.getElementsByTagName("callbycall")
245         else:
246                 debug("[FritzCall] initCbC: callbycallFileName does not exist?!?!")
247
248 def stripCbCPrefix(number, countrycode):
249         if number and number[:2] != "00" and cbcInfos.has_key(countrycode):
250                 for cbc in cbcInfos[countrycode]:
251                         if len(cbc.getElementsByTagName("length"))<1 or len(cbc.getElementsByTagName("prefix"))<1:
252                                 debug("[FritzCall] stripCbCPrefix: entries for " + countrycode + " %s invalid")
253                                 return number
254                         length = int(cbc.getElementsByTagName("length")[0].childNodes[0].data)
255                         prefix = cbc.getElementsByTagName("prefix")[0].childNodes[0].data
256                         # if re.match('^'+prefix, number):
257                         if number[:len(prefix)] == prefix:
258                                 return number[length:]
259         return number
260
261 class FritzAbout(Screen):
262
263         def __init__(self, session):
264                 textFieldWidth = scaleV(350, 250)
265                 width = 5 + 150 + 20 + textFieldWidth + 5 + 175 + 5
266                 height = 5 + 175 + 5 + 25 + 5
267                 self.skin = """
268                         <screen name="FritzAbout" position="center,center" size="%d,%d" title="About FritzCall" >
269                                 <widget name="text" position="175,%d" size="%d,%d" font="Regular;%d" />
270                                 <ePixmap position="5,37" size="150,110" pixmap="%s" transparent="1" alphatest="blend" />
271                                 <ePixmap position="%d,5" size="175,175" pixmap="%s" transparent="1" alphatest="blend" />
272                                 <widget name="url" position="20,185" size="%d,25" font="Regular;%d" />
273                         </screen>""" % (
274                                                         width, height, # size
275                                                         (height-scaleV(150,130)) / 2, # text vertical position
276                                                         textFieldWidth,
277                                                         scaleV(150,130), # text height
278                                                         scaleV(24,21), # text font size
279                                                         resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/fritz.png"), # 150x110
280                                                         5 + 150 + 5 + textFieldWidth + 5, # qr code horizontal offset
281                                                         resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/website.png"), # 175x175
282                                                         width-40, # url width
283                                                         scaleV(24,21) # url font size
284                                                         )
285                 Screen.__init__(self, session)
286                 self["aboutActions"] = ActionMap(["OkCancelActions"],
287                 {
288                 "cancel": self.exit,
289                 "ok": self.exit,
290                 }, -2)
291                 self["text"] = Label(
292                                                         "FritzCall Plugin" + "\n\n" +
293                                                         "$Author: michael $"[1:-2] + "\n" +
294                                                         "$Revision: 640 $"[1:-2] + "\n" + 
295                                                         "$Date: 2011-01-15 16:17:12 +0100 (Sa, 15. Jan 2011) $"[1:23] + "\n"
296                                                         )
297                 self["url"] = Label("http://wiki.blue-panel.com/index.php/FritzCall")
298                 self.onLayoutFinish.append(self.setWindowTitle)
299
300         def setWindowTitle(self):
301                 # TRANSLATORS: this is a window title.
302                 self.setTitle(_("About FritzCall"))
303
304         def exit(self):
305                 self.close()
306
307 FBF_boxInfo = 0
308 FBF_upTime = 1
309 FBF_ipAddress = 2
310 FBF_wlanState = 3
311 FBF_dslState = 4
312 FBF_tamActive = 5
313 FBF_dectActive = 6
314 FBF_faxActive = 7
315 FBF_rufumlActive = 8
316
317 class FritzCallFBF:
318         def __init__(self):
319                 debug("[FritzCallFBF] __init__")
320                 self._callScreen = None
321                 self._md5LoginTimestamp = None
322                 self._md5Sid = '0000000000000000'
323                 self._callTimestamp = 0
324                 self._callList = []
325                 self._callType = config.plugins.FritzCall.fbfCalls.value
326                 self._phoneBookID = '0'
327                 self.info = None # (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive)
328                 self.getInfo(None)
329                 self.blacklist = ([], [])
330                 self.readBlacklist()
331
332         def _notify(self, text):
333                 debug("[FritzCallFBF] notify: " + text)
334                 self._md5LoginTimestamp = None
335                 if self._callScreen:
336                         debug("[FritzCallFBF] notify: try to close callScreen")
337                         self._callScreen.close()
338                         self._callScreen = None
339                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
340                         
341         def _login(self, callback=None):
342                 debug("[FritzCallFBF] _login")
343                 if self._callScreen:
344                         self._callScreen.updateStatus(_("login"))
345                 if self._md5LoginTimestamp and ((time.time() - self._md5LoginTimestamp) < float(9.5*60)) and self._md5Sid != '0000000000000000': # new login after 9.5 minutes inactivity 
346                         debug("[FritzCallFBF] _login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
347                         self._md5LoginTimestamp = time.time()
348                         callback(None)
349                 else:
350                         debug("[FritzCallFBF] _login: not logged in or outdated login")
351                         # http://fritz.box/cgi-bin/webcm?getpage=../html/login_sid.xml
352                         parms = urlencode({'getpage':'../html/login_sid.xml'})
353                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
354                         debug("[FritzCallFBF] _login: '" + url + "' parms: '" + parms + "'")
355                         getPage(url,
356                                 method="POST",
357                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
358                                                 }, postdata=parms).addCallback(lambda x: self._md5Login(callback,x)).addErrback(lambda x:self._oldLogin(callback,x))
359
360         def _oldLogin(self, callback, error): 
361                 debug("[FritzCallFBF] _oldLogin: " + repr(error))
362                 self._md5LoginTimestamp = None
363                 if config.plugins.FritzCall.password.value != "":
364                         parms = "login:command/password=%s" % (config.plugins.FritzCall.password.value)
365                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
366                         debug("[FritzCallFBF] _oldLogin: '" + url + "' parms: '" + parms + "'")
367                         getPage(url,
368                                 method="POST",
369                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
370                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
371                                                 }, postdata=parms).addCallback(self._gotPageLogin).addCallback(callback).addErrback(self._errorLogin)
372                 elif callback:
373                         debug("[FritzCallFBF] _oldLogin: no password, calling " + repr(callback))
374                         callback(None)
375
376         def _md5Login(self, callback, sidXml):
377                 def buildResponse(challenge, text):
378                         debug("[FritzCallFBF] _md5Login7buildResponse: challenge: " + challenge + ' text: ' + text)
379                         text = (challenge + '-' + text).decode('utf-8','ignore').encode('utf-16-le')
380                         for i in range(len(text)):
381                                 if ord(text[i]) > 255:
382                                         text[i] = '.'
383                         md5 = hashlib.md5()
384                         md5.update(text)
385                         debug("[FritzCallFBF] md5Login/buildResponse: " + md5.hexdigest())
386                         return challenge + '-' + md5.hexdigest()
387
388                 debug("[FritzCallFBF] _md5Login")
389                 found = re.match('.*<SID>([^<]*)</SID>', sidXml, re.S)
390                 if found:
391                         self._md5Sid = found.group(1)
392                         debug("[FritzCallFBF] _md5Login: SID "+ self._md5Sid)
393                 else:
394                         debug("[FritzCallFBF] _md5Login: no sid! That must be an old firmware.")
395                         self._oldLogin(callback, 'No error')
396                         return
397
398                 debug("[FritzCallFBF] _md5Login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
399                 self._md5LoginTimestamp = time.time()
400                 if sidXml.find('<iswriteaccess>0</iswriteaccess>') != -1:
401                         debug("[FritzCallFBF] _md5Login: logging in")
402                         found = re.match('.*<Challenge>([^<]*)</Challenge>', sidXml, re.S)
403                         if found:
404                                 challenge = found.group(1)
405                                 debug("[FritzCallFBF] _md5Login: challenge " + challenge)
406                         else:
407                                 challenge = None
408                                 debug("[FritzCallFBF] _md5Login: login necessary and no challenge! That is terribly wrong.")
409                         parms = urlencode({
410                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
411                                                         'login:command/response': buildResponse(challenge, config.plugins.FritzCall.password.value),
412                                                         })
413                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
414                         debug("[FritzCallFBF] _md5Login: '" + url + "' parms: '" + parms + "'")
415                         getPage(url,
416                                 method="POST",
417                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
418                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
419                                                 }, postdata=parms).addCallback(self._gotPageLogin).addCallback(callback).addErrback(self._errorLogin)
420                 elif callback: # we assume value 1 here, no login necessary
421                         debug("[FritzCallFBF] _md5Login: no login necessary")
422                         callback(None)
423
424         def _gotPageLogin(self, html):
425                 if self._callScreen:
426                         self._callScreen.updateStatus(_("login verification"))
427                 debug("[FritzCallFBF] _gotPageLogin: verify login")
428                 start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
429                 if start != -1:
430                         start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
431                         text = _("FRITZ!Box - Error logging in\n\n") + html[start : html.find('</p>', start)]
432                         self._notify(text)
433                 else:
434                         if self._callScreen:
435                                 self._callScreen.updateStatus(_("login ok"))
436
437                 found = re.match('.*<input type="hidden" name="sid" value="([^\"]*)"', html, re.S)
438                 if found:
439                         self._md5Sid = found.group(1)
440                         debug("[FritzCallFBF] _gotPageLogin: found sid: " + self._md5Sid)
441
442         def _errorLogin(self, error):
443                 global fritzbox
444                 debug("[FritzCallFBF] _errorLogin: %s" % (error))
445                 text = _("FRITZ!Box - Error logging in: %s\nDisabling plugin.") % error.getErrorMessage()
446                 # config.plugins.FritzCall.enable.value = False
447                 fritzbox = None
448                 self._notify(text)
449
450         def _logout(self):
451                 if self._md5LoginTimestamp:
452                         self._md5LoginTimestamp = None
453                         parms = urlencode({
454                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
455                                                         'login:command/logout':'bye bye Fritz'
456                                                         })
457                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
458                         debug("[FritzCallFBF] logout: '" + url + "' parms: '" + parms + "'")
459                         getPage(url,
460                                 method="POST",
461                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
462                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
463                                                 }, postdata=parms).addErrback(self._errorLogout)
464
465         def _errorLogout(self, error):
466                 debug("[FritzCallFBF] _errorLogout: %s" % (error))
467                 text = _("FRITZ!Box - Error logging out: %s") % error.getErrorMessage()
468                 self._notify(text)
469
470         def loadFritzBoxPhonebook(self):
471                 debug("[FritzCallFBF] loadFritzBoxPhonebook")
472                 if config.plugins.FritzCall.fritzphonebook.value:
473                         self._phoneBookID = '0'
474                         debug("[FritzCallFBF] loadFritzBoxPhonebook: logging in")
475                         self._login(self._loadFritzBoxPhonebook)
476
477         def _loadFritzBoxPhonebook(self, html):
478                 if html:
479                         #===================================================================
480                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
481                         # if found:
482                         #       self._errorLoad('Login: ' + found.group(1))
483                         #       return
484                         #===================================================================
485                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
486                         if start != -1:
487                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
488                                 self._errorLoad('Login: ' + html[start, html.find('</p>', start)])
489                                 return
490                 parms = urlencode({
491                                                 'getpage':'../html/de/menus/menu2.html',
492                                                 'var:lang':'de',
493                                                 'var:pagename':'fonbuch',
494                                                 'var:menu':'fon',
495                                                 'sid':self._md5Sid,
496                                                 'telcfg:settings/Phonebook/Books/Select':self._phoneBookID, # this selects always the first phonbook
497                                                 })
498                 url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
499                 debug("[FritzCallFBF] _loadFritzBoxPhonebook: '" + url + "' parms: '" + parms + "'")
500                 getPage(url,
501                         method="POST",
502                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
503                         headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
504                                         }, postdata=parms).addCallback(self._parseFritzBoxPhonebook).addErrback(self._errorLoad)
505
506         def _parseFritzBoxPhonebook(self, html):
507                 debug("[FritzCallFBF] _parseFritzBoxPhonebook")
508
509                 # first, let us get the charset
510                 found = re.match('.*<meta http-equiv=content-type content="text/html; charset=([^"]*)">', html, re.S)
511                 if found:
512                         charset = found.group(1)
513                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: found charset: " + charset)
514                         html = html2unicode(html.decode(charset), charset).encode('utf-8') # this looks silly, but has to be
515                 else: # this is kind of emergency conversion...
516                         try:
517                                 debug("[FritzCallFBF] _parseFritzBoxPhonebook: try charset utf-8")
518                                 charset = 'utf-8'
519                                 html = html2unicode(html.decode('utf-8'), 'utf-8').encode('utf-8') # this looks silly, but has to be
520                         except UnicodeDecodeError:
521                                 debug("[FritzCallFBF] _parseFritzBoxPhonebook: try charset iso-8859-1")
522                                 charset = 'iso-8859-1'
523                                 html = html2unicode(html.decode('iso-8859-1'), 'iso-8859-1').encode('utf-8') # this looks silly, but has to be
524
525                 # if re.search('document.write\(TrFon1\(\)', html):
526                 if html.find('document.write(TrFon1()') != -1:
527                         #===============================================================================
528                         #                                New Style: 7270 (FW 54.04.58, 54.04.63-11941, 54.04.70, 54.04.74-14371, 54.04.76, PHONE Labor 54.04.80-16624)
529                         #                                                       7170 (FW 29.04.70) 22.03.2009
530                         #                                                       7141 (FW 40.04.68) 22.03.2009
531                         #       We expect one line with TrFonName followed by several lines with
532                         #       TrFonNr(Type,Number,Shortcut,Vanity), which all belong to the name in TrFonName.
533                         #===============================================================================
534                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: discovered newer firmware")
535                         found = re.match('.*<input type="hidden" name="telcfg:settings/Phonebook/Books/Name(\d+)" value="[Dd]reambox" id="uiPostPhonebookName\d+" disabled>', html, re.S)
536                         if found:
537                                 phoneBookID = found.group(1)
538                                 debug("[FritzCallFBF] _parseFritzBoxPhonebook: found dreambox phonebook with id: " + phoneBookID)
539                                 if self._phoneBookID != phoneBookID:
540                                         self._phoneBookID = phoneBookID
541                                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: reload phonebook")
542                                         self._loadFritzBoxPhonebook(self._phoneBookID) # reload with dreambox phonebook
543                                         return
544
545                         entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]*"(?:, "[^"]*")?\);.*?)document.write\(TrFon1\(\)', re.S)
546                         entries = entrymask.finditer(html)
547                         for entry in entries:
548                                 # TrFonName (id, name, category)
549                                 # TODO: replace re.match?
550                                 found = re.match('TrFonName\("[^"]*", "([^"]+)", "[^"]*"(?:, "[^"]*")?\);', entry.group(1))
551                                 if found:
552                                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: name: %s" %found.group(1))
553                                         name = found.group(1).replace(',','').strip()
554                                 else:
555                                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: could not find name")
556                                         continue
557                                 # TrFonNr (type, rufnr, code, vanity)
558                                 detailmask = re.compile('TrFonNr\("([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\);', re.S)
559                                 details = detailmask.finditer(entry.group(1))
560                                 for found in details:
561                                         thisnumber = found.group(2).strip()
562                                         if not thisnumber:
563                                                 debug("[FritzCallFBF] Ignoring entry with empty number for '''%s'''" % (name))
564                                                 continue
565                                         else:
566                                                 thisname = name
567                                                 callType = found.group(1)
568                                                 if config.plugins.FritzCall.showType.value:
569                                                         if callType == "mobile":
570                                                                 thisname = thisname + " (" + _("mobile") + ")"
571                                                         elif callType == "home":
572                                                                 thisname = thisname + " (" + _("home") + ")"
573                                                         elif callType == "work":
574                                                                 thisname = thisname + " (" + _("work") + ")"
575
576                                                 if config.plugins.FritzCall.showShortcut.value and found.group(3):
577                                                         thisname = thisname + ", " + _("Shortcut") + ": " + found.group(3)
578                                                 if config.plugins.FritzCall.showVanity.value and found.group(4):
579                                                         thisname = thisname + ", " + _("Vanity") + ": " + found.group(4)
580
581                                                 debug("[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (thisname.strip(), thisnumber))
582                                                 # Beware: strings in phonebook.phonebook have to be in utf-8!
583                                                 phonebook.phonebook[thisnumber] = thisname
584
585                 # elif re.search('document.write\(TrFon\(', html):
586                 elif html.find('document.write(TrFon(') != -1:
587                         #===============================================================================
588                         #                               Old Style: 7050 (FW 14.04.33)
589                         #       We expect one line with TrFon(No,Name,Number,Shortcut,Vanity)
590                         #   Encoding should be plain Ascii...
591                         #===============================================================================                                
592                         entrymask = re.compile('TrFon\("[^"]*", "([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\)', re.S)
593                         entries = entrymask.finditer(html)
594                         for found in entries:
595                                 name = found.group(1).strip().replace(',','')
596                                 # debug("[FritzCallFBF] pos: %s name: %s" %(found.group(0),name))
597                                 thisnumber = found.group(2).strip()
598                                 if config.plugins.FritzCall.showShortcut.value and found.group(3):
599                                         name = name + ", " + _("Shortcut") + ": " + found.group(3)
600                                 if config.plugins.FritzCall.showVanity.value and found.group(4):
601                                         name = name + ", " + _("Vanity") + ": " + found.group(4)
602                                 if thisnumber:
603                                         # name = name.encode('utf-8')
604                                         debug("[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (name, thisnumber))
605                                         # Beware: strings in phonebook.phonebook have to be in utf-8!
606                                         phonebook.phonebook[thisnumber] = name
607                                 else:
608                                         debug("[FritzCallFBF] ignoring empty number for %s" % name)
609                                 continue
610                 elif self._md5Sid == '0000000000000000': # retry, it could be a race condition
611                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: retry loading phonebook")
612                         self.loadFritzBoxPhonebook()
613                 else:
614                         self._notify(_("Could not parse FRITZ!Box Phonebook entry"))
615
616         def _errorLoad(self, error):
617                 debug("[FritzCallFBF] _errorLoad: %s" % (error))
618                 text = _("FRITZ!Box - Could not load phonebook: %s") % error.getErrorMessage()
619                 self._notify(text)
620
621         def getCalls(self, callScreen, callback, callType):
622                 #
623                 # call sequence must be:
624                 # - login
625                 # - getPage -> _gotPageLogin
626                 # - loginCallback (_getCalls)
627                 # - getPage -> _getCalls1
628                 debug("[FritzCallFBF] getCalls")
629                 self._callScreen = callScreen
630                 self._callType = callType
631                 if (time.time() - self._callTimestamp) > 180: 
632                         debug("[FritzCallFBF] getCalls: outdated data, login and get new ones: " + time.ctime(self._callTimestamp) + " time: " + time.ctime())
633                         self._callTimestamp = time.time()
634                         self._login(lambda x:self._getCalls(callback, x))
635                 elif not self._callList:
636                         debug("[FritzCallFBF] getCalls: time is ok, but no callList")
637                         self._getCalls1(callback)
638                 else:
639                         debug("[FritzCallFBF] getCalls: time is ok, callList is ok")
640                         self._gotPageCalls(callback)
641
642         def _getCalls(self, callback, html):
643                 if html:
644                         #===================================================================
645                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
646                         # if found:
647                         #       self._errorCalls('Login: ' + found.group(1))
648                         #       return
649                         #===================================================================
650                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
651                         if start != -1:
652                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
653                                 self._errorCalls('Login: ' + html[start, html.find('</p>', start)])
654                                 return
655                 #
656                 # we need this to fill Anrufliste.csv
657                 # http://repeater1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=foncalls
658                 #
659                 debug("[FritzCallFBF] _getCalls")
660                 if html:
661                         #===================================================================
662                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
663                         # if found:
664                         #       text = _("FRITZ!Box - Error logging in: %s") + found.group(1)
665                         #       self._notify(text)
666                         #       return
667                         #===================================================================
668                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
669                         if start != -1:
670                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
671                                 self._notify(_("FRITZ!Box - Error logging in: %s") + html[start, html.find('</p>', start)])
672                                 return
673
674                 if self._callScreen:
675                         self._callScreen.updateStatus(_("preparing"))
676                 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de', 'var:pagename':'foncalls', 'var:menu':'fon', 'sid':self._md5Sid})
677                 url = "http://%s/cgi-bin/webcm?%s" % (config.plugins.FritzCall.hostname.value, parms)
678                 getPage(url).addCallback(lambda x:self._getCalls1(callback)).addErrback(self._errorCalls) #@UnusedVariable # pylint: disable=W0613
679
680         def _getCalls1(self, callback):
681                 #
682                 # finally we should have successfully lgged in and filled the csv
683                 #
684                 debug("[FritzCallFBF] _getCalls1")
685                 if self._callScreen:
686                         self._callScreen.updateStatus(_("finishing"))
687                 parms = urlencode({'getpage':'../html/de/FRITZ!Box_Anrufliste.csv', 'sid':self._md5Sid})
688                 url = "http://%s/cgi-bin/webcm?%s" % (config.plugins.FritzCall.hostname.value, parms)
689                 getPage(url).addCallback(lambda x:self._gotPageCalls(callback, x)).addErrback(self._errorCalls)
690
691         def _gotPageCalls(self, callback, csv=""):
692                 def resolveNumber(number, default=None):
693                         if number.isdigit():
694                                 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0] == "0":
695                                         number = number[1:]
696                                 # strip CbC prefix
697                                 number = stripCbCPrefix(number, config.plugins.FritzCall.country.value)
698                                 if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
699                                         number = config.plugins.FritzCall.prefix.value + number
700                                 name = phonebook.search(number)
701                                 if name:
702                                         #===========================================================
703                                         # found = re.match('(.*?)\n.*', name)
704                                         # if found:
705                                         #       name = found.group(1)
706                                         #===========================================================
707                                         end = name.find('\n')
708                                         if end != -1:
709                                                 name = name[:end]
710                                         number = name
711                                 elif default:
712                                         number = default
713                                 else:
714                                         name = resolveNumberWithAvon(number, config.plugins.FritzCall.country.value)
715                                         if name:
716                                                 number = number + ' ' + name
717                         elif number == "":
718                                 number = _("UNKNOWN")
719                         # if len(number) > 20: number = number[:20]
720                         return number
721
722                 if csv:
723                         debug("[FritzCallFBF] _gotPageCalls: got csv, setting callList")
724                         if self._callScreen:
725                                 self._callScreen.updateStatus(_("done"))
726                         if csv.find('Melden Sie sich mit dem Kennwort der FRITZ!Box an') != -1:
727                                 text = _("You need to set the password of the FRITZ!Box\nin the configuration dialog to display calls\n\nIt could be a communication issue, just try again.")
728                                 # self.session.open(MessageBox, text, MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
729                                 self._notify(text)
730                                 return
731
732                         csv = csv.decode('iso-8859-1', 'replace').encode('utf-8', 'replace')
733                         lines = csv.splitlines()
734                         self._callList = lines
735                 elif self._callList:
736                         debug("[FritzCallFBF] _gotPageCalls: got no csv, but have callList")
737                         if self._callScreen:
738                                 self._callScreen.updateStatus(_("done, using last list"))
739                         lines = self._callList
740                 else:
741                         debug("[FritzCallFBF] _gotPageCalls: got no csv, no callList, laving")
742                         return
743                         
744                 callListL = []
745                 if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
746                         filtermsns = map(lambda x: x.strip(), config.plugins.FritzCall.filtermsn.value.split(","))
747                         debug("[FritzCallFBF] _gotPageCalls: filtermsns %s" % (repr(filtermsns)))
748
749                 # Typ;Datum;Name;Rufnummer;Nebenstelle;Eigene Rufnummer;Dauer
750                 # 0  ;1    ;2   ;3        ;4          ;5               ;6
751                 lines = map(lambda line: line.split(';'), lines)
752                 lines = filter(lambda line: (len(line)==7 and (line[0]=="Typ" or self._callType == '.' or line[0] == self._callType)), lines)
753
754                 for line in lines:
755                         # debug("[FritzCallFBF] _gotPageCalls: line %s" % (line))
756                         direct = line[0]
757                         date = line[1]
758                         length = line[6]
759                         if config.plugins.FritzCall.phonebook.value and line[2]:
760                                 remote = resolveNumber(line[3], line[2] + " (FBF)")
761                         else:
762                                 remote = resolveNumber(line[3], line[2])
763                         here = line[5]
764                         start = here.find('Internet: ')
765                         if start != -1:
766                                 start += len('Internet: ')
767                                 here = here[start:]
768                         else:
769                                 here = line[5]
770                         if direct != "Typ" and config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
771                                 # debug("[FritzCallFBF] _gotPageCalls: check %s" % (here))
772                                 if here not in filtermsns:
773                                         # debug("[FritzCallFBF] _gotPageCalls: skip %s" % (here))
774                                         continue
775                         here = resolveNumber(here, line[4])
776
777                         number = stripCbCPrefix(line[3], config.plugins.FritzCall.country.value)
778                         if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
779                                 number = config.plugins.FritzCall.prefix.value + number
780                         callListL.append((number, date, direct, remote, length, here))
781
782                 if callback:
783                         # debug("[FritzCallFBF] _gotPageCalls call callback with\n" + text
784                         callback(callListL)
785                 self._callScreen = None
786
787         def _errorCalls(self, error):
788                 debug("[FritzCallFBF] _errorCalls: %s" % (error))
789                 text = _("FRITZ!Box - Could not load calls: %s") % error.getErrorMessage()
790                 self._notify(text)
791
792         def dial(self, number):
793                 ''' initiate a call to number '''
794                 self._login(lambda x: self._dial(number, x))
795                 
796         def _dial(self, number, html):
797                 if html:
798                         #===================================================================
799                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
800                         # if found:
801                         #       self._errorDial('Login: ' + found.group(1))
802                         #       return
803                         #===================================================================
804                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
805                         if start != -1:
806                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
807                                 self._errorDial('Login: ' + html[start, html.find('</p>', start)])
808                                 return
809                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
810                 parms = urlencode({
811                         'getpage':'../html/de/menus/menu2.html',
812                         'var:pagename':'fonbuch',
813                         'var:menu':'home',
814                         'telcfg:settings/UseClickToDial':'1',
815                         'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
816                         'telcfg:command/Dial':number,
817                         'sid':self._md5Sid
818                         })
819                 debug("[FritzCallFBF] dial url: '" + url + "' parms: '" + parms + "'")
820                 getPage(url,
821                         method="POST",
822                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
823                         headers={
824                                         'Content-Type': "application/x-www-form-urlencoded",
825                                         'Content-Length': str(len(parms))},
826                         postdata=parms).addCallback(self._okDial).addErrback(self._errorDial)
827
828         def _okDial(self, html): #@UnusedVariable # pylint: disable=W0613
829                 debug("[FritzCallFBF] okDial")
830
831         def _errorDial(self, error):
832                 debug("[FritzCallFBF] errorDial: $s" % error)
833                 text = _("FRITZ!Box - Dialling failed: %s") % error.getErrorMessage()
834                 self._notify(text)
835
836         def changeWLAN(self, statusWLAN):
837                 ''' get status info from FBF '''
838                 debug("[FritzCallFBF] changeWLAN start")
839                 if not statusWLAN or (statusWLAN != '1' and statusWLAN != '0'):
840                         return
841                 self._login(lambda x: self._changeWLAN(statusWLAN, x))
842                 
843         def _changeWLAN(self, statusWLAN, html):
844                 if html:
845                         #===================================================================
846                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
847                         # if found:
848                         #       self._errorChangeWLAN('Login: ' + found.group(1))
849                         #       return
850                         #===================================================================
851                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
852                         if start != -1:
853                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
854                                 self._errorChangeWLAN('Login: ' + html[start, html.find('</p>', start)])
855                                 return
856                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
857                 parms = urlencode({
858                         'getpage':'../html/de/menus/menu2.html',
859                         'var:lang':'de',
860                         'var:pagename':'wlan',
861                         'var:menu':'wlan',
862                         'wlan:settings/ap_enabled':str(statusWLAN),
863                         'sid':self._md5Sid
864                         })
865                 debug("[FritzCallFBF] changeWLAN url: '" + url + "' parms: '" + parms + "'")
866                 getPage(url,
867                         method="POST",
868                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
869                         headers={
870                                         'Content-Type': "application/x-www-form-urlencoded",
871                                         'Content-Length': str(len(parms))},
872                         postdata=parms).addCallback(self._okChangeWLAN).addErrback(self._errorChangeWLAN)
873
874         def _okChangeWLAN(self, html): #@UnusedVariable # pylint: disable=W0613
875                 debug("[FritzCallFBF] _okChangeWLAN")
876
877         def _errorChangeWLAN(self, error):
878                 debug("[FritzCallFBF] _errorChangeWLAN: $s" % error)
879                 text = _("FRITZ!Box - Failed changing WLAN: %s") % error.getErrorMessage()
880                 self._notify(text)
881
882         def changeMailbox(self, whichMailbox):
883                 ''' switch mailbox on/off '''
884                 debug("[FritzCallFBF] changeMailbox start: " + str(whichMailbox))
885                 self._login(lambda x: self._changeMailbox(whichMailbox, x))
886
887         def _changeMailbox(self, whichMailbox, html):
888                 if html:
889                         #===================================================================
890                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
891                         # if found:
892                         #       self._errorChangeMailbox('Login: ' + found.group(1))
893                         #       return
894                         #===================================================================
895                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
896                         if start != -1:
897                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
898                                 self._errorChangeMailbox('Login: ' + html[start, html.find('</p>', start)])
899                                 return
900                 debug("[FritzCallFBF] _changeMailbox")
901                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
902                 if whichMailbox == -1:
903                         for i in range(5):
904                                 if self.info[FBF_tamActive][i+1]:
905                                         state = '0'
906                                 else:
907                                         state = '1'
908                                 parms = urlencode({
909                                         'tam:settings/TAM'+str(i)+'/Active':state,
910                                         'sid':self._md5Sid
911                                         })
912                                 debug("[FritzCallFBF] changeMailbox url: '" + url + "' parms: '" + parms + "'")
913                                 getPage(url,
914                                         method="POST",
915                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
916                                         headers={
917                                                         'Content-Type': "application/x-www-form-urlencoded",
918                                                         'Content-Length': str(len(parms))},
919                                         postdata=parms).addCallback(self._okChangeMailbox).addErrback(self._errorChangeMailbox)
920                 elif whichMailbox > 4:
921                         debug("[FritzCallFBF] changeMailbox invalid mailbox number")
922                 else:
923                         if self.info[FBF_tamActive][whichMailbox+1]:
924                                 state = '0'
925                         else:
926                                 state = '1'
927                         parms = urlencode({
928                                 'tam:settings/TAM'+str(whichMailbox)+'/Active':state,
929                                 'sid':self._md5Sid
930                                 })
931                         debug("[FritzCallFBF] changeMailbox url: '" + url + "' parms: '" + parms + "'")
932                         getPage(url,
933                                 method="POST",
934                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
935                                 headers={
936                                                 'Content-Type': "application/x-www-form-urlencoded",
937                                                 'Content-Length': str(len(parms))},
938                                 postdata=parms).addCallback(self._okChangeMailbox).addErrback(self._errorChangeMailbox)
939
940         def _okChangeMailbox(self, html): #@UnusedVariable # pylint: disable=W0613
941                 debug("[FritzCallFBF] _okChangeMailbox")
942
943         def _errorChangeMailbox(self, error):
944                 debug("[FritzCallFBF] _errorChangeMailbox: $s" % error)
945                 text = _("FRITZ!Box - Failed changing Mailbox: %s") % error.getErrorMessage()
946                 self._notify(text)
947
948         def getInfo(self, callback):
949                 ''' get status info from FBF '''
950                 debug("[FritzCallFBF] getInfo")
951                 self._login(lambda x:self._getInfo(callback, x))
952                 
953         def _getInfo(self, callback, html):
954                 # http://192.168.178.1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:pagename=home&var:menu=home
955                 debug("[FritzCallFBF] _getInfo: verify login")
956                 if html:
957                         #===================================================================
958                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
959                         # if found:
960                         #       self._errorGetInfo('Login: ' + found.group(1))
961                         #       return
962                         #===================================================================
963                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
964                         if start != -1:
965                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
966                                 self._errorGetInfo('Login: ' + html[start, html.find('</p>', start)])
967                                 return
968
969                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
970                 parms = urlencode({
971                         'getpage':'../html/de/menus/menu2.html',
972                         'var:lang':'de',
973                         'var:pagename':'home',
974                         'var:menu':'home',
975                         'sid':self._md5Sid
976                         })
977                 debug("[FritzCallFBF] _getInfo url: '" + url + "' parms: '" + parms + "'")
978                 getPage(url,
979                         method="POST",
980                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
981                         headers={
982                                         'Content-Type': "application/x-www-form-urlencoded",
983                                         'Content-Length': str(len(parms))},
984                         postdata=parms).addCallback(lambda x:self._okGetInfo(callback,x)).addErrback(self._errorGetInfo)
985
986         def _okGetInfo(self, callback, html):
987                 def readInfo(html):
988                         if self.info:
989                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
990                         else:
991                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = (None, None, None, None, None, None, None, None, None)
992
993                         debug("[FritzCallFBF] _okGetInfo/readinfo")
994                         found = re.match('.*<table class="tborder" id="tProdukt">\s*<tr>\s*<td style="padding-top:2px;">([^<]*)</td>\s*<td style="padding-top:2px;text-align:right;">\s*([^\s]*)\s*</td>', html, re.S)
995                         if found:
996                                 boxInfo = found.group(1)+ ', ' + found.group(2)
997                                 boxInfo = boxInfo.replace('&nbsp;',' ')
998                                 # debug("[FritzCallFBF] _okGetInfo Boxinfo: " + boxInfo)
999                         else:
1000                                 found = re.match('.*<p class="ac">([^<]*)</p>', html, re.S)
1001                                 if found:
1002                                         # debug("[FritzCallFBF] _okGetInfo Boxinfo: " + found.group(1))
1003                                         boxInfo = found.group(1)
1004
1005                         if html.find('home_coninf.txt') != -1:
1006                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1007                                 parms = urlencode({
1008                                         'getpage':'../html/de/home/home_coninf.txt',
1009                                         'sid':self._md5Sid
1010                                         })
1011                                 # debug("[FritzCallFBF] get coninfo: url: '" + url + "' parms: '" + parms + "'")
1012                                 getPage(url,
1013                                         method="POST",
1014                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1015                                         headers={
1016                                                         'Content-Type': "application/x-www-form-urlencoded",
1017                                                         'Content-Length': str(len(parms))},
1018                                         postdata=parms).addCallback(lambda x:self._okSetConInfo(callback,x)).addErrback(self._errorGetInfo)
1019                         else:
1020                                 found = re.match('.*if \(isNaN\(jetzt\)\)\s*return "";\s*var str = "([^"]*)";', html, re.S)
1021                                 if found:
1022                                         # debug("[FritzCallFBF] _okGetInfo Uptime: " + found.group(1))
1023                                         upTime = found.group(1)
1024                                 else:
1025                                         found = re.match('.*str = g_pppSeit \+"([^<]*)<br>"\+mldIpAdr;', html, re.S)
1026                                         if found:
1027                                                 # debug("[FritzCallFBF] _okGetInfo Uptime: " + found.group(1))
1028                                                 upTime = found.group(1)
1029         
1030                                 found = re.match(".*IpAdrDisplay\('([.\d]+)'\)", html, re.S)
1031                                 if found:
1032                                         # debug("[FritzCallFBF] _okGetInfo IpAdrDisplay: " + found.group(1))
1033                                         ipAddress = found.group(1)
1034
1035                         if html.find('g_tamActive') != -1:
1036                                 entries = re.compile('if \("(\d)" == "1"\) {\s*g_tamActive \+= 1;\s*}', re.S).finditer(html)
1037                                 tamActive = [0, False, False, False, False, False]
1038                                 i = 1
1039                                 for entry in entries:
1040                                         state = entry.group(1)
1041                                         if state == '1':
1042                                                 tamActive[0] += 1
1043                                                 tamActive[i] = True
1044                                         i += 1
1045                                 # debug("[FritzCallFBF] _okGetInfo tamActive: " + str(tamActive))
1046                 
1047                         if html.find('home_dect.txt') != -1:
1048                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1049                                 parms = urlencode({
1050                                         'getpage':'../html/de/home/home_dect.txt',
1051                                         'sid':self._md5Sid
1052                                         })
1053                                 # debug("[FritzCallFBF] get coninfo: url: '" + url + "' parms: '" + parms + "'")
1054                                 getPage(url,
1055                                         method="POST",
1056                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1057                                         headers={
1058                                                         'Content-Type': "application/x-www-form-urlencoded",
1059                                                         'Content-Length': str(len(parms))},
1060                                         postdata=parms).addCallback(lambda x:self._okSetDect(callback,x)).addErrback(self._errorGetInfo)
1061                         else:
1062                                 if html.find('countDect2') != -1:
1063                                         entries = re.compile('if \("1" == "1"\) countDect2\+\+;', re.S).findall(html)
1064                                         dectActive = len(entries)
1065                                         # debug("[FritzCallFBF] _okGetInfo dectActive: " + str(dectActive))
1066
1067                         found = re.match('.*var g_intFaxActive = "0";\s*if \("1" != ""\) {\s*g_intFaxActive = "1";\s*}\s*', html, re.S)
1068                         if found:
1069                                 faxActive = True
1070                                 # debug("[FritzCallFBF] _okGetInfo faxActive")
1071
1072                         if html.find('cntRufumleitung') != -1:
1073                                 entries = re.compile('mode = "1";\s*ziel = "[^"]+";\s*if \(mode == "1" \|\| ziel != ""\)\s*{\s*g_RufumleitungAktiv = true;', re.S).findall(html)
1074                                 rufumlActive = len(entries)
1075                                 entries = re.compile('if \("([^"]*)"=="([^"]*)"\) isAllIncoming\+\+;', re.S).finditer(html)
1076                                 isAllIncoming = 0
1077                                 for entry in entries:
1078                                         # debug("[FritzCallFBF] _okGetInfo rufumlActive add isAllIncoming")
1079                                         if entry.group(1) == entry.group(2):
1080                                                 isAllIncoming += 1
1081                                 if isAllIncoming == 2 and rufumlActive > 0:
1082                                         rufumlActive -= 1
1083                                 # debug("[FritzCallFBF] _okGetInfo rufumlActive: " + str(rufumlActive))
1084
1085                         # /cgi-bin/webcm?getpage=../html/de/home/home_dsl.txt
1086                         # { "dsl_carrier_state": "5", "umts_enabled": "0", "ata_mode": "0", "isusbgsm": "", "dsl_ds_nrate": "3130", "dsl_us_nrate": "448", "hint_dsl_no_cable": "0", "wds_enabled": "0", "wds_hop": "0", "isata": "" } 
1087                         if html.find('home_dsl.txt') != -1:
1088                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1089                                 parms = urlencode({
1090                                         'getpage':'../html/de/home/home_dsl.txt',
1091                                         'sid':self._md5Sid
1092                                         })
1093                                 # debug("[FritzCallFBF] get dsl state: url: '" + url + "' parms: '" + parms + "'")
1094                                 getPage(url,
1095                                         method="POST",
1096                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1097                                         headers={
1098                                                         'Content-Type': "application/x-www-form-urlencoded",
1099                                                         'Content-Length': str(len(parms))},
1100                                         postdata=parms).addCallback(lambda x:self._okSetDslState(callback,x)).addErrback(self._errorGetInfo)
1101                         else:
1102                                 found = re.match('.*function DslStateDisplay \(state\){\s*var state = "(\d+)";', html, re.S)
1103                                 if found:
1104                                         # debug("[FritzCallFBF] _okGetInfo DslState: " + found.group(1))
1105                                         dslState = [ found.group(1), None ] # state, speed
1106                                         found = re.match('.*function DslStateDisplay \(state\){\s*var state = "\d+";.*?if \("3130" != "0"\) str = "([^"]*)";', html, re.S)
1107                                         if found:
1108                                                 # debug("[FritzCallFBF] _okGetInfo DslSpeed: " + found.group(1).strip())
1109                                                 dslState[1] = found.group(1).strip()
1110                 
1111                         # /cgi-bin/webcm?getpage=../html/de/home/home_wlan.txt
1112                         # { "ap_enabled": "1", "active_stations": "0", "encryption": "4", "wireless_stickandsurf_enabled": "0", "is_macfilter_active": "0", "wmm_enabled": "1", "wlan_state": [ "end" ] }
1113                         if html.find('home_wlan.txt') != -1:
1114                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1115                                 parms = urlencode({
1116                                         'getpage':'../html/de/home/home_wlan.txt',
1117                                         'sid':self._md5Sid
1118                                         })
1119                                 # debug("[FritzCallFBF] get wlan state: url: '" + url + "' parms: '" + parms + "'")
1120                                 getPage(url,
1121                                         method="POST",
1122                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1123                                         headers={
1124                                                         'Content-Type': "application/x-www-form-urlencoded",
1125                                                         'Content-Length': str(len(parms))},
1126                                         postdata=parms).addCallback(lambda x:self._okSetWlanState(callback,x)).addErrback(self._errorGetInfo)
1127                         else:
1128                                 found = re.match('.*function WlanStateLed \(state\){.*?return StateLed\("(\d+)"\);\s*}', html, re.S)
1129                                 if found:
1130                                         # debug("[FritzCallFBF] _okGetInfo WlanState: " + found.group(1))
1131                                         wlanState = [ found.group(1), 0, 0 ] # state, encryption, number of devices
1132                                         found = re.match('.*var (?:g_)?encryption = "(\d+)";', html, re.S)
1133                                         if found:
1134                                                 # debug("[FritzCallFBF] _okGetInfo WlanEncrypt: " + found.group(1))
1135                                                 wlanState[1] = found.group(1)
1136
1137                         return (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1138
1139                 debug("[FritzCallFBF] _okGetInfo")
1140                 info = readInfo(html)
1141                 debug("[FritzCallFBF] _okGetInfo info: " + str(info))
1142                 self.info = info
1143                 if callback:
1144                         callback(info)
1145
1146         def _okSetDect(self, callback, html):
1147                 # debug("[FritzCallFBF] _okSetDect: " + html)
1148                 # found = re.match('.*"connection_status":"(\d+)".*"connection_ip":"([.\d]+)".*"connection_detail":"([^"]+)".*"connection_uptime":"([^"]+)"', html, re.S)
1149                 if html.find('"dect_enabled": "1"') != -1:
1150                         # debug("[FritzCallFBF] _okSetDect: dect_enabled")
1151                         found = re.match('.*"dect_device_list":.*\[([^\]]*)\]', html, re.S)
1152                         if found:
1153                                 # debug("[FritzCallFBF] _okSetDect: dect_device_list: %s" %(found.group(1)))
1154                                 entries = re.compile('"1"', re.S).findall(found.group(1))
1155                                 dectActive = len(entries)
1156                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dummy, faxActive, rufumlActive) = self.info
1157                                 self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1158                                 debug("[FritzCallFBF] _okSetDect info: " + str(self.info))
1159                 if callback:
1160                         callback(self.info)
1161
1162         def _okSetConInfo(self, callback, html):
1163                 # debug("[FritzCallFBF] _okSetConInfo: " + html)
1164                 # found = re.match('.*"connection_status":"(\d+)".*"connection_ip":"([.\d]+)".*"connection_detail":"([^"]+)".*"connection_uptime":"([^"]+)"', html, re.S)
1165                 found = re.match('.*"connection_ip": "([.\d]+)".*"connection_uptime": "([^"]+)"', html, re.S)
1166                 if found:
1167                         # debug("[FritzCallFBF] _okSetConInfo: connection_ip: %s upTime: %s" %( found.group(1), found.group(2)))
1168                         ipAddress = found.group(1)
1169                         upTime = found.group(2)
1170                         (boxInfo, dummy, dummy, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
1171                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1172                         debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
1173                 else:
1174                         found = re.match('.*_ip": "([.\d]+)".*"connection_uptime": "([^"]+)"', html, re.S)
1175                         if found:
1176                                 # debug("[FritzCallFBF] _okSetConInfo: _ip: %s upTime: %s" %( found.group(1), found.group(2)))
1177                                 ipAddress = found.group(1)
1178                                 upTime = found.group(2)
1179                                 (boxInfo, dummy, dummy, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
1180                                 self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1181                                 debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
1182                 if callback:
1183                         callback(self.info)
1184
1185         def _okSetWlanState(self, callback, html):
1186                 # debug("[FritzCallFBF] _okSetWlanState: " + html)
1187                 found = re.match('.*"ap_enabled": "(\d+)"', html, re.S)
1188                 if found:
1189                         # debug("[FritzCallFBF] _okSetWlanState: ap_enabled: " + found.group(1))
1190                         wlanState = [ found.group(1), None, None ]
1191                         found = re.match('.*"encryption": "(\d+)"', html, re.S)
1192                         if found:
1193                                 # debug("[FritzCallFBF] _okSetWlanState: encryption: " + found.group(1))
1194                                 wlanState[1] = found.group(1)
1195                         found = re.match('.*"active_stations": "(\d+)"', html, re.S)
1196                         if found:
1197                                 # debug("[FritzCallFBF] _okSetWlanState: active_stations: " + found.group(1))
1198                                 wlanState[2] = found.group(1)
1199                         (boxInfo, upTime, ipAddress, dummy, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
1200                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1201                         debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
1202                 if callback:
1203                         callback(self.info)
1204
1205         def _okSetDslState(self, callback, html):
1206                 # debug("[FritzCallFBF] _okSetDslState: " + html)
1207                 found = re.match('.*"dsl_carrier_state": "(\d+)"', html, re.S)
1208                 if found:
1209                         # debug("[FritzCallFBF] _okSetDslState: dsl_carrier_state: " + found.group(1))
1210                         dslState = [ found.group(1), None ]
1211                         found = re.match('.*"dsl_ds_nrate": "(\d+)"', html, re.S)
1212                         if found:
1213                                 # debug("[FritzCallFBF] _okSetDslState: dsl_ds_nrate: " + found.group(1))
1214                                 dslState[1] = found.group(1)
1215                         found = re.match('.*"dsl_us_nrate": "(\d+)"', html, re.S)
1216                         if found:
1217                                 # debug("[FritzCallFBF] _okSetDslState: dsl_us_nrate: " + found.group(1))
1218                                 dslState[1] = dslState[1] + '/' + found.group(1)
1219                         (boxInfo, upTime, ipAddress, wlanState, dummy, tamActive, dectActive, faxActive, rufumlActive) = self.info
1220                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1221                         debug("[FritzCallFBF] _okSetDslState info: " + str(self.info))
1222                 if callback:
1223                         callback(self.info)
1224
1225         def _errorGetInfo(self, error):
1226                 debug("[FritzCallFBF] _errorGetInfo: %s" % (error))
1227                 text = _("FRITZ!Box - Error getting status: %s") % error.getErrorMessage()
1228                 self._notify(text)
1229                 # linkP = open("/tmp/FritzCall_errorGetInfo.htm", "w")
1230                 # linkP.write(error)
1231                 # linkP.close()
1232
1233         def reset(self):
1234                 self._login(self._reset)
1235
1236         def _reset(self, html):
1237                 # POSTDATA=getpage=../html/reboot.html&errorpage=../html/de/menus/menu2.html&var:lang=de&var:pagename=home&var:errorpagename=home&var:menu=home&var:pagemaster=&time:settings/time=1242207340%2C-120&var:tabReset=0&logic:command/reboot=../gateway/commands/saveconfig.html
1238                 if html:
1239                         #===================================================================
1240                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1241                         # if found:
1242                         #       self._errorReset('Login: ' + found.group(1))
1243                         #       return
1244                         #===================================================================
1245                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1246                         if start != -1:
1247                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1248                                 self._errorReset('Login: ' + html[start, html.find('</p>', start)])
1249                                 return
1250                 if self._callScreen:
1251                         self._callScreen.close()
1252                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1253                 parms = urlencode({
1254                         'getpage':'../html/reboot.html',
1255                         'var:lang':'de',
1256                         'var:pagename':'reset',
1257                         'var:menu':'system',
1258                         'logic:command/reboot':'../gateway/commands/saveconfig.html',
1259                         'sid':self._md5Sid
1260                         })
1261                 debug("[FritzCallFBF] _reset url: '" + url + "' parms: '" + parms + "'")
1262                 getPage(url,
1263                         method="POST",
1264                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1265                         headers={
1266                                         'Content-Type': "application/x-www-form-urlencoded",
1267                                         'Content-Length': str(len(parms))},
1268                         postdata=parms)
1269
1270         def _okReset(self, html): #@UnusedVariable # pylint: disable=W0613
1271                 debug("[FritzCallFBF] _okReset")
1272
1273         def _errorReset(self, error):
1274                 debug("[FritzCallFBF] _errorReset: %s" % (error))
1275                 text = _("FRITZ!Box - Error resetting: %s") % error.getErrorMessage()
1276                 self._notify(text)
1277
1278         def readBlacklist(self):
1279                 self._login(self._readBlacklist)
1280                 
1281         def _readBlacklist(self, html):
1282                 if html:
1283                         #===================================================================
1284                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1285                         # if found:
1286                         #       self._errorBlacklist('Login: ' + found.group(1))
1287                         #       return
1288                         #===================================================================
1289                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1290                         if start != -1:
1291                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1292                                 self._errorBlacklist('Login: ' + html[start, html.find('</p>', start)])
1293                                 return
1294                 # http://fritz.box/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=sperre
1295                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1296                 parms = urlencode({
1297                         'getpage':'../html/de/menus/menu2.html',
1298                         'var:lang':'de',
1299                         'var:pagename':'sperre',
1300                         'var:menu':'fon',
1301                         'sid':self._md5Sid
1302                         })
1303                 debug("[FritzCallFBF] _readBlacklist url: '" + url + "' parms: '" + parms + "'")
1304                 getPage(url,
1305                         method="POST",
1306                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1307                         headers={
1308                                         'Content-Type': "application/x-www-form-urlencoded",
1309                                         'Content-Length': str(len(parms))},
1310                         postdata=parms).addCallback(self._okBlacklist).addErrback(self._errorBlacklist)
1311
1312         def _okBlacklist(self, html):
1313                 debug("[FritzCallFBF] _okBlacklist")
1314                 entries = re.compile('<script type="text/javascript">document.write\(Tr(Out|In)\("\d+", "(\d+)", "\w*"\)\);</script>', re.S).finditer(html)
1315                 self.blacklist = ([], [])
1316                 for entry in entries:
1317                         if entry.group(1) == "In":
1318                                 self.blacklist[0].append(entry.group(2))
1319                         else:
1320                                 self.blacklist[1].append(entry.group(2))
1321                 debug("[FritzCallFBF] _okBlacklist: %s" % repr(self.blacklist))
1322
1323         def _errorBlacklist(self, error):
1324                 debug("[FritzCallFBF] _errorBlacklist: %s" % (error))
1325                 text = _("FRITZ!Box - Error getting blacklist: %s") % error.getErrorMessage()
1326                 self._notify(text)
1327
1328 #===============================================================================
1329 #       def hangup(self):
1330 #               ''' hangup call on port; not used for now '''
1331 #               url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1332 #               parms = urlencode({
1333 #                       'id':'uiPostForm',
1334 #                       'name':'uiPostForm',
1335 #                       'login:command/password': config.plugins.FritzCall.password.value,
1336 #                       'telcfg:settings/UseClickToDial':'1',
1337 #                       'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
1338 #                       'telcfg:command/Hangup':'',
1339 #                       'sid':self._md5Sid
1340 #                       })
1341 #               debug("[FritzCallFBF] hangup url: '" + url + "' parms: '" + parms + "'")
1342 #               getPage(url,
1343 #                       method="POST",
1344 #                       headers={
1345 #                                       'Content-Type': "application/x-www-form-urlencoded",
1346 #                                       'Content-Length': str(len(parms))},
1347 #                       postdata=parms)
1348 #===============================================================================
1349
1350 fritzbox = None
1351
1352 class FritzMenu(Screen, HelpableScreen):
1353         def __init__(self, session):
1354                 fontSize = scaleV(24, 21) # indeed this is font size +2
1355                 noButtons = 2 # reset, wlan
1356
1357                 if not fritzbox or not fritzbox.info:
1358                         return
1359
1360                 if fritzbox.info[FBF_tamActive]:
1361                         noButtons += 1 # toggle mailboxes
1362                 width = max(DESKTOP_WIDTH - scaleH(500, 250), noButtons*140+(noButtons+1)*10)
1363                 # boxInfo 2 lines, gap, internet 2 lines, gap, dsl/wlan each 1 line, gap, buttons
1364                 height = 5 + 2*fontSize + 10 + 2*fontSize + 10 + 2*fontSize + 10 + 40 + 5
1365                 if fritzbox.info[FBF_tamActive] is not None:
1366                         height += fontSize
1367                 if fritzbox.info[FBF_dectActive] is not None:
1368                         height += fontSize
1369                 if fritzbox.info[FBF_faxActive] is not None:
1370                         height += fontSize
1371                 if fritzbox.info[FBF_rufumlActive] is not None:
1372                         height += fontSize
1373                 buttonsGap = (width-noButtons*140)/(noButtons+1)
1374                 buttonsVPos = height-40-5
1375
1376                 varLinePos = 4
1377                 if fritzbox.info[FBF_tamActive] is not None:
1378                         mailboxLine = """
1379                                 <widget name="FBFMailbox" position="%d,%d" size="%d,%d" font="Regular;%d" />
1380                                 <widget name="mailbox_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1381                                 <widget name="mailbox_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1382                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
1383                                 <widget name="key_yellow" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1384                                 """ % (
1385                                                 40, 5+2*fontSize+10+varLinePos*fontSize+10, # position mailbox
1386                                                 width-40-20, fontSize, # size mailbox
1387                                                 fontSize-2,
1388                                                 "skin_default/buttons/button_green_off.png",
1389                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button mailbox
1390                                                 "skin_default/buttons/button_green.png",
1391                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button mailbox
1392                                                 noButtons*buttonsGap+(noButtons-1)*140, buttonsVPos,
1393                                                 noButtons*buttonsGap+(noButtons-1)*140, buttonsVPos,
1394                                 )
1395                         varLinePos += 1
1396                 else:
1397                         mailboxLine = ""
1398
1399                 if fritzbox.info[FBF_dectActive] is not None:
1400                         dectLine = """
1401                                 <widget name="FBFDect" position="%d,%d" size="%d,%d" font="Regular;%d" />
1402                                 <widget name="dect_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1403                                 <widget name="dect_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1404                                 """ % (
1405                                                 40, 5+2*fontSize+10+varLinePos*fontSize+10, # position dect
1406                                                 width-40-20, fontSize, # size dect
1407                                                 fontSize-2,
1408                                                 "skin_default/buttons/button_green_off.png",
1409                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1410                                                 "skin_default/buttons/button_green.png",
1411                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1412                                 )
1413                         varLinePos += 1
1414                 else:
1415                         dectLine = ""
1416
1417                 if fritzbox.info[FBF_faxActive] is not None:
1418                         faxLine = """
1419                                 <widget name="FBFFax" position="%d,%d" size="%d,%d" font="Regular;%d" />
1420                                 <widget name="fax_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1421                                 <widget name="fax_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1422                                 """ % (
1423                                                 40, 5+2*fontSize+10+varLinePos*fontSize+10, # position dect
1424                                                 width-40-20, fontSize, # size dect
1425                                                 fontSize-2,
1426                                                 "skin_default/buttons/button_green_off.png",
1427                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1428                                                 "skin_default/buttons/button_green.png",
1429                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1430                                 )
1431                         varLinePos += 1
1432                 else:
1433                         faxLine = ""
1434
1435                 if fritzbox.info[FBF_rufumlActive] is not None:
1436                         rufumlLine = """
1437                                 <widget name="FBFRufuml" position="%d,%d" size="%d,%d" font="Regular;%d" />
1438                                 <widget name="rufuml_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1439                                 <widget name="rufuml_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1440                                 """ % (
1441                                                 40, 5+2*fontSize+10+varLinePos*fontSize+10, # position dect
1442                                                 width-40-20, fontSize, # size dect
1443                                                 fontSize-2,
1444                                                 "skin_default/buttons/button_green_off.png",
1445                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1446                                                 "skin_default/buttons/button_green.png",
1447                                                 20, 5+2*fontSize+10+varLinePos*fontSize+10+(fontSize-16)/2, # position button dect
1448                                 )
1449                         varLinePos += 1
1450                 else:
1451                         rufumlLine = ""
1452         
1453                 self.skin = """
1454                         <screen name="FritzMenu" position="center,center" size="%d,%d" title="FRITZ!Box Fon Status" >
1455                                 <widget name="FBFInfo" position="%d,%d" size="%d,%d" font="Regular;%d" />
1456                                 <widget name="FBFInternet" position="%d,%d" size="%d,%d" font="Regular;%d" />
1457                                 <widget name="internet_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1458                                 <widget name="internet_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1459                                 <widget name="FBFDsl" position="%d,%d" size="%d,%d" font="Regular;%d" />
1460                                 <widget name="dsl_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1461                                 <widget name="dsl_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1462                                 <widget name="FBFWlan" position="%d,%d" size="%d,%d" font="Regular;%d" />
1463                                 <widget name="wlan_inactive" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1464                                 <widget name="wlan_active" pixmap="%s" position="%d,%d" size="15,16" transparent="1" alphatest="on"/>
1465                                 %s
1466                                 %s
1467                                 %s
1468                                 %s
1469                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1470                                 <widget name="key_red" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1471                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1472                                 <widget name="key_green" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1473                         </screen>""" % (
1474                                                 width, height, # size
1475                                                 40, 5, # position info
1476                                                 width-2*40, 2*fontSize, # size info
1477                                                 fontSize-2,
1478                                                 40, 5+2*fontSize+10, # position internet
1479                                                 width-40, 2*fontSize, # size internet
1480                                                 fontSize-2,
1481                                                 "skin_default/buttons/button_green_off.png",
1482                                                 20, 5+2*fontSize+10+(fontSize-16)/2, # position button internet
1483                                                 "skin_default/buttons/button_green.png",
1484                                                 20, 5+2*fontSize+10+(fontSize-16)/2, # position button internet
1485                                                 40, 5+2*fontSize+10+2*fontSize+10, # position dsl
1486                                                 width-40-20, fontSize, # size dsl
1487                                                 fontSize-2,
1488                                                 "skin_default/buttons/button_green_off.png",
1489                                                 20, 5+2*fontSize+10+2*fontSize+10+(fontSize-16)/2, # position button dsl
1490                                                 "skin_default/buttons/button_green.png",
1491                                                 20, 5+2*fontSize+10+2*fontSize+10+(fontSize-16)/2, # position button dsl
1492                                                 40, 5+2*fontSize+10+3*fontSize+10, # position wlan
1493                                                 width-40-20, fontSize, # size wlan
1494                                                 fontSize-2,
1495                                                 "skin_default/buttons/button_green_off.png",
1496                                                 20, 5+2*fontSize+10+3*fontSize+10+(fontSize-16)/2, # position button wlan
1497                                                 "skin_default/buttons/button_green.png",
1498                                                 20, 5+2*fontSize+10+3*fontSize+10+(fontSize-16)/2, # position button wlan
1499                                                 mailboxLine,
1500                                                 dectLine,
1501                                                 faxLine,
1502                                                 rufumlLine,
1503                                                 buttonsGap, buttonsVPos, "skin_default/buttons/red.png", buttonsGap, buttonsVPos,
1504                                                 buttonsGap+140+buttonsGap, buttonsVPos, "skin_default/buttons/green.png", buttonsGap+140+buttonsGap, buttonsVPos,
1505                                                 )
1506
1507                 Screen.__init__(self, session)
1508                 HelpableScreen.__init__(self)
1509                 # TRANSLATORS: keep it short, this is a button
1510                 self["key_red"] = Button(_("Reset"))
1511                 # TRANSLATORS: keep it short, this is a button
1512                 self["key_green"] = Button(_("Toggle WLAN"))
1513                 self._mailboxActive = False
1514                 if fritzbox.info[FBF_tamActive] is not None:
1515                         # TRANSLATORS: keep it short, this is a button
1516                         self["key_yellow"] = Button(_("Toggle Mailbox"))
1517                         self["menuActions"] = ActionMap(["OkCancelActions", "ColorActions", "NumberActions", "EPGSelectActions"],
1518                                                                                         {
1519                                                                                         "cancel": self._exit,
1520                                                                                         "ok": self._exit,
1521                                                                                         "red": self._reset,
1522                                                                                         "green": self._toggleWlan,
1523                                                                                         "yellow": (lambda: self._toggleMailbox(-1)),
1524                                                                                         "0": (lambda: self._toggleMailbox(0)),
1525                                                                                         "1": (lambda: self._toggleMailbox(1)),
1526                                                                                         "2": (lambda: self._toggleMailbox(2)),
1527                                                                                         "3": (lambda: self._toggleMailbox(3)),
1528                                                                                         "4": (lambda: self._toggleMailbox(4)),
1529                                                                                         "info": self._getInfo,
1530                                                                                         }, -2)
1531                         # TRANSLATORS: keep it short, this is a help text
1532                         self.helpList.append((self["menuActions"], "ColorActions", [("yellow", _("Toggle all mailboxes"))]))
1533                         # TRANSLATORS: keep it short, this is a help text
1534                         self.helpList.append((self["menuActions"], "NumberActions", [("0", _("Toggle 1. mailbox"))]))
1535                         # TRANSLATORS: keep it short, this is a help text
1536                         self.helpList.append((self["menuActions"], "NumberActions", [("1", _("Toggle 2. mailbox"))]))
1537                         # TRANSLATORS: keep it short, this is a help text
1538                         self.helpList.append((self["menuActions"], "NumberActions", [("2", _("Toggle 3. mailbox"))]))
1539                         # TRANSLATORS: keep it short, this is a help text
1540                         self.helpList.append((self["menuActions"], "NumberActions", [("3", _("Toggle 4. mailbox"))]))
1541                         # TRANSLATORS: keep it short, this is a help text
1542                         self.helpList.append((self["menuActions"], "NumberActions", [("4", _("Toggle 5. mailbox"))]))
1543                         self["FBFMailbox"] = Label(_('Mailbox'))
1544                         self["mailbox_inactive"] = Pixmap()
1545                         self["mailbox_active"] = Pixmap()
1546                         self["mailbox_active"].hide()
1547                 else:
1548                         self["menuActions"] = ActionMap(["OkCancelActions", "ColorActions", "EPGSelectActions"],
1549                                                                                         {
1550                                                                                         "cancel": self._exit,
1551                                                                                         "ok": self._exit,
1552                                                                                         "green": self._toggleWlan,
1553                                                                                         "red": self._reset,
1554                                                                                         "info": self._getInfo,
1555                                                                                         }, -2)
1556
1557                 # TRANSLATORS: keep it short, this is a help text
1558                 self.helpList.append((self["menuActions"], "OkCancelActions", [("cancel", _("Quit"))]))
1559                 # TRANSLATORS: keep it short, this is a help text
1560                 self.helpList.append((self["menuActions"], "OkCancelActions", [("ok", _("Quit"))]))
1561                 # TRANSLATORS: keep it short, this is a help text
1562                 self.helpList.append((self["menuActions"], "ColorActions", [("green", _("Toggle WLAN"))]))
1563                 # TRANSLATORS: keep it short, this is a help text
1564                 self.helpList.append((self["menuActions"], "ColorActions", [("red", _("Reset"))]))
1565                 # TRANSLATORS: keep it short, this is a help text
1566                 self.helpList.append((self["menuActions"], "EPGSelectActions", [("info", _("Refresh status"))]))
1567
1568                 self["FBFInfo"] = Label(_('Getting status from FRITZ!Box Fon...'))
1569
1570                 self["FBFInternet"] = Label('Internet')
1571                 self["internet_inactive"] = Pixmap()
1572                 self["internet_active"] = Pixmap()
1573                 self["internet_active"].hide()
1574
1575                 self["FBFDsl"] = Label('DSL')
1576                 self["dsl_inactive"] = Pixmap()
1577                 self["dsl_inactive"].hide()
1578                 self["dsl_active"] = Pixmap()
1579                 self["dsl_active"].hide()
1580
1581                 self["FBFWlan"] = Label('WLAN ')
1582                 self["wlan_inactive"] = Pixmap()
1583                 self["wlan_inactive"].hide()
1584                 self["wlan_active"] = Pixmap()
1585                 self["wlan_active"].hide()
1586                 self._wlanActive = False
1587
1588                 if fritzbox.info[FBF_dectActive] is not None: 
1589                         self["FBFDect"] = Label('DECT')
1590                         self["dect_inactive"] = Pixmap()
1591                         self["dect_active"] = Pixmap()
1592                         self["dect_active"].hide()
1593
1594                 if fritzbox.info[FBF_faxActive] is not None: 
1595                         self["FBFFax"] = Label('Fax')
1596                         self["fax_inactive"] = Pixmap()
1597                         self["fax_active"] = Pixmap()
1598                         self["fax_active"].hide()
1599
1600                 if fritzbox.info[FBF_rufumlActive] is not None: 
1601                         self["FBFRufuml"] = Label(_('Call redirection'))
1602                         self["rufuml_inactive"] = Pixmap()
1603                         self["rufuml_active"] = Pixmap()
1604                         self["rufuml_active"].hide()
1605
1606                 self._timer = eTimer()
1607                 self._timer.callback.append(self._getInfo)
1608                 self.onShown.append(lambda: self._timer.start(5000))
1609                 self.onHide.append(self._timer.stop)
1610                 self._getInfo()
1611                 self.onLayoutFinish.append(self.setWindowTitle)
1612
1613         def setWindowTitle(self):
1614                 # TRANSLATORS: this is a window title.
1615                 self.setTitle(_("FRITZ!Box Fon Status"))
1616
1617         def _getInfo(self):
1618                 fritzbox.getInfo(self._fillMenu)
1619
1620         def _fillMenu(self, status):
1621                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = status
1622                 self._wlanActive = (wlanState[0] == '1')
1623                 self._mailboxActive = False
1624                 try:
1625                         if not self.has_key("FBFInfo"): # screen is closed already
1626                                 return
1627
1628                         if boxInfo:
1629                                 self["FBFInfo"].setText(boxInfo.replace(', ', '\n'))
1630                         else:
1631                                 self["FBFInfo"].setText('BoxInfo ' + _('Status not available'))
1632
1633                         if ipAddress:
1634                                 if upTime:
1635                                         self["FBFInternet"].setText('Internet ' + _('IP Address:') + ' ' + ipAddress + '\n' + _('Connected since') + ' ' + upTime)
1636                                 else:
1637                                         self["FBFInternet"].setText('Internet ' + _('IP Address:') + ' ' + ipAddress)
1638                                 self["internet_inactive"].hide()
1639                                 self["internet_active"].show()
1640                         else:
1641                                 self["internet_active"].hide()
1642                                 self["internet_inactive"].show()
1643
1644                         if dslState:
1645                                 if dslState[0] == '5':
1646                                         self["dsl_inactive"].hide()
1647                                         self["dsl_active"].show()
1648                                         if dslState[1]:
1649                                                 self["FBFDsl"].setText('DSL ' + dslState[1])
1650                                 else:
1651                                         self["dsl_active"].hide()
1652                                         self["dsl_inactive"].show()
1653                         else:
1654                                 self["FBFDsl"].setText('DSL ' + _('Status not available'))
1655                                 self["dsl_active"].hide()
1656                                 self["dsl_inactive"].hide()
1657
1658                         if wlanState:
1659                                 if wlanState[0 ] == '1':
1660                                         self["wlan_inactive"].hide()
1661                                         self["wlan_active"].show()
1662                                         message = 'WLAN'
1663                                         if wlanState[1] == '0':
1664                                                 message += ' ' + _('not encrypted')
1665                                         else:
1666                                                 message += ' ' + _('encrypted')
1667                                         if wlanState[2]:
1668                                                 if wlanState[2] == '0':
1669                                                         message = message + ', ' + _('no device active')
1670                                                 elif wlanState[2] == '1':
1671                                                         message = message + ', ' + _('one device active')
1672                                                 else:
1673                                                         message = message + ', ' + wlanState[2] + ' ' + _('devices active')
1674                                         self["FBFWlan"].setText(message)
1675                                 else:
1676                                         self["wlan_active"].hide()
1677                                         self["wlan_inactive"].show()
1678                                         self["FBFWlan"].setText('WLAN')
1679                         else:
1680                                 self["FBFWlan"].setText('WLAN ' + _('Status not available'))
1681                                 self["wlan_active"].hide()
1682                                 self["wlan_inactive"].hide()
1683
1684                         if fritzbox.info[FBF_tamActive]:
1685                                 if  not tamActive or tamActive[0] == 0:
1686                                         self._mailboxActive = False
1687                                         self["mailbox_active"].hide()
1688                                         self["mailbox_inactive"].show()
1689                                         self["FBFMailbox"].setText(_('No mailbox active'))
1690                                 else:
1691                                         self._mailboxActive = True
1692                                         message = '('
1693                                         for i in range(5):
1694                                                 if tamActive[i+1]:
1695                                                         message = message + str(i) + ','
1696                                         message = message[:-1] + ')'
1697                                         self["mailbox_inactive"].hide()
1698                                         self["mailbox_active"].show()
1699                                         if tamActive[0] == 1:
1700                                                 self["FBFMailbox"].setText(_('One mailbox active') + ' ' + message)
1701                                         else:
1702                                                 self["FBFMailbox"].setText(str(tamActive[0]) + ' ' + _('mailboxes active') + ' ' + message)
1703         
1704                         if fritzbox.info[FBF_dectActive] and dectActive:
1705                                 self["dect_inactive"].hide()
1706                                 self["dect_active"].show()
1707                                 if dectActive == 0:
1708                                         self["FBFDect"].setText(_('No DECT phone registered'))
1709                                 else:
1710                                         if dectActive == 1:
1711                                                 self["FBFDect"].setText(_('One DECT phone registered'))
1712                                         else:
1713                                                 self["FBFDect"].setText(str(dectActive) + ' ' + _('DECT phones registered'))
1714
1715                         if fritzbox.info[FBF_faxActive] and faxActive:
1716                                 self["fax_inactive"].hide()
1717                                 self["fax_active"].show()
1718                                 self["FBFFax"].setText(_('Software fax active'))
1719
1720                         if fritzbox.info[FBF_rufumlActive] is not None and rufumlActive is not None:
1721                                 if rufumlActive == 0:
1722                                         self["rufuml_active"].hide()
1723                                         self["rufuml_inactive"].show()
1724                                         self["FBFRufuml"].setText(_('No call redirection active'))
1725                                 else:
1726                                         self["rufuml_inactive"].hide()
1727                                         self["rufuml_active"].show()
1728                                         if rufumlActive == 1:
1729                                                 self["FBFRufuml"].setText(_('One call redirection active'))
1730                                         else:
1731                                                 self["FBFRufuml"].setText(str(rufumlActive) + ' ' + _('call redirections active'))
1732
1733                 except KeyError:
1734                         debug("[FritzCallFBF] _fillMenu: " + traceback.format_exc())
1735
1736         def _toggleWlan(self):
1737                 if self._wlanActive:
1738                         debug("[FritzMenu] toggleWlan off")
1739                         fritzbox.changeWLAN('0')
1740                 else:
1741                         debug("[FritzMenu] toggleWlan off")
1742                         fritzbox.changeWLAN('1')
1743
1744         def _toggleMailbox(self, which):
1745                 debug("[FritzMenu] toggleMailbox")
1746                 if fritzbox.info[FBF_tamActive]:
1747                         debug("[FritzMenu] toggleMailbox off")
1748                         fritzbox.changeMailbox(which)
1749
1750         def _reset(self):
1751                 fritzbox.reset()
1752                 self._exit()
1753
1754         def _exit(self):
1755                 self._timer.stop()
1756                 self.close()
1757
1758
1759 class FritzDisplayCalls(Screen, HelpableScreen):
1760
1761         def __init__(self, session, text=""): #@UnusedVariable # pylint: disable=W0613
1762                 self.width = DESKTOP_WIDTH * scaleH(75, 85)/100
1763                 self.height = DESKTOP_HEIGHT * 0.75
1764                 dateFieldWidth = scaleH(180, 105)
1765                 dirFieldWidth = 16
1766                 lengthFieldWidth = scaleH(55, 45)
1767                 scrollbarWidth = scaleH(35, 35)
1768                 entriesWidth = self.width -scaleH(40, 5) -5
1769                 hereFieldWidth = entriesWidth -dirFieldWidth -5 -dateFieldWidth -5 -lengthFieldWidth -scrollbarWidth
1770                 fieldWidth = entriesWidth -dirFieldWidth -5 -5 -scrollbarWidth
1771                 fontSize = scaleV(22, 20)
1772                 itemHeight = 2*fontSize+5
1773                 entriesHeight = self.height -scaleV(15, 10) -5 -fontSize -5 -5 -5 -40 -5
1774                 buttonGap = (self.width -4*140)/5
1775                 buttonV = self.height -40
1776                 debug("[FritzDisplayCalls] width: " + str(self.width))
1777                 self.skin = """
1778                         <screen name="FritzDisplayCalls" position="center,center" size="%d,%d" title="Phone calls" >
1779                                 <eLabel position="0,0" size="%d,2" backgroundColor="#aaaaaa" />
1780                                 <widget name="statusbar" position="%d,%d" size="%d,%d" font="Regular;%d" backgroundColor="#aaaaaa" transparent="1" />
1781                                 <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
1782                                 <widget source="entries" render="Listbox" position="%d,%d" size="%d,%d" scrollbarMode="showOnDemand" transparent="1">
1783                                         <convert type="TemplatedMultiContent">
1784                                                 {"template": [
1785                                                                 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER, text = 1), # index 0 is the number, index 1 is date
1786                                                                 MultiContentEntryPixmapAlphaTest(pos = (%d,%d), size = (%d,%d), png = 2), # index 1 i direction pixmap
1787                                                                 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=1, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER, text = 3), # index 2 is remote name/number
1788                                                                 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER, text = 4), # index 3 is length of call
1789                                                                 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_RIGHT|RT_VALIGN_CENTER, text = 5), # index 4 is my number/name for number
1790                                                         ],
1791                                                 "fonts": [gFont("Regular", %d), gFont("Regular", %d)],
1792                                                 "itemHeight": %d
1793                                                 }
1794                                         </convert>
1795                                 </widget>
1796                                 <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
1797                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1798                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1799                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1800                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1801                                 <widget name="key_red" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1802                                 <widget name="key_green" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1803                                 <widget name="key_yellow" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1804                                 <widget name="key_blue" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1805                         </screen>""" % (
1806                                                 # scaleH(90, 75), scaleV(100, 78), # position 
1807                                                 self.width, self.height, # size
1808                                                 self.width, # eLabel width
1809                                                 scaleH(40, 5), scaleV(10, 5), # statusbar position
1810                                                 self.width, fontSize+5, # statusbar size
1811                                                 scaleV(21, 21), # statusbar font size
1812                                                 scaleV(10, 5)+5+fontSize+5, # eLabel position vertical
1813                                                 self.width, # eLabel width
1814                                                 scaleH(40, 5), scaleV(10, 5)+5+fontSize+5+5, # entries position
1815                                                 entriesWidth, entriesHeight, # entries size
1816                                                 5+dirFieldWidth+5, fontSize+5, dateFieldWidth, fontSize, # date pos/size
1817                                                 5, (itemHeight-dirFieldWidth)/2, dirFieldWidth, dirFieldWidth, # dir pos/size
1818                                                 5+dirFieldWidth+5, 5, fieldWidth, fontSize, # caller pos/size
1819                                                 2+dirFieldWidth+2+dateFieldWidth+5, fontSize+5, lengthFieldWidth, fontSize, # length pos/size
1820                                                 2+dirFieldWidth+2+dateFieldWidth+5+lengthFieldWidth+5, fontSize+5, hereFieldWidth, fontSize, # my number pos/size
1821                                                 fontSize-4, fontSize, # fontsize
1822                                                 itemHeight, # itemHeight
1823                                                 buttonV-5, # eLabel position vertical
1824                                                 self.width, # eLabel width
1825                                                 buttonGap, buttonV, "skin_default/buttons/red.png", # widget red
1826                                                 2*buttonGap+140, buttonV, "skin_default/buttons/green.png", # widget green
1827                                                 3*buttonGap+2*140, buttonV, "skin_default/buttons/yellow.png", # widget yellow
1828                                                 4*buttonGap+3*140, buttonV, "skin_default/buttons/blue.png", # widget blue
1829                                                 buttonGap, buttonV, scaleV(22, 21), # widget red
1830                                                 2*buttonGap+140, buttonV, scaleV(22, 21), # widget green
1831                                                 3*buttonGap+2*140, buttonV, scaleV(22, 21), # widget yellow
1832                                                 4*buttonGap+3*140, buttonV, scaleV(22, 21), # widget blue
1833                                                                                                                 )
1834                 # debug("[FritzDisplayCalls] skin: " + self.skin)
1835                 Screen.__init__(self, session)
1836                 HelpableScreen.__init__(self)
1837
1838                 # TRANSLATORS: keep it short, this is a button
1839                 self["key_yellow"] = Button(_("All"))
1840                 # TRANSLATORS: keep it short, this is a button
1841                 self["key_red"] = Button(_("Missed"))
1842                 # TRANSLATORS: keep it short, this is a button
1843                 self["key_blue"] = Button(_("Incoming"))
1844                 # TRANSLATORS: keep it short, this is a button
1845                 self["key_green"] = Button(_("Outgoing"))
1846
1847                 self["setupActions"] = ActionMap(["OkCancelActions", "ColorActions"],
1848                 {
1849                         "yellow": (lambda: self.display(FBF_ALL_CALLS)),
1850                         "red": (lambda: self.display(FBF_MISSED_CALLS)),
1851                         "blue": (lambda: self.display(FBF_IN_CALLS)),
1852                         "green": (lambda: self.display(FBF_OUT_CALLS)),
1853                         "cancel": self.ok,
1854                         "ok": self.showEntry, }, - 2)
1855
1856                 # TRANSLATORS: keep it short, this is a help text
1857                 self.helpList.append((self["setupActions"], "OkCancelActions", [("ok", _("Show details of entry"))]))
1858                 # TRANSLATORS: keep it short, this is a help text
1859                 self.helpList.append((self["setupActions"], "OkCancelActions", [("cancel", _("Quit"))]))
1860                 # TRANSLATORS: keep it short, this is a help text
1861                 self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("Display all calls"))]))
1862                 # TRANSLATORS: keep it short, this is a help text
1863                 self.helpList.append((self["setupActions"], "ColorActions", [("red", _("Display missed calls"))]))
1864                 # TRANSLATORS: keep it short, this is a help text
1865                 self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("Display incoming calls"))]))
1866                 # TRANSLATORS: keep it short, this is a help text
1867                 self.helpList.append((self["setupActions"], "ColorActions", [("green", _("Display outgoing calls"))]))
1868
1869                 self["statusbar"] = Label(_("Getting calls from FRITZ!Box..."))
1870                 self.list = []
1871                 self["entries"] = List(self.list)
1872                 #=======================================================================
1873                 # fontSize = scaleV(22, 18)
1874                 # fontHeight = scaleV(24, 20)
1875                 # self["entries"].l.setFont(0, gFont("Regular", fontSize))
1876                 # self["entries"].l.setItemHeight(fontHeight)
1877                 #=======================================================================
1878                 debug("[FritzDisplayCalls] init: '''%s'''" % config.plugins.FritzCall.fbfCalls.value)
1879                 self.display()
1880                 self.onLayoutFinish.append(self.setWindowTitle)
1881
1882         def setWindowTitle(self):
1883                 # TRANSLATORS: this is a window title.
1884                 self.setTitle(_("Phone calls"))
1885
1886         def ok(self):
1887                 self.close()
1888
1889         def display(self, which=config.plugins.FritzCall.fbfCalls.value):
1890                 debug("[FritzDisplayCalls] display")
1891                 config.plugins.FritzCall.fbfCalls.value = which
1892                 config.plugins.FritzCall.fbfCalls.save()
1893                 fritzbox.getCalls(self, lambda x: self.gotCalls(x, which), which)
1894
1895         def gotCalls(self, listOfCalls, which):
1896                 debug("[FritzDisplayCalls] gotCalls")
1897                 self.updateStatus(fbfCallsChoices[which] + " (" + str(len(listOfCalls)) + ")")
1898
1899                 directout = LoadPixmap(resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/callout.png"))
1900                 directin = LoadPixmap(resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/callin.png"))
1901                 directfailed = LoadPixmap(resolveFilename(SCOPE_PLUGINS, "Extensions/FritzCall/images/callinfailed.png"))
1902                 def pixDir(direct):
1903                         if direct == FBF_OUT_CALLS:
1904                                 direct = directout
1905                         elif direct == FBF_IN_CALLS:
1906                                 direct = directin
1907                         else:
1908                                 direct = directfailed
1909                         return direct
1910
1911                 # debug("[FritzDisplayCalls] gotCalls: %s" %repr(listOfCalls))
1912                 self.list = [(number, date[:6] + ' ' + date[9:14], pixDir(direct), remote, length, here) for (number, date, direct, remote, length, here) in listOfCalls]
1913                 self["entries"].setList(self.list)
1914                 if len(self.list) > 1:
1915                         self["entries"].setIndex(1)
1916
1917         def updateStatus(self, text):
1918                 if self.has_key("statusbar"):
1919                         self["statusbar"].setText(_("Getting calls from FRITZ!Box...") + ' ' + text)
1920
1921         def showEntry(self):
1922                 debug("[FritzDisplayCalls] showEntry")
1923                 cur = self["entries"].getCurrent()
1924                 if cur:
1925                         if cur[0]:
1926                                 debug("[FritzDisplayCalls] showEntry %s" % (cur[0]))
1927                                 number = cur[0]
1928                                 fullname = phonebook.search(cur[0])
1929                                 if fullname:
1930                                         # we have a name for this number
1931                                         name = fullname
1932                                         self.session.open(FritzOfferAction, self, number, name)
1933                                 elif cur[3]:
1934                                         name = cur[3]
1935                                         self.session.open(FritzOfferAction, self, number, name)
1936                                 else:
1937                                         # we don't
1938                                         fullname = resolveNumberWithAvon(number, config.plugins.FritzCall.country.value)
1939                                         if fullname:
1940                                                 name = fullname
1941                                                 self.session.open(FritzOfferAction, self, number, name)
1942                                         else:
1943                                                 self.session.open(FritzOfferAction, self, number)
1944                         else:
1945                                 # we do not even have a number...
1946                                 self.session.open(MessageBox,
1947                                                   _("UNKNOWN"),
1948                                                   type=MessageBox.TYPE_INFO)
1949
1950
1951 class FritzOfferAction(Screen):
1952
1953         def __init__(self, session, parent, number, name=""):
1954                 # the layout will completely be recalculated in finishLayout
1955                 self.skin = """
1956                         <screen name="FritzOfferAction" title="Do what?" >
1957                                 <widget name="text" size="%d,%d" font="Regular;%d" />
1958                                 <widget name="FacePixmap" size="%d,%d" alphatest="on" />
1959                                 <widget name="key_red_p" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1960                                 <widget name="key_green_p" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1961                                 <widget name="key_yellow_p" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
1962                                 <widget name="key_red" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1963                                 <widget name="key_green" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1964                                 <widget name="key_yellow" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
1965                         </screen>""" % (
1966                                                         DESKTOP_WIDTH, DESKTOP_HEIGHT, # set maximum size
1967                                                         scaleH(22,21), # text
1968                                                         DESKTOP_WIDTH, DESKTOP_HEIGHT, # set maximum size
1969                                                         "skin_default/buttons/red.png",
1970                                                         "skin_default/buttons/green.png",
1971                                                         "skin_default/buttons/yellow.png",
1972                                                         ) 
1973                 debug("[FritzOfferAction] init: %s, %s" %(number, name))
1974                 Screen.__init__(self, session)
1975         
1976                 # TRANSLATORS: keep it short, this is a button
1977                 self["key_red"] = Button(_("Lookup"))
1978                 # TRANSLATORS: keep it short, this is a button
1979                 self["key_green"] = Button(_("Call"))
1980                 # TRANSLATORS: keep it short, this is a button
1981                 self["key_yellow"] = Button(_("Save"))
1982                 # TRANSLATORS: keep it short, this is a button
1983                 # self["key_blue"] = Button(_("Search"))
1984
1985                 self["FritzOfferActions"] = ActionMap(["OkCancelActions", "ColorActions"],
1986                 {
1987                         "red": self._lookup,
1988                         "green": self._call,
1989                         "yellow": self._add,
1990                         "cancel": self._exit,
1991                         "ok": self._exit, }, - 2)
1992
1993                 self._session = session
1994                 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0] == "0":
1995                         number = number[1:]
1996                 self._number = number
1997                 self._name = name.replace("\n", ", ")
1998                 self["text"] = Label(number + "\n\n" + name.replace(", ", "\n"))
1999                 self._parent = parent
2000                 self._lookupState = 0
2001                 self["key_red_p"] = Pixmap()
2002                 self["key_green_p"] = Pixmap()
2003                 self["key_yellow_p"] = Pixmap()
2004                 self["FacePixmap"] = Pixmap()
2005                 self.onLayoutFinish.append(self._finishLayout)
2006                 self.onLayoutFinish.append(self.setWindowTitle)
2007
2008         def setWindowTitle(self):
2009                 # TRANSLATORS: this is a window title.
2010                 self.setTitle(_("Do what?"))
2011
2012         def _finishLayout(self):
2013                 # pylint: disable=W0142
2014                 debug("[FritzCall] FritzOfferAction/finishLayout number: %s/%s" % (self._number, self._name))
2015
2016                 faceFile = findFace(self._number, self._name)
2017                 picPixmap = LoadPixmap(faceFile)
2018                 if not picPixmap:       # that means most probably, that the picture is not 8 bit...
2019                         Notifications.AddNotification(MessageBox, _("Found picture\n\n%s\n\nBut did not load. Probably not PNG, 8-bit") %faceFile, type = MessageBox.TYPE_ERROR)
2020                         picPixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/icons/input_error.png"))
2021                 picSize = picPixmap.size()
2022                 self["FacePixmap"].instance.setPixmap(picPixmap)
2023
2024                 noButtons = 3
2025                 # recalculate window size
2026                 textSize = self["text"].getSize()
2027                 textSize = (textSize[0]+20, textSize[1]+20) # don't know, why, but size is too small
2028                 debug("[FritzCall] FritzOfferAction/finishLayout textsize: %s/%s" % textSize)
2029                 textSize = eSize(*textSize)
2030                 width = max(scaleH(620, 545), noButtons*145, picSize.width() + textSize.width() + 30)
2031                 height = max(picSize.height()+5, textSize.height()+5, scaleV(-1, 136)) + 5 + 40 + 5
2032                 buttonsGap = (width-noButtons*140)/(noButtons+1)
2033                 buttonsVPos = height-40-5
2034                 wSize = (width, height)
2035                 wSize = eSize(*wSize)
2036
2037                 # center the smaller vertically
2038                 hGap = (width-picSize.width()-textSize.width())/3
2039                 picPos = (hGap, (height-50-picSize.height())/2+5)
2040                 textPos = (hGap+picSize.width()+hGap, (height-50-textSize.height())/2+5)
2041
2042                 # resize screen
2043                 self.instance.resize(wSize)
2044                 # resize text
2045                 self["text"].instance.resize(textSize)
2046                 # resize pixmap
2047                 self["FacePixmap"].instance.resize(picSize)
2048                 # move buttons
2049                 buttonPos = (buttonsGap, buttonsVPos)
2050                 self["key_red_p"].instance.move(ePoint(*buttonPos))
2051                 self["key_red"].instance.move(ePoint(*buttonPos))
2052                 buttonPos = (buttonsGap+140+buttonsGap, buttonsVPos)
2053                 self["key_green_p"].instance.move(ePoint(*buttonPos))
2054                 self["key_green"].instance.move(ePoint(*buttonPos))
2055                 buttonPos = (buttonsGap+140+buttonsGap+140+buttonsGap, buttonsVPos)
2056                 self["key_yellow_p"].instance.move(ePoint(*buttonPos))
2057                 self["key_yellow"].instance.move(ePoint(*buttonPos))
2058                 # move text
2059                 self["text"].instance.move(ePoint(*textPos))
2060                 # move pixmap
2061                 self["FacePixmap"].instance.move(ePoint(*picPos))
2062                 # center window
2063                 self.instance.move(ePoint((DESKTOP_WIDTH-wSize.width())/2, (DESKTOP_HEIGHT-wSize.height())/2))
2064
2065         def _setTextAndResize(self, message):
2066                 # pylint: disable=W0142
2067                 self["text"].instance.resize(eSize(*(DESKTOP_WIDTH, DESKTOP_HEIGHT)))
2068                 self["text"].setText(self._number + "\n\n" + message)
2069                 self._finishLayout()
2070
2071         def _lookup(self):
2072                 phonebookLocation = config.plugins.FritzCall.phonebookLocation.value
2073                 if self._lookupState == 0:
2074                         self._lookupState = 1
2075                         self._setTextAndResize(_("Reverse searching..."))
2076                         ReverseLookupAndNotifier(self._number, self._lookedUp, "UTF-8", config.plugins.FritzCall.country.value)
2077                         return
2078                 if self._lookupState == 1 and os.path.exists(os.path.join(phonebookLocation, "PhoneBook.csv")):
2079                         self._setTextAndResize(_("Searching in Outlook export..."))
2080                         self._lookupState = 2
2081                         self._lookedUp(self._number, FritzOutlookCSV.findNumber(self._number, os.path.join(phonebookLocation, "PhoneBook.csv"))) #@UndefinedVariable
2082                         return
2083                 else:
2084                         self._lookupState = 2
2085                 if self._lookupState == 2 and os.path.exists(os.path.join(phonebookLocation, "PhoneBook.ldif")):
2086                         self._setTextAndResize(_("Searching in LDIF..."))
2087                         self._lookupState = 0
2088                         FritzLDIF.FindNumber(self._number, open(os.path.join(phonebookLocation, "PhoneBook.ldif")), self._lookedUp)
2089                         return
2090                 else:
2091                         self._lookupState = 0
2092                         self._lookup()
2093
2094         def _lookedUp(self, number, name):
2095                 name = handleReverseLookupResult(name)
2096                 if not name:
2097                         if self._lookupState == 1:
2098                                 name = _("No result from reverse lookup")
2099                         elif self._lookupState == 2:
2100                                 name = _("No result from Outlook export")
2101                         else:
2102                                 name = _("No result from LDIF")
2103                 self._name = name
2104                 self._number = number
2105                 debug("[FritzOfferAction] lookedUp: " + str(name.replace(", ", "\n")))
2106                 self._setTextAndResize(str(name.replace(", ", "\n")))
2107
2108         def _call(self):
2109                 debug("[FritzOfferAction] add: %s" %self._number)
2110                 fritzbox.dial(self._number)
2111                 self._exit()
2112
2113         def _add(self):
2114                 debug("[FritzOfferAction] add: %s, %s" %(self._number, self._name))
2115                 phonebook.FritzDisplayPhonebook(self._session).add(self._parent, self._number, self._name)
2116                 self._exit()
2117
2118         def _exit(self):
2119                 self.close()
2120
2121
2122 class FritzCallPhonebook:
2123         def __init__(self):
2124                 debug("[FritzCallPhonebook] init")
2125                 # Beware: strings in phonebook.phonebook have to be in utf-8!
2126                 self.phonebook = {}
2127                 self.reload()
2128
2129         def reload(self):
2130                 debug("[FritzCallPhonebook] reload")
2131                 # Beware: strings in phonebook.phonebook have to be in utf-8!
2132                 self.phonebook = {}
2133
2134                 if not config.plugins.FritzCall.enable.value:
2135                         return
2136
2137                 phonebookFilename = os.path.join(config.plugins.FritzCall.phonebookLocation.value, "PhoneBook.txt")
2138                 if config.plugins.FritzCall.phonebook.value and os.path.exists(phonebookFilename):
2139                         debug("[FritzCallPhonebook] reload: read " + phonebookFilename)
2140                         phonebookTxtCorrupt = False
2141                         self.phonebook = {}
2142                         for line in open(phonebookFilename):
2143                                 try:
2144                                         # Beware: strings in phonebook.phonebook have to be in utf-8!
2145                                         line = line.decode("utf-8")
2146                                 except UnicodeDecodeError: # this is just for the case, somebody wrote latin1 chars into PhoneBook.txt
2147                                         try:
2148                                                 line = line.decode("iso-8859-1")
2149                                                 debug("[FritzCallPhonebook] Fallback to ISO-8859-1 in %s" % line)
2150                                                 phonebookTxtCorrupt = True
2151                                         except UnicodeDecodeError:
2152                                                 debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2153                                                 phonebookTxtCorrupt = True
2154                                 line = line.encode("utf-8")
2155                                 elems = line.split('#')
2156                                 if len(elems) == 2:
2157                                         try:
2158                                                 self.phonebook[elems[0]] = elems[1]
2159                                         except ValueError: # how could this possibly happen?!?!
2160                                                 debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2161                                                 phonebookTxtCorrupt = True
2162                                 else:
2163                                         debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2164                                         phonebookTxtCorrupt = True
2165                                         
2166                                 #===============================================================
2167                                 # found = re.match("^(\d+)#(.*)$", line)
2168                                 # if found:
2169                                 #       try:
2170                                 #               self.phonebook[found.group(1)] = found.group(2)
2171                                 #       except ValueError: # how could this possibly happen?!?!
2172                                 #               debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2173                                 #               phonebookTxtCorrupt = True
2174                                 # else:
2175                                 #       debug("[FritzCallPhonebook] Could not parse internal Phonebook Entry %s" % line)
2176                                 #       phonebookTxtCorrupt = True
2177                                 #===============================================================
2178
2179                         if phonebookTxtCorrupt:
2180                                 # dump phonebook to PhoneBook.txt
2181                                 debug("[FritzCallPhonebook] dump Phonebook.txt")
2182                                 try:
2183                                         os.rename(phonebookFilename, phonebookFilename + ".bck")
2184                                         fNew = open(phonebookFilename, 'w')
2185                                         # Beware: strings in phonebook.phonebook are utf-8!
2186                                         for (number, name) in self.phonebook.iteritems():
2187                                                 # Beware: strings in PhoneBook.txt have to be in utf-8!
2188                                                 fNew.write(number + "#" + name.encode("utf-8"))
2189                                         fNew.close()
2190                                 except (IOError, OSError):
2191                                         debug("[FritzCallPhonebook] error renaming or writing to %s" %phonebookFilename)
2192
2193 #===============================================================================
2194 #               #
2195 #               # read entries from Outlook export
2196 #               #
2197 #               # not reliable with coding yet
2198 #               # 
2199 #               # import csv exported from Outlook 2007 with csv(Windows)
2200 #               csvFilename = "/tmp/PhoneBook.csv"
2201 #               if config.plugins.FritzCall.phonebook.value and os.path.exists(csvFilename):
2202 #                       try:
2203 #                               readOutlookCSV(csvFilename, self.add)
2204 #                               os.rename(csvFilename, csvFilename + ".done")
2205 #                       except ImportError:
2206 #                               debug("[FritzCallPhonebook] CSV import failed" %line)
2207 #===============================================================================
2208
2209                 
2210 #===============================================================================
2211 #               #
2212 #               # read entries from LDIF
2213 #               #
2214 #               # import ldif exported from Thunderbird 2.0.0.19
2215 #               ldifFilename = "/tmp/PhoneBook.ldif"
2216 #               if config.plugins.FritzCall.phonebook.value and os.path.exists(ldifFilename):
2217 #                       try:
2218 #                               parser = MyLDIF(open(ldifFilename), self.add)
2219 #                               parser.parse()
2220 #                               os.rename(ldifFilename, ldifFilename + ".done")
2221 #                       except ImportError:
2222 #                               debug("[FritzCallPhonebook] LDIF import failed" %line)
2223 #===============================================================================
2224                 
2225                 if fritzbox and config.plugins.FritzCall.fritzphonebook.value:
2226                         fritzbox.loadFritzBoxPhonebook()
2227
2228         def search(self, number):
2229                 # debug("[FritzCallPhonebook] Searching for %s" %number)
2230                 name = ""
2231                 if not self.phonebook or not number:
2232                         return
2233
2234                 if config.plugins.FritzCall.prefix.value:
2235                         prefix = config.plugins.FritzCall.prefix.value
2236                         if number[0] != '0':
2237                                 number = prefix + number
2238                                 # debug("[FritzCallPhonebook] search: added prefix: %s" %number)
2239                         elif number[:len(prefix)] == prefix and self.phonebook.has_key(number[len(prefix):]):
2240                                 # debug("[FritzCallPhonebook] search: same prefix")
2241                                 name = self.phonebook[number[len(prefix):]]
2242                                 # debug("[FritzCallPhonebook] search: result: %s" %name)
2243                 else:
2244                         prefix = ""
2245                                 
2246                 if not name and self.phonebook.has_key(number):
2247                         name = self.phonebook[number]
2248                                 
2249                 return name.replace(", ", "\n").strip()
2250
2251         def add(self, number, name):
2252                 '''
2253                 
2254                 @param number: number of entry
2255                 @param name: name of entry, has to be in utf-8
2256                 '''
2257                 debug("[FritzCallPhonebook] add")
2258                 name = name.replace("\n", ", ").replace('#','') # this is just for safety reasons. add should only be called with newlines converted into commas
2259                 self.remove(number)
2260                 self.phonebook[number] = name
2261                 if number and number != 0:
2262                         if config.plugins.FritzCall.phonebook.value:
2263                                 try:
2264                                         name = name.strip() + "\n"
2265                                         string = "%s#%s" % (number, name)
2266                                         # Beware: strings in PhoneBook.txt have to be in utf-8!
2267                                         f = open(os.path.join(config.plugins.FritzCall.phonebookLocation.value, "PhoneBook.txt"), 'a')
2268                                         f.write(string)
2269                                         f.close()
2270                                         debug("[FritzCallPhonebook] added %s with %s to Phonebook.txt" % (number, name.strip()))
2271                                         return True
2272         
2273                                 except IOError:
2274                                         return False
2275
2276         def remove(self, number):
2277                 if number in self.phonebook:
2278                         debug("[FritzCallPhonebook] remove entry in phonebook")
2279                         del self.phonebook[number]
2280                         if config.plugins.FritzCall.phonebook.value:
2281                                 try:
2282                                         phonebookFilename = os.path.join(config.plugins.FritzCall.phonebookLocation.value, "PhoneBook.txt")
2283                                         debug("[FritzCallPhonebook] remove entry in Phonebook.txt")
2284                                         fOld = open(phonebookFilename, 'r')
2285                                         fNew = open(phonebookFilename + str(os.getpid()), 'w')
2286                                         line = fOld.readline()
2287                                         while (line):
2288                                                 elems = line.split('#')
2289                                                 if len(elems) == 2 and not elems[0] == number:
2290                                                         fNew.write(line)
2291                                                 line = fOld.readline()
2292                                         fOld.close()
2293                                         fNew.close()
2294                                         # os.remove(phonebookFilename)
2295                                         eBackgroundFileEraser.getInstance().erase(phonebookFilename)
2296                                         os.rename(phonebookFilename + str(os.getpid()), phonebookFilename)
2297                                         debug("[FritzCallPhonebook] removed %s from Phonebook.txt" % number)
2298                                         return True
2299         
2300                                 except (IOError, OSError):
2301                                         debug("[FritzCallPhonebook] error removing %s from %s" %(number, phonebookFilename))
2302                 return False
2303
2304         class FritzDisplayPhonebook(Screen, HelpableScreen, NumericalTextInput):
2305         
2306                 def __init__(self, session):
2307                         self.entriesWidth = DESKTOP_WIDTH * scaleH(75, 85)/100
2308                         self.height = DESKTOP_HEIGHT * 0.75
2309                         numberFieldWidth = scaleH(220, 160)
2310                         fieldWidth = self.entriesWidth -5 -numberFieldWidth -10
2311                         fontSize = scaleV(22, 18)
2312                         fontHeight = scaleV(24, 20)
2313                         buttonGap = (self.entriesWidth-4*140)/5
2314                         debug("[FritzDisplayPhonebook] width: " + str(self.entriesWidth))
2315                         self.skin = """
2316                                 <screen name="FritzDisplayPhonebook" position="center,center" size="%d,%d" title="Phonebook" >
2317                                         <eLabel position="0,0" size="%d,2" backgroundColor="#aaaaaa" />
2318                                         <widget source="entries" render="Listbox" position="%d,%d" size="%d,%d" scrollbarMode="showOnDemand" transparent="1">
2319                                                 <convert type="TemplatedMultiContent">
2320                                                         {"template": [
2321                                                                         MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT, text = 1), # index 0 is the name, index 1 is shortname
2322                                                                         MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT, text = 2), # index 2 is number
2323                                                                 ],
2324                                                         "fonts": [gFont("Regular", %d)],
2325                                                         "itemHeight": %d
2326                                                         }
2327                                                 </convert>
2328                                         </widget>
2329                                         <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
2330                                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2331                                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2332                                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2333                                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2334                                         <widget name="key_red" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
2335                                         <widget name="key_green" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
2336                                         <widget name="key_yellow" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
2337                                         <widget name="key_blue" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
2338                                 </screen>""" % (
2339                                                 # scaleH(90, 75), scaleV(100, 73), # position 
2340                                                 self.entriesWidth, self.height, # size
2341                                                 self.entriesWidth, # eLabel width
2342                                                 scaleH(40, 5), scaleV(20, 5), # entries position
2343                                                 self.entriesWidth-scaleH(40, 5), self.height-scaleV(20, 5)-5-5-40, # entries size
2344                                                 0, 0, fieldWidth, scaleH(24,20), # name pos/size
2345                                                 fieldWidth +5, 0, numberFieldWidth, scaleH(24,20), # dir pos/size
2346                                                 fontSize, # fontsize
2347                                                 fontHeight, # itemHeight
2348                                                 self.height-40-5, # eLabel position vertical
2349                                                 self.entriesWidth, # eLabel width
2350                                                 buttonGap, self.height-40, "skin_default/buttons/red.png", # ePixmap red
2351                                                 2*buttonGap+140, self.height-40, "skin_default/buttons/green.png", # ePixmap green
2352                                                 3*buttonGap+2*140, self.height-40, "skin_default/buttons/yellow.png", # ePixmap yellow
2353                                                 4*buttonGap+3*140, self.height-40, "skin_default/buttons/blue.png", # ePixmap blue
2354                                                 buttonGap, self.height-40, scaleV(22, 21), # widget red
2355                                                 2*buttonGap+140, self.height-40, scaleV(22, 21), # widget green
2356                                                 3*buttonGap+2*140, self.height-40, scaleV(22, 21), # widget yellow
2357                                                 4*buttonGap+3*140, self.height-40, scaleV(22, 21), # widget blue
2358                                                 )
2359         
2360                         # debug("[FritzDisplayCalls] skin: " + self.skin)
2361                         Screen.__init__(self, session)
2362                         NumericalTextInput.__init__(self)
2363                         HelpableScreen.__init__(self)
2364                 
2365                         # TRANSLATORS: keep it short, this is a button
2366                         self["key_red"] = Button(_("Delete"))
2367                         # TRANSLATORS: keep it short, this is a button
2368                         self["key_green"] = Button(_("New"))
2369                         # TRANSLATORS: keep it short, this is a button
2370                         self["key_yellow"] = Button(_("Edit"))
2371                         # TRANSLATORS: keep it short, this is a button
2372                         self["key_blue"] = Button(_("Search"))
2373         
2374                         self["setupActions"] = ActionMap(["OkCancelActions", "ColorActions"],
2375                         {
2376                                 "red": self.delete,
2377                                 "green": self.add,
2378                                 "yellow": self.edit,
2379                                 "blue": self.search,
2380                                 "cancel": self.exit,
2381                                 "ok": self.showEntry, }, - 2)
2382         
2383                         # TRANSLATORS: keep it short, this is a help text
2384                         self.helpList.append((self["setupActions"], "OkCancelActions", [("ok", _("Show details of entry"))]))
2385                         # TRANSLATORS: keep it short, this is a help text
2386                         self.helpList.append((self["setupActions"], "OkCancelActions", [("cancel", _("Quit"))]))
2387                         # TRANSLATORS: keep it short, this is a help text
2388                         self.helpList.append((self["setupActions"], "ColorActions", [("red", _("Delete entry"))]))
2389                         # TRANSLATORS: keep it short, this is a help text
2390                         self.helpList.append((self["setupActions"], "ColorActions", [("green", _("Add entry to phonebook"))]))
2391                         # TRANSLATORS: keep it short, this is a help text
2392                         self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("Edit selected entry"))]))
2393                         # TRANSLATORS: keep it short, this is a help text
2394                         self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("Search (case insensitive)"))]))
2395         
2396                         self["entries"] = List([])
2397                         debug("[FritzCallPhonebook] displayPhonebook init")
2398                         self.help_window = None
2399                         self.sortlist = []
2400                         self.onLayoutFinish.append(self.setWindowTitle)
2401                         self.display()
2402
2403                 def setWindowTitle(self):
2404                         # TRANSLATORS: this is a window title.
2405                         self.setTitle(_("Phonebook"))
2406
2407                 def display(self, filterNumber=""):
2408                         debug("[FritzCallPhonebook] displayPhonebook/display")
2409                         self.sortlist = []
2410                         # Beware: strings in phonebook.phonebook are utf-8!
2411                         sortlistHelp = sorted((name.lower(), name, number) for (number, name) in phonebook.phonebook.iteritems())
2412                         for (low, name, number) in sortlistHelp:
2413                                 if number == "01234567890":
2414                                         continue
2415                                 try:
2416                                         low = low.decode("utf-8")
2417                                 except UnicodeDecodeError:  # this should definitely not happen
2418                                         try:
2419                                                 low = low.decode("iso-8859-1")
2420                                         except UnicodeDecodeError:
2421                                                 debug("[FritzCallPhonebook] displayPhonebook/display: corrupt phonebook entry for %s" % number)
2422                                                 # self.session.open(MessageBox, _("Corrupt phonebook entry\nfor number %s\nDeleting.") %number, type = MessageBox.TYPE_ERROR)
2423                                                 phonebook.remove(number)
2424                                                 continue
2425                                 else:
2426                                         if filterNumber:
2427                                                 filterNumber = filterNumber.lower()
2428                                                 if low.find(filterNumber) == - 1:
2429                                                         continue
2430                                         name = name.strip().decode("utf-8")
2431                                         number = number.strip().decode("utf-8")
2432                                         comma = name.find(',')
2433                                         if comma != -1:
2434                                                 shortname = name[:comma]
2435                                         else:
2436                                                 shortname = name
2437                                         number = number.encode("utf-8", "replace")
2438                                         name = name.encode("utf-8", "replace")
2439                                         shortname = shortname.encode('utf-8', 'replace')
2440                                         self.sortlist.append((name, shortname, number))
2441                                 
2442                         self["entries"].setList(self.sortlist)
2443         
2444                 def showEntry(self):
2445                         cur = self["entries"].getCurrent()
2446                         if cur:
2447                                 debug("[FritzCallPhonebook] displayPhonebook/showEntry %s" % (repr(cur)))
2448                                 number = cur[2]
2449                                 name = cur[0]
2450                                 self.session.open(FritzOfferAction, self, number, name)
2451         
2452                 def delete(self):
2453                         cur = self["entries"].getCurrent()
2454                         if cur:
2455                                 debug("[FritzCallPhonebook] displayPhonebook/delete %s" % (repr(cur)))
2456                                 self.session.openWithCallback(
2457                                         self.deleteConfirmed,
2458                                         MessageBox,
2459                                         _("Do you really want to delete entry for\n\n%(number)s\n\n%(name)s?") 
2460                                         % { 'number':str(cur[2]), 'name':str(cur[0]).replace(", ", "\n") }
2461                                                                 )
2462                         else:
2463                                 self.session.open(MessageBox, _("No entry selected"), MessageBox.TYPE_INFO)
2464         
2465                 def deleteConfirmed(self, ret):
2466                         debug("[FritzCallPhonebook] displayPhonebook/deleteConfirmed")
2467                         #
2468                         # if ret: delete number from sortlist, delete number from phonebook.phonebook and write it to disk
2469                         #
2470                         cur = self["entries"].getCurrent()
2471                         if cur:
2472                                 if ret:
2473                                         # delete number from sortlist, delete number from phonebook.phonebook and write it to disk
2474                                         debug("[FritzCallPhonebook] displayPhonebook/deleteConfirmed %s" % (repr(cur)))
2475                                         phonebook.remove(cur[2])
2476                                         self.display()
2477                                 # else:
2478                                         # self.session.open(MessageBox, _("Not deleted."), MessageBox.TYPE_INFO)
2479                         else:
2480                                 self.session.open(MessageBox, _("No entry selected"), MessageBox.TYPE_INFO)
2481         
2482                 def add(self, parent=None, number="", name=""):
2483                         class AddScreen(Screen, ConfigListScreen):
2484                                 '''ConfiglistScreen with two ConfigTexts for Name and Number'''
2485         
2486                                 def __init__(self, session, parent, number="", name=""):
2487                                         #
2488                                         # setup screen with two ConfigText and OK and ABORT button
2489                                         # 
2490                                         noButtons = 2
2491                                         width = max(scaleH(-1, 570), noButtons*140)
2492                                         height = scaleV(-1, 100) # = 5 + 126 + 40 + 5; 6 lines of text possible
2493                                         buttonsGap = (width-noButtons*140)/(noButtons+1)
2494                                         buttonsVPos = height-40-5
2495                                         self.skin = """
2496                                                 <screen position="center,center" size="%d,%d" title="Add entry to phonebook" >
2497                                                 <widget name="config" position="5,5" size="%d,%d" scrollbarMode="showOnDemand" />
2498                                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2499                                                 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2500                                                 <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" />
2501                                                 <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" />
2502                                                 </screen>""" % (
2503                                                                                 width, height,
2504                                                                                 width - 5 - 5, height - 5 - 40 - 5,
2505                                                                                 buttonsGap, buttonsVPos, "skin_default/buttons/red.png",
2506                                                                                 buttonsGap+140+buttonsGap, buttonsVPos, "skin_default/buttons/green.png",
2507                                                                                 buttonsGap, buttonsVPos,
2508                                                                                 buttonsGap+140+buttonsGap, buttonsVPos,
2509                                                                                 )
2510                                         Screen.__init__(self, session)
2511                                         self.session = session
2512                                         self.parent = parent
2513                                         # TRANSLATORS: keep it short, this is a button
2514                                         self["key_red"] = Button(_("Cancel"))
2515                                         # TRANSLATORS: keep it short, this is a button
2516                                         self["key_green"] = Button(_("OK"))
2517                                         self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
2518                                         {
2519                                                 "cancel": self.cancel,
2520                                                 "red": self.cancel,
2521                                                 "green": self.add,
2522                                                 "ok": self.add,
2523                                         }, - 2)
2524         
2525                                         self.list = [ ]
2526                                         ConfigListScreen.__init__(self, self.list, session=session)
2527                                         self.name = name
2528                                         self.number = number
2529                                         config.plugins.FritzCall.name.value = name
2530                                         config.plugins.FritzCall.number.value = number
2531                                         self.list.append(getConfigListEntry(_("Name"), config.plugins.FritzCall.name))
2532                                         self.list.append(getConfigListEntry(_("Number"), config.plugins.FritzCall.number))
2533                                         self["config"].list = self.list
2534                                         self["config"].l.setList(self.list)
2535                                         self.onLayoutFinish.append(self.setWindowTitle)
2536                         
2537                                 def setWindowTitle(self):
2538                                         # TRANSLATORS: this is a window title.
2539                                         self.setTitle(_("Add entry to phonebook"))
2540
2541                                 def add(self):
2542                                         # get texts from Screen
2543                                         # add (number,name) to sortlist and phonebook.phonebook and disk
2544                                         self.name = config.plugins.FritzCall.name.value
2545                                         self.number = config.plugins.FritzCall.number.value
2546                                         if not self.number or not self.name:
2547                                                 self.session.open(MessageBox, _("Entry incomplete."), type=MessageBox.TYPE_ERROR)
2548                                                 return
2549                                         # add (number,name) to sortlist and phonebook.phonebook and disk
2550         #                                       oldname = phonebook.search(self.number)
2551         #                                       if oldname:
2552         #                                               self.session.openWithCallback(
2553         #                                                       self.overwriteConfirmed,
2554         #                                                       MessageBox,
2555         #                                                       _("Do you really want to overwrite entry for %(number)s\n\n%(name)s\n\nwith\n\n%(newname)s?")
2556         #                                                       % {
2557         #                                                       'number':self.number,
2558         #                                                       'name': oldname,
2559         #                                                       'newname':self.name.replace(", ","\n")
2560         #                                                       }
2561         #                                                       )
2562         #                                               self.close()
2563         #                                               return
2564                                         phonebook.add(self.number, self.name)
2565                                         self.close()
2566                                         self.parent.display()
2567         
2568                                 def overwriteConfirmed(self, ret):
2569                                         if ret:
2570                                                 phonebook.remove(self.number)
2571                                                 phonebook.add(self.number, self.name)
2572                                                 self.parent.display()
2573         
2574                                 def cancel(self):
2575                                         self.close()
2576         
2577                         debug("[FritzCallPhonebook] displayPhonebook/add")
2578                         if not parent:
2579                                 parent = self
2580                         self.session.open(AddScreen, parent, number, name)
2581         
2582                 def edit(self):
2583                         debug("[FritzCallPhonebook] displayPhonebook/edit")
2584                         cur = self["entries"].getCurrent()
2585                         if cur is None:
2586                                 self.session.open(MessageBox, _("No entry selected"), MessageBox.TYPE_INFO)
2587                         else:
2588                                 self.add(self, cur[2], cur[0])
2589         
2590                 def search(self):
2591                         debug("[FritzCallPhonebook] displayPhonebook/search")
2592                         self.help_window = self.session.instantiateDialog(NumericalTextInputHelpDialog, self)
2593                         self.help_window.show()
2594                         # VirtualKeyboard instead of InputBox?
2595                         self.session.openWithCallback(self.doSearch, InputBox, _("Enter Search Terms"), _("Search phonebook"))
2596         
2597                 def doSearch(self, searchTerms):
2598                         if not searchTerms:
2599                                 searchTerms = ""
2600                         debug("[FritzCallPhonebook] displayPhonebook/doSearch: " + searchTerms)
2601                         if self.help_window:
2602                                 self.session.deleteDialog(self.help_window)
2603                                 self.help_window = None
2604                         self.display(searchTerms)
2605         
2606                 def exit(self):
2607                         self.close()
2608
2609 phonebook = FritzCallPhonebook()
2610
2611 class FritzCallSetup(Screen, ConfigListScreen, HelpableScreen):
2612
2613         def __init__(self, session, args=None): #@UnusedVariable # pylint: disable=W0613
2614                 self.width = scaleH(20+4*(140+90)+2*(35+40)+20, 4*140+2*35)
2615                 width = self.width
2616                 debug("[FritzCallSetup] width: " + str(self.width))
2617                 self.skin = """
2618                         <screen name="FritzCallSetup" position="center,center" size="%d,%d" title="FritzCall Setup" >
2619                         <eLabel position="0,0" size="%d,2" backgroundColor="#aaaaaa" />
2620                         <widget name="consideration" position="%d,%d" halign="center" size="%d,%d" font="Regular;%d" backgroundColor="#20040404" transparent="1" />
2621                         <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
2622                         <widget name="config" position="%d,%d" size="%d,%d" scrollbarMode="showOnDemand" backgroundColor="#20040404" transparent="1" />
2623                         <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
2624                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2625                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2626                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2627                         <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
2628                         <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" />
2629                         <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" />
2630                         <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" />
2631                         <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" />
2632                         <ePixmap position="%d,%d" zPosition="4" size="35,25" pixmap="%s" transparent="1" alphatest="on" />
2633                         <ePixmap position="%d,%d" zPosition="4" size="35,25" pixmap="%s" transparent="1" alphatest="on" />
2634                         </screen>""" % (
2635                                                 # (DESKTOP_WIDTH-width)/2, scaleV(100, 73), # position 
2636                                                 width, scaleV(560, 430), # size
2637                                                 width, # eLabel width
2638                                                 scaleH(40, 20), scaleV(10, 5), # consideration position
2639                                                 scaleH(width-80, width-40), scaleV(25, 45), # consideration size
2640                                                 scaleV(22, 20), # consideration font size
2641                                                 scaleV(40, 50), # eLabel position vertical
2642                                                 width, # eLabel width
2643                                                 scaleH(40, 5), scaleV(60, 57), # config position
2644                                                 scaleH(width-80, width-10), scaleV(453, 328), # config size
2645                                                 scaleV(518, 390), # eLabel position vertical
2646                                                 width, # eLabel width
2647                                                 scaleH(20, 0), scaleV(525, 395), "skin_default/buttons/red.png", # pixmap red
2648                                                 scaleH(20+140+90, 140), scaleV(525, 395), "skin_default/buttons/green.png", # pixmap green
2649                                                 scaleH(20+2*(140+90), 2*140), scaleV(525, 395), "skin_default/buttons/yellow.png", # pixmap yellow
2650                                                 scaleH(20+3*(140+90), 3*140), scaleV(525, 395), "skin_default/buttons/blue.png", # pixmap blue
2651                                                 scaleH(20, 0), scaleV(525, 395), scaleV(21, 21), # widget red
2652                                                 scaleH(20+(140+90), 140), scaleV(525, 395), scaleV(21, 21), # widget green
2653                                                 scaleH(20+2*(140+90), 2*140), scaleV(525, 395), scaleV(21, 21), # widget yellow
2654                                                 scaleH(20+3*(140+90), 3*140), scaleV(525, 395), scaleV(21, 21), # widget blue
2655                                                 scaleH(20+4*(140+90), 4*140), scaleV(532, 402), "skin_default/buttons/key_info.png", # button info
2656                                                 scaleH(20+4*(140+90)+(35+40), 4*140+35), scaleV(532, 402), "skin_default/buttons/key_menu.png", # button menu
2657                                                                                                         )
2658
2659                 Screen.__init__(self, session)
2660                 HelpableScreen.__init__(self)
2661                 self.session = session
2662
2663                 self["consideration"] = Label(_("You need to enable the monitoring on your FRITZ!Box by dialing #96*5*!"))
2664                 self.list = []
2665
2666                 # Initialize Buttons
2667                 # TRANSLATORS: keep it short, this is a button
2668                 self["key_red"] = Button(_("Cancel"))
2669                 # TRANSLATORS: keep it short, this is a button
2670                 self["key_green"] = Button(_("OK"))
2671                 # TRANSLATORS: keep it short, this is a button
2672                 self["key_yellow"] = Button(_("Phone calls"))
2673                 # TRANSLATORS: keep it short, this is a button
2674                 self["key_blue"] = Button(_("Phonebook"))
2675                 # TRANSLATORS: keep it short, this is a button
2676                 self["key_info"] = Button(_("About FritzCall"))
2677                 # TRANSLATORS: keep it short, this is a button
2678                 self["key_menu"] = Button(_("FRITZ!Box Fon Status"))
2679
2680                 self["setupActions"] = ActionMap(["ColorActions", "OkCancelActions", "MenuActions", "EPGSelectActions"],
2681                 {
2682                         "red": self.cancel,
2683                         "green": self.save,
2684                         "yellow": self.displayCalls,
2685                         "blue": self.displayPhonebook,
2686                         "cancel": self.cancel,
2687                         "ok": self.save,
2688                         "menu": self.menu,
2689                         "info": self.about,
2690                 }, - 2)
2691
2692                 # TRANSLATORS: keep it short, this is a help text
2693                 self.helpList.append((self["setupActions"], "ColorActions", [("red", _("quit"))]))
2694                 # TRANSLATORS: keep it short, this is a help text
2695                 self.helpList.append((self["setupActions"], "ColorActions", [("green", _("save and quit"))]))
2696                 # TRANSLATORS: keep it short, this is a help text
2697                 self.helpList.append((self["setupActions"], "ColorActions", [("yellow", _("display calls"))]))
2698                 # TRANSLATORS: keep it short, this is a help text
2699                 self.helpList.append((self["setupActions"], "ColorActions", [("blue", _("display phonebook"))]))
2700                 # TRANSLATORS: keep it short, this is a help text
2701                 self.helpList.append((self["setupActions"], "OkCancelActions", [("ok", _("save and quit"))]))
2702                 # TRANSLATORS: keep it short, this is a help text
2703                 self.helpList.append((self["setupActions"], "OkCancelActions", [("cancel", _("quit"))]))
2704                 # TRANSLATORS: keep it short, this is a help text
2705                 self.helpList.append((self["setupActions"], "MenuActions", [("menu", _("FRITZ!Box Fon Status"))]))
2706                 # TRANSLATORS: keep it short, this is a help text
2707                 self.helpList.append((self["setupActions"], "EPGSelectActions", [("info", _("About FritzCall"))]))
2708
2709                 ConfigListScreen.__init__(self, self.list, session=session)
2710
2711                 # get new list of locations for PhoneBook.txt
2712                 self._mountedDevs = getMountedDevs()
2713                 self.createSetup()
2714                 self.onLayoutFinish.append(self.setWindowTitle)
2715
2716         def setWindowTitle(self):
2717                 # TRANSLATORS: this is a window title.
2718                 self.setTitle(_("FritzCall Setup") + " (" + "$Revision: 640 $"[1: - 1] + "$Date: 2011-01-15 16:17:12 +0100 (Sa, 15. Jan 2011) $"[7:23] + ")")
2719
2720         def keyLeft(self):
2721                 ConfigListScreen.keyLeft(self)
2722                 self.createSetup()
2723
2724         def keyRight(self):
2725                 ConfigListScreen.keyRight(self)
2726                 self.createSetup()
2727
2728         def createSetup(self):
2729                 self.list = [ ]
2730                 self.list.append(getConfigListEntry(_("Call monitoring"), config.plugins.FritzCall.enable))
2731                 if config.plugins.FritzCall.enable.value:
2732                         self.list.append(getConfigListEntry(_("FRITZ!Box FON address (Name or IP)"), config.plugins.FritzCall.hostname))
2733
2734                         self.list.append(getConfigListEntry(_("Show after Standby"), config.plugins.FritzCall.afterStandby))
2735
2736                         self.list.append(getConfigListEntry(_("Show only calls for specific MSN"), config.plugins.FritzCall.filter))
2737                         if config.plugins.FritzCall.filter.value:
2738                                 self.list.append(getConfigListEntry(_("MSN to show (separated by ,)"), config.plugins.FritzCall.filtermsn))
2739                                 self.list.append(getConfigListEntry(_("Filter also list of calls"), config.plugins.FritzCall.filterCallList))
2740                         self.list.append(getConfigListEntry(_("Mute on call"), config.plugins.FritzCall.muteOnCall))
2741
2742                         self.list.append(getConfigListEntry(_("Show Outgoing Calls"), config.plugins.FritzCall.showOutgoing))
2743                         # not only for outgoing: config.plugins.FritzCall.showOutgoing.value:
2744                         self.list.append(getConfigListEntry(_("Areacode to add to calls without one (if necessary)"), config.plugins.FritzCall.prefix))
2745                         self.list.append(getConfigListEntry(_("Timeout for Call Notifications (seconds)"), config.plugins.FritzCall.timeout))
2746                         self.list.append(getConfigListEntry(_("Reverse Lookup Caller ID (select country below)"), config.plugins.FritzCall.lookup))
2747                         if config.plugins.FritzCall.lookup.value:
2748                                 self.list.append(getConfigListEntry(_("Country"), config.plugins.FritzCall.country))
2749
2750                         # TODO: make password unreadable?
2751                         self.list.append(getConfigListEntry(_("Password Accessing FRITZ!Box"), config.plugins.FritzCall.password))
2752                         self.list.append(getConfigListEntry(_("Extension number to initiate call on"), config.plugins.FritzCall.extension))
2753                         self.list.append(getConfigListEntry(_("Read PhoneBook from FRITZ!Box"), config.plugins.FritzCall.fritzphonebook))
2754                         if config.plugins.FritzCall.fritzphonebook.value:
2755                                 self.list.append(getConfigListEntry(_("Append type of number"), config.plugins.FritzCall.showType))
2756                                 self.list.append(getConfigListEntry(_("Append shortcut number"), config.plugins.FritzCall.showShortcut))
2757                                 self.list.append(getConfigListEntry(_("Append vanity name"), config.plugins.FritzCall.showVanity))
2758
2759                         self.list.append(getConfigListEntry(_("Use internal PhoneBook"), config.plugins.FritzCall.phonebook))
2760                         if config.plugins.FritzCall.phonebook.value:
2761                                 if config.plugins.FritzCall.phonebookLocation.value in self._mountedDevs:
2762                                         config.plugins.FritzCall.phonebookLocation.setChoices(self._mountedDevs, config.plugins.FritzCall.phonebookLocation.value)
2763                                 else:
2764                                         config.plugins.FritzCall.phonebookLocation.setChoices(self._mountedDevs)
2765                                 path = config.plugins.FritzCall.phonebookLocation.value
2766                                 # check whether we can write to PhoneBook.txt
2767                                 if os.path.exists(os.path.join(path[0], "PhoneBook.txt")):
2768                                         if not os.access(os.path.join(path[0], "PhoneBook.txt"), os.W_OK):
2769                                                 debug("[FritzCallSetup] createSetup: %s/PhoneBook.txt not writable, resetting to default" %(path[0]))
2770                                                 config.plugins.FritzCall.phonebookLocation.setChoices(self._mountedDevs)
2771                                 elif not (os.path.isdir(path[0]) and os.access(path[0], os.W_OK|os.X_OK)):
2772                                         debug("[FritzCallSetup] createSetup: directory %s not writable, resetting to default" %(path[0]))
2773                                         config.plugins.FritzCall.phonebookLocation.setChoices(self._mountedDevs)
2774
2775                                 self.list.append(getConfigListEntry(_("PhoneBook Location"), config.plugins.FritzCall.phonebookLocation))
2776                                 if config.plugins.FritzCall.lookup.value:
2777                                         self.list.append(getConfigListEntry(_("Automatically add new Caller to PhoneBook"), config.plugins.FritzCall.addcallers))
2778
2779                         self.list.append(getConfigListEntry(_("Strip Leading 0"), config.plugins.FritzCall.internal))
2780                         # self.list.append(getConfigListEntry(_("Default display mode for FRITZ!Box calls"), config.plugins.FritzCall.fbfCalls))
2781                         self.list.append(getConfigListEntry(_("Display connection infos"), config.plugins.FritzCall.connectionVerbose))
2782                         self.list.append(getConfigListEntry(_("Debug"), config.plugins.FritzCall.debug))
2783
2784                 self["config"].list = self.list
2785                 self["config"].l.setList(self.list)
2786
2787         def save(self):
2788 #               debug("[FritzCallSetup] save"
2789                 for x in self["config"].list:
2790                         x[1].save()
2791                 if config.plugins.FritzCall.phonebookLocation.isChanged():
2792                         global phonebook
2793                         phonebook = FritzCallPhonebook()
2794                 if fritz_call:
2795                         if config.plugins.FritzCall.enable.value:
2796                                 fritz_call.connect()
2797                         else:
2798                                 fritz_call.shutdown()
2799                 self.close()
2800
2801         def cancel(self):
2802 #               debug("[FritzCallSetup] cancel"
2803                 for x in self["config"].list:
2804                         x[1].cancel()
2805                 self.close()
2806
2807         def displayCalls(self):
2808                 if config.plugins.FritzCall.enable.value:
2809                         if fritzbox:
2810                                 self.session.open(FritzDisplayCalls)
2811                         else:
2812                                 self.session.open(MessageBox, _("Cannot get calls from FRITZ!Box"), type=MessageBox.TYPE_INFO)
2813                 else:
2814                         self.session.open(MessageBox, _("Plugin not active"), type=MessageBox.TYPE_INFO)
2815
2816         def displayPhonebook(self):
2817                 if phonebook:
2818                         if config.plugins.FritzCall.enable.value:
2819                                 self.session.open(phonebook.FritzDisplayPhonebook)
2820                         else:
2821                                 self.session.open(MessageBox, _("Plugin not active"), type=MessageBox.TYPE_INFO)
2822                 else:
2823                         self.session.open(MessageBox, _("No phonebook"), type=MessageBox.TYPE_INFO)
2824
2825         def about(self):
2826                 self.session.open(FritzAbout)
2827
2828         def menu(self):
2829                 if config.plugins.FritzCall.enable.value:
2830                         if fritzbox and fritzbox.info:
2831                                 self.session.open(FritzMenu)
2832                         else:
2833                                 self.session.open(MessageBox, _("Cannot get infos from FRITZ!Box"), type=MessageBox.TYPE_INFO)
2834                 else:
2835                         self.session.open(MessageBox, _("Plugin not active"), type=MessageBox.TYPE_INFO)
2836
2837 standbyMode = False
2838
2839 class FritzCallList:
2840         def __init__(self):
2841                 self.callList = [ ]
2842         
2843         def add(self, event, date, number, caller, phone):
2844                 debug("[FritzCallList] add: %s %s" % (number, caller))
2845                 if len(self.callList) > 10:
2846                         if self.callList[0] != "Start":
2847                                 self.callList[0] = "Start"
2848                         del self.callList[1]
2849
2850                 self.callList.append((event, number, date, caller, phone))
2851         
2852         def display(self):
2853                 debug("[FritzCallList] display")
2854                 global standbyMode
2855                 standbyMode = False
2856                 # Standby.inStandby.onClose.remove(self.display) object does not exist anymore...
2857                 # build screen from call list
2858                 text = "\n"
2859
2860                 if not self.callList:
2861                         text = _("no calls") 
2862                 else:
2863                         if self.callList[0] == "Start":
2864                                 text = text + _("Last 10 calls:\n")
2865                                 del self.callList[0]
2866         
2867                         for call in self.callList:
2868                                 (event, number, date, caller, phone) = call
2869                                 if event == "RING":
2870                                         direction = "->"
2871                                 else:
2872                                         direction = "<-"
2873         
2874                                 # shorten the date info
2875                                 date = date[:6] + date[9:14]
2876         
2877                                 # our phone could be of the form "0123456789 (home)", then we only take "home"
2878                                 oBrack = phone.find('(')
2879                                 cBrack = phone.find(')')
2880                                 if oBrack != -1 and cBrack != -1:
2881                                         phone = phone[oBrack+1:cBrack]
2882         
2883                                 # should not happen, for safety reasons
2884                                 if not caller:
2885                                         caller = _("UNKNOWN")
2886                                 
2887                                 #  if we have an unknown number, show the number
2888                                 if caller == _("UNKNOWN") and number != "":
2889                                         caller = number
2890                                 else:
2891                                         # strip off the address part of the remote caller/callee, if there is any
2892                                         nl = caller.find('\n')
2893                                         if nl != -1:
2894                                                 caller = caller[:nl]
2895                                         elif caller[0] == '[' and caller[-1] == ']':
2896                                                 # that means, we've got an unknown number with a city added from avon.dat 
2897                                                 if (len(number) + 1 + len(caller) + len(phone)) <= 40:
2898                                                         caller = number + ' ' + caller
2899                                                 else:
2900                                                         caller = number
2901         
2902                                 while (len(caller) + len(phone)) > 40:
2903                                         if len(caller) > len(phone):
2904                                                 caller = caller[: - 1]
2905                                         else:
2906                                                 phone = phone[: - 1]
2907         
2908                                 text = text + "%s %s %s %s\n" % (date, caller, direction, phone)
2909                                 debug("[FritzCallList] display: '%s %s %s %s'" % (date, caller, direction, phone))
2910
2911                 # display screen
2912                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO)
2913                 # TODO please HELP: from where can I get a session?
2914                 # my_global_session.open(FritzDisplayCalls, text)
2915                 self.callList = [ ]
2916
2917 callList = FritzCallList()
2918
2919 def findFace(number, name):
2920         debug("[FritzCall] findFace number/name: %s/%s" % (number, name))
2921         if name:
2922                 sep = name.find(',')
2923                 if sep != -1:
2924                         name = name[:sep]
2925                 sep = name.find('\n')
2926                 if sep != -1:
2927                         name = name[:sep]
2928         else:
2929                 name = _("UNKNOWN")
2930
2931         facesDir = os.path.join(config.plugins.FritzCall.phonebookLocation.value, "FritzCallFaces")
2932         numberFile = os.path.join(facesDir, number)
2933         nameFile = os.path.join(facesDir, name)
2934         facesFile = ""
2935         if number and os.path.exists(numberFile):
2936                 facesFile = numberFile
2937         elif number and os.path.exists(numberFile + ".png"):
2938                 facesFile = numberFile + ".png"
2939         elif number and os.path.exists(numberFile + ".PNG"):
2940                 facesFile = numberFile + ".PNG"
2941         elif name and os.path.exists(nameFile + ".png"):
2942                 facesFile = nameFile + ".png"
2943         elif name and os.path.exists(nameFile + ".PNG"):
2944                 facesFile = nameFile + ".PNG"
2945         else:
2946                 facesFile = resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/icons/input_info.png")
2947
2948         debug("[FritzCall] findFace result: %s" % (facesFile))
2949         return facesFile
2950
2951 class MessageBoxPixmap(Screen):
2952         def __init__(self, session, text, number = "", name = "", timeout = -1):
2953                 self.skin = """
2954         <screen name="MessageBoxPixmap" position="center,center" size="600,10" title="MessageBoxPixmap">
2955                 <widget name="text" position="115,8" size="520,0" font="Regular;%d" />
2956                 <widget name="InfoPixmap" pixmap="%s" position="5,5" size="100,100" alphatest="on" />
2957         </screen>
2958                 """ % (
2959                         # scaleH(350, 60), scaleV(175, 245),
2960                         scaleV(24, 22), resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/icons/input_info.png")
2961                         )
2962                 debug("[FritzCall] MessageBoxPixmap number: %s" % number)
2963                 Screen.__init__(self, session)
2964                 # MessageBox.__init__(self, session, text, type=MessageBox.TYPE_INFO, timeout=timeout)
2965                 self["text"] = Label(text)
2966                 self["InfoPixmap"] = Pixmap()
2967                 self._session = session
2968                 self._number = number
2969                 self._name = name
2970                 self._timerRunning = False
2971                 self._timer = None
2972                 self._timeout = timeout
2973                 self._origTitle = None
2974                 self._initTimeout()
2975                 self.onLayoutFinish.append(self._finishLayout)
2976                 self["actions"] = ActionMap(["OkCancelActions"],
2977                 {
2978                         "cancel": self._exit,
2979                         "ok": self._exit, }, - 2)
2980
2981         def _finishLayout(self):
2982                 # pylint: disable=W0142
2983                 debug("[FritzCall] MessageBoxPixmap/setInfoPixmap number: %s/%s" % (self._number, self._name))
2984
2985                 faceFile = findFace(self._number, self._name)
2986                 picPixmap = LoadPixmap(faceFile)
2987                 if not picPixmap:       # that means most probably, that the picture is not 8 bit...
2988                         Notifications.AddNotification(MessageBox, _("Found picture\n\n%s\n\nBut did not load. Probably not PNG, 8-bit") %faceFile, type = MessageBox.TYPE_ERROR)
2989                         picPixmap = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/icons/input_error.png"))
2990                 picSize = picPixmap.size()
2991                 self["InfoPixmap"].instance.setPixmap(picPixmap)
2992
2993                 # recalculate window size
2994                 textSize = self["text"].getSize()
2995                 textSize = (textSize[0]+20, textSize[1]+20) # don't know, why, but size is too small
2996                 textSize = eSize(*textSize)
2997                 width = max(scaleH(600, 280), picSize.width() + textSize.width() + 30)
2998                 height = max(scaleV(300, 250), picSize.height()+10, textSize.height()+10)
2999                 wSize = (width, height)
3000                 wSize = eSize(*wSize)
3001
3002                 # center the smaller vertically
3003                 hGap = (width-picSize.width()-textSize.width())/3
3004                 picPos = (hGap, (height-picSize.height())/2+1)
3005                 textPos = (hGap+picSize.width()+hGap, (height-textSize.height())/2+1)
3006
3007                 # resize screen
3008                 self.instance.resize(wSize)
3009                 # resize text
3010                 self["text"].instance.resize(textSize)
3011                 # resize pixmap
3012                 self["InfoPixmap"].instance.resize(picSize)
3013                 # move text
3014                 self["text"].instance.move(ePoint(*textPos))
3015                 # move pixmap
3016                 self["InfoPixmap"].instance.move(ePoint(*picPos))
3017                 # center window
3018                 self.instance.move(ePoint((DESKTOP_WIDTH-wSize.width())/2, (DESKTOP_HEIGHT-wSize.height())/2))
3019
3020         def _initTimeout(self):
3021                 if self._timeout > 0:
3022                         self._timer = eTimer()
3023                         self._timer.callback.append(self._timerTick)
3024                         self.onExecBegin.append(self._startTimer)
3025                         self._origTitle = None
3026                         if self.execing:
3027                                 self._timerTick()
3028                         else:
3029                                 self.onShown.append(self.__onShown)
3030                         self._timerRunning = True
3031                 else:
3032                         self._timerRunning = False
3033
3034         def __onShown(self):
3035                 self.onShown.remove(self.__onShown)
3036                 self._timerTick()
3037
3038         def _startTimer(self):
3039                 self._timer.start(1000)
3040
3041 #===============================================================================
3042 #       def stopTimer(self):
3043 #               if self._timerRunning:
3044 #                       del self._timer
3045 #                       self.setTitle(self._origTitle)
3046 #                       self._timerRunning = False
3047 #===============================================================================
3048
3049         def _timerTick(self):
3050                 if self.execing:
3051                         self._timeout -= 1
3052                         if self._origTitle is None:
3053                                 self._origTitle = self.instance.getTitle()
3054                         self.setTitle(self._origTitle + " (" + str(self._timeout) + ")")
3055                         if self._timeout == 0:
3056                                 self._timer.stop()
3057                                 self._timerRunning = False
3058                                 self._exit()
3059
3060         def _exit(self):
3061                 self.close()
3062
3063 mutedOnConnID = None
3064 def notifyCall(event, date, number, caller, phone, connID):
3065         if Standby.inStandby is None or config.plugins.FritzCall.afterStandby.value == "each":
3066                 if event == "RING":
3067                         global mutedOnConnID
3068                         if config.plugins.FritzCall.muteOnCall.value and not mutedOnConnID:
3069                                 debug("[FritzCall] mute on connID: %s" % connID)
3070                                 mutedOnConnID = connID
3071                                 # eDVBVolumecontrol.getInstance().volumeMute() # with this, we get no mute icon...
3072                                 if not eDVBVolumecontrol.getInstance().isMuted():
3073                                         globalActionMap.actions["volumeMute"]()
3074                         text = _("Incoming Call on %(date)s at %(time)s from\n---------------------------------------------\n%(number)s\n%(caller)s\n---------------------------------------------\nto: %(phone)s") % { 'date':date[:8], 'time':date[9:], 'number':number, 'caller':caller, 'phone':phone }
3075                 else:
3076                         text = _("Outgoing Call on %(date)s at %(time)s to\n---------------------------------------------\n%(number)s\n%(caller)s\n---------------------------------------------\nfrom: %(phone)s") % { 'date':date[:8], 'time':date[9:], 'number':number, 'caller':caller, 'phone':phone }
3077                 debug("[FritzCall] notifyCall:\n%s" % text)
3078                 # Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
3079                 Notifications.AddNotification(MessageBoxPixmap, text, number=number, name=caller, timeout=config.plugins.FritzCall.timeout.value)
3080         elif config.plugins.FritzCall.afterStandby.value == "inList":
3081                 #
3082                 # if not yet done, register function to show call list
3083                 global standbyMode
3084                 if not standbyMode :
3085                         standbyMode = True
3086                         Standby.inStandby.onHide.append(callList.display) #@UndefinedVariable
3087                 # add text/timeout to call list
3088                 callList.add(event, date, number, caller, phone)
3089                 debug("[FritzCall] notifyCall: added to callList")
3090         else: # this is the "None" case
3091                 debug("[FritzCall] notifyCall: standby and no show")
3092
3093         # user exit
3094         # call FritzCallserAction.sh in the same dir as Phonebook.txt with the following parameters:
3095         # event: "RING" (incomning) or "CALL" (outgoing)
3096         # date of event, format: "dd.mm.yy hh.mm.ss"
3097         # telephone number which is calling/is called
3098         # caller's name and address, format Name\n Street\n ZIP City
3099         # line/number which is called/which is used for calling
3100         userActionScript = os.path.join(config.plugins.FritzCall.phonebookLocation.value, "FritzCallUserAction.sh")
3101         if os.path.exists(userActionScript) and os.access(userActionScript, os.X_OK):
3102                 cmd = userActionScript + ' "' + event + '" "' + date + '" "' + number + '" "' + caller + '" "' + phone + '"'
3103                 debug("[FritzCall] notifyCall: calling: %s" % cmd)
3104                 os.system(cmd)
3105
3106
3107 #===============================================================================
3108 #               We need a separate class for each invocation of reverseLookup to retain
3109 #               the necessary data for the notification
3110 #===============================================================================
3111
3112 countries = { }
3113 reverselookupMtime = 0
3114
3115 class FritzReverseLookupAndNotifier:
3116         def __init__(self, event, number, caller, phone, date, connID):
3117                 '''
3118                 
3119                 Initiate a reverse lookup for the given number in the configured country
3120                 
3121                 @param event: CALL or RING
3122                 @param number: number to be looked up
3123                 @param caller: caller including name and address
3124                 @param phone: Number (and name) of or own phone
3125                 @param date: date of call
3126                 '''
3127                 debug("[FritzReverseLookupAndNotifier] reverse Lookup for %s!" % number)
3128                 self.event = event
3129                 self.number = number
3130                 self.caller = caller
3131                 self.phone = phone
3132                 self.date = date
3133                 self.connID = connID
3134
3135                 if number[0] != "0":
3136                         self.notifyAndReset(number, caller)
3137                         return
3138
3139                 ReverseLookupAndNotifier(number, self.notifyAndReset, "UTF-8", config.plugins.FritzCall.country.value)
3140
3141         def notifyAndReset(self, number, caller):
3142                 '''
3143                 
3144                 this gets called with the result of the reverse lookup
3145                 
3146                 @param number: number
3147                 @param caller: name and address of remote. it comes in with name, address and city separated by commas
3148                 '''
3149                 debug("[FritzReverseLookupAndNotifier] got: " + caller)
3150                 self.number = number
3151 #===============================================================================
3152 #               if not caller and os.path.exists(config.plugins.FritzCall.phonebookLocation.value + "/PhoneBook.csv"):
3153 #                       caller = FritzOutlookCSV.findNumber(number, config.plugins.FritzCall.phonebookLocation.value + "/PhoneBook.csv") #@UndefinedVariable
3154 #                       debug("[FritzReverseLookupAndNotifier] got from Outlook csv: " + caller)
3155 #===============================================================================
3156 #===============================================================================
3157 #               if not caller and os.path.exists(config.plugins.FritzCall.phonebookLocation.value + "/PhoneBook.ldif"):
3158 #                       caller = FritzLDIF.findNumber(number, open(config.plugins.FritzCall.phonebookLocation.value + "/PhoneBook.ldif"))
3159 #                       debug("[FritzReverseLookupAndNotifier] got from ldif: " + caller)
3160 #===============================================================================
3161
3162                 name = handleReverseLookupResult(caller)
3163                 if name:
3164                         self.caller = name.replace(", ", "\n").replace('#','')
3165                         # TODO: I don't know, why we store only for incoming calls...
3166                         # if self.number != 0 and config.plugins.FritzCall.addcallers.value and self.event == "RING":
3167                         if self.number != 0 and config.plugins.FritzCall.addcallers.value:
3168                                 debug("[FritzReverseLookupAndNotifier] add to phonebook")
3169                                 phonebook.add(self.number, self.caller)
3170                 else:
3171                         name = resolveNumberWithAvon(self.number, config.plugins.FritzCall.country.value)
3172                         if not name:
3173                                 self.caller = _("UNKNOWN")
3174                         else:
3175                                 self.caller = name
3176                 notifyCall(self.event, self.date, self.number, self.caller, self.phone, self.connID)
3177                 # kill that object...
3178
3179 class FritzProtocol(LineReceiver):
3180         def __init__(self):
3181                 debug("[FritzProtocol] " + "$Revision: 640 $"[1:-1]     + "$Date: 2011-01-15 16:17:12 +0100 (Sa, 15. Jan 2011) $"[7:23] + " starting")
3182                 global mutedOnConnID
3183                 mutedOnConnID = None
3184                 self.number = '0'
3185                 self.caller = None
3186                 self.phone = None
3187                 self.date = '0'
3188                 self.event = None
3189                 self.connID = None
3190
3191         def resetValues(self):
3192                 debug("[FritzProtocol] resetValues")
3193                 self.number = '0'
3194                 self.caller = None
3195                 self.phone = None
3196                 self.date = '0'
3197                 self.event = None
3198                 self.connID = None
3199
3200         def notifyAndReset(self):
3201                 notifyCall(self.event, self.date, self.number, self.caller, self.phone, self.connID)
3202                 self.resetValues()
3203
3204         def lineReceived(self, line):
3205                 debug("[FritzProtocol] lineReceived: %s" % line)
3206 #15.07.06 00:38:54;CALL;1;4;<from/our msn>;<to/extern>;
3207 #15.07.06 00:38:58;DISCONNECT;1;0;
3208 #15.07.06 00:39:22;RING;0;<from/extern>;<to/our msn>;
3209 #15.07.06 00:39:27;DISCONNECT;0;0;
3210                 anEvent = line.split(';')
3211                 (self.date, self.event) = anEvent[0:2]
3212                 self.connID = anEvent[2]
3213
3214                 filtermsns = config.plugins.FritzCall.filtermsn.value.split(",")
3215                 for i in range(len(filtermsns)):
3216                         filtermsns[i] = filtermsns[i].strip()
3217
3218                 # debug("[FritzProtocol] Volcontrol dir: %s" % dir(eDVBVolumecontrol.getInstance()))
3219                 # debug("[FritzCall] unmute on connID: %s?" %self.connID)
3220                 global mutedOnConnID
3221                 if self.event == "DISCONNECT" and config.plugins.FritzCall.muteOnCall.value and mutedOnConnID == self.connID:
3222                         debug("[FritzCall] unmute on connID: %s!" % self.connID)
3223                         mutedOnConnID = None
3224                         # eDVBVolumecontrol.getInstance().volumeUnMute()
3225                         if eDVBVolumecontrol.getInstance().isMuted():
3226                                 globalActionMap.actions["volumeMute"]()
3227                 # not supported so far, because, taht would mean muting on EVERY connect, regardless of RING or CALL or filter active
3228                 #=======================================================================
3229                 # elif self.event == "CONNECT" and config.plugins.FritzCall.muteOnCall.value == "connect":
3230                 #       debug("[FritzCall] mute on connID: %s" % self.connID)
3231                 #       mutedOnConnID = self.connID
3232                 #       # eDVBVolumecontrol.getInstance().volumeMute() # with this, we get no mute icon...
3233                 #       if not eDVBVolumecontrol.getInstance().isMuted():
3234                 #               globalActionMap.actions["volumeMute"]()
3235                 #=======================================================================
3236                 elif self.event == "RING" or (self.event == "CALL" and config.plugins.FritzCall.showOutgoing.value):
3237                         phone = anEvent[4]
3238
3239                         if self.event == "RING":
3240                                 number = anEvent[3] 
3241                                 if fritzbox and number in fritzbox.blacklist[0]:
3242                                         debug("[FritzProtocol] lineReceived phone: '''%s''' blacklisted number: '''%s'''" % (phone, number))
3243                                         return 
3244                         else:
3245                                 number = anEvent[5]
3246                                 if number in fritzbox.blacklist[1]:
3247                                         debug("[FritzProtocol] lineReceived phone: '''%s''' blacklisted number: '''%s'''" % (phone, number))
3248                                         return 
3249
3250                         debug("[FritzProtocol] lineReceived phone: '''%s''' number: '''%s'''" % (phone, number))
3251
3252                         if not (config.plugins.FritzCall.filter.value and phone not in filtermsns):
3253                                 debug("[FritzProtocol] lineReceived no filter hit")
3254                                 if phone:
3255                                         phonename = phonebook.search(phone)                # do we have a name for the number of our side?
3256                                         if phonename:
3257                                                 self.phone = "%s (%s)" % (phone, phonename)
3258                                         else:
3259                                                 self.phone = phone
3260                                 else:
3261                                         self.phone = _("UNKNOWN")
3262
3263                                 if not number:
3264                                         debug("[FritzProtocol] lineReceived: no number")
3265                                         self.number = _("number suppressed")
3266                                         self.caller = _("UNKNOWN")
3267                                 else:
3268                                         if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0] == "0":
3269                                                 debug("[FritzProtocol] lineReceived: strip leading 0")
3270                                                 self.number = number[1:]
3271                                         else:
3272                                                 self.number = number
3273                                                 if self.event == "CALL" and self.number[0] != '0':                                        # should only happen for outgoing
3274                                                         debug("[FritzProtocol] lineReceived: add local prefix")
3275                                                         self.number = config.plugins.FritzCall.prefix.value + self.number
3276         
3277                                         # strip CbC prefixes
3278                                         if self.event == "CALL":
3279                                                 number = stripCbCPrefix(self.number, config.plugins.FritzCall.country.value)
3280         
3281                                         debug("[FritzProtocol] lineReceived phonebook.search: %s" % self.number)
3282                                         self.caller = phonebook.search(self.number)
3283                                         debug("[FritzProtocol] lineReceived phonebook.search reault: %s" % self.caller)
3284                                         if not self.caller:
3285                                                 if config.plugins.FritzCall.lookup.value:
3286                                                         FritzReverseLookupAndNotifier(self.event, self.number, self.caller, self.phone, self.date, self.connID)
3287                                                         return                                                  # reverselookup is supposed to handle the message itself
3288                                                 else:
3289                                                         self.caller = _("UNKNOWN")
3290
3291                                 self.notifyAndReset()
3292
3293 class FritzClientFactory(ReconnectingClientFactory):
3294         initialDelay = 20
3295         maxDelay = 30
3296
3297         def __init__(self):
3298                 self.hangup_ok = False
3299         def startedConnecting(self, connector): #@UnusedVariable # pylint: disable=W0613
3300                 if config.plugins.FritzCall.connectionVerbose.value:
3301                         Notifications.AddNotification(MessageBox, _("Connecting to FRITZ!Box..."), type=MessageBox.TYPE_INFO, timeout=2)
3302
3303         def buildProtocol(self, addr): #@UnusedVariable # pylint: disable=W0613
3304                 global fritzbox, phonebook
3305                 if config.plugins.FritzCall.connectionVerbose.value:
3306                         Notifications.AddNotification(MessageBox, _("Connected to FRITZ!Box!"), type=MessageBox.TYPE_INFO, timeout=4)
3307                 self.resetDelay()
3308                 initDebug()
3309                 initCbC()
3310                 initAvon()
3311                 fritzbox = FritzCallFBF()
3312                 phonebook = FritzCallPhonebook()
3313                 return FritzProtocol()
3314
3315         def clientConnectionLost(self, connector, reason):
3316                 global fritzbox
3317                 if not self.hangup_ok and config.plugins.FritzCall.connectionVerbose.value:
3318                         Notifications.AddNotification(MessageBox, _("Connection to FRITZ!Box! lost\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
3319                 ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
3320                 # config.plugins.FritzCall.enable.value = False
3321                 fritzbox = None
3322
3323         def clientConnectionFailed(self, connector, reason):
3324                 global fritzbox
3325                 if config.plugins.FritzCall.connectionVerbose.value:
3326                         Notifications.AddNotification(MessageBox, _("Connecting to FRITZ!Box failed\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.FritzCall.timeout.value)
3327                 ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
3328                 # config.plugins.FritzCall.enable.value = False
3329                 fritzbox = None
3330                 
3331 class FritzCall:
3332         def __init__(self):
3333                 self.dialog = None
3334                 self.desc = None
3335                 if config.plugins.FritzCall.enable.value:
3336                         self.connect()
3337
3338         def connect(self):
3339                 self.abort()
3340                 if config.plugins.FritzCall.enable.value:
3341                         fact = FritzClientFactory()
3342                         self.desc = (fact, reactor.connectTCP(config.plugins.FritzCall.hostname.value, 1012, fact)) #@UndefinedVariable # pylint: disable=E1101
3343
3344         def shutdown(self):
3345                 self.abort()
3346
3347         def abort(self):
3348                 if self.desc is not None:
3349                         self.desc[0].hangup_ok = True
3350                         self.desc[0].stopTrying()
3351                         self.desc[1].disconnect()
3352                         self.desc = None
3353
3354 def displayCalls(session, servicelist=None): #@UnusedVariable # pylint: disable=W0613
3355         if config.plugins.FritzCall.enable.value:
3356                 if fritzbox:
3357                         session.open(FritzDisplayCalls)
3358                 else:
3359                         Notifications.AddNotification(MessageBox, _("Cannot get calls from FRITZ!Box"), type=MessageBox.TYPE_INFO)
3360         else:
3361                 Notifications.AddNotification(MessageBox, _("Plugin not active"), type=MessageBox.TYPE_INFO)
3362
3363 def displayPhonebook(session, servicelist=None): #@UnusedVariable # pylint: disable=W0613
3364         if phonebook:
3365                 if config.plugins.FritzCall.enable.value:
3366                         session.open(phonebook.FritzDisplayPhonebook)
3367                 else:
3368                         Notifications.AddNotification(MessageBox, _("Plugin not active"), type=MessageBox.TYPE_INFO)
3369         else:
3370                 Notifications.AddNotification(MessageBox, _("No phonebook"), type=MessageBox.TYPE_INFO)
3371
3372 def displayFBFStatus(session, servicelist=None): #@UnusedVariable # pylint: disable=W0613
3373         if config.plugins.FritzCall.enable.value:
3374                 if fritzbox and fritzbox.info:
3375                         session.open(FritzMenu)
3376                 else:
3377                         Notifications.AddNotification(MessageBox, _("Cannot get infos from FRITZ!Box"), type=MessageBox.TYPE_INFO)
3378         else:
3379                 Notifications.AddNotification(MessageBox, _("Plugin not active"), type=MessageBox.TYPE_INFO)
3380
3381 def main(session, servicelist=None):
3382         session.open(FritzCallSetup)
3383
3384 fritz_call = None
3385
3386 def autostart(reason, **kwargs):
3387         global fritz_call
3388
3389         # ouch, this is a hack
3390         if kwargs.has_key("session"):
3391                 global my_global_session
3392                 my_global_session = kwargs["session"]
3393                 return
3394
3395         debug("[FRITZ!Call] - Autostart")
3396         if reason == 0:
3397                 fritz_call = FritzCall()
3398         elif reason == 1:
3399                 fritz_call.shutdown()
3400                 fritz_call = None
3401
3402 def Plugins(**kwargs): #@UnusedVariable # pylint: disable=W0613,C0103
3403         what = _("Display FRITZ!box-Fon calls on screen")
3404         what_calls = _("Phone calls")
3405         what_phonebook = _("Phonebook")
3406         what_status = _("FRITZ!Box Fon Status")
3407         return [ PluginDescriptor(name="FritzCall", description=what, where=PluginDescriptor.WHERE_PLUGINMENU, icon="plugin.png", fnc=main),
3408                 PluginDescriptor(name=what_calls, description=what_calls, where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=displayCalls),
3409                 PluginDescriptor(name=what_phonebook, description=what_phonebook, where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=displayPhonebook),
3410                 PluginDescriptor(name=what_status, description=what_status, where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=displayFBFStatus),
3411                 PluginDescriptor(where=[PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc=autostart) ]