[MerlinSkinThemes] - add support for layouts
[enigma2-plugins.git] / remotetimer / src / plugin.py
1 # -*- coding: utf-8 -*-
2 #===============================================================================
3 # Remote Timer Setup by Homey
4 #
5 # This is free software; you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation; either version 2, or (at your option) any later
8 # version.
9 #
10 # Copyright (C) 2009 by nixkoenner@newnigma2.to
11 # http://newnigma2.to
12 #
13 # License: GPL
14 #
15 # $Id$
16 #===============================================================================
17
18 from Plugins.Plugin import PluginDescriptor
19 from Screens.Screen import Screen
20
21 from Components.ActionMap import ActionMap
22 from Components.Button import Button
23 from Components.Label import Label
24 from Components.TimerList import TimerList
25
26 from Components.ConfigList import ConfigList, ConfigListScreen
27 from Components.config import getConfigListEntry, config, \
28         ConfigSubsection, ConfigText, ConfigIP, ConfigYesNo, \
29         ConfigPassword, ConfigNumber, KEY_LEFT, KEY_RIGHT, KEY_0
30
31 from Screens.TimerEntry import TimerEntry
32 from Screens.MessageBox import MessageBox
33 from RecordTimer import AFTEREVENT
34
35 from enigma import eEPGCache, eServiceReference
36
37 from Tools.BoundFunction import boundFunction
38
39 from twisted.web.client import getPage
40 from xml.etree.cElementTree import fromstring as cElementTree_fromstring
41 from base64 import encodestring
42
43 from urllib import quote_plus
44 #------------------------------------------------------------------------------------------
45
46 config.plugins.remoteTimer = ConfigSubsection()
47 config.plugins.remoteTimer.httphost = ConfigText(default = "" , fixed_size = False)
48 config.plugins.remoteTimer.httpip = ConfigIP(default = [0, 0, 0, 0])
49 config.plugins.remoteTimer.httpport = ConfigNumber(default = 80)
50 config.plugins.remoteTimer.username = ConfigText(default = "root", fixed_size = False)
51 config.plugins.remoteTimer.password = ConfigPassword(default = "", fixed_size = False)
52
53 def localGetPage(url):
54         username = config.plugins.remoteTimer.username.value
55         password = config.plugins.remoteTimer.password.value
56         if username and password:
57                 basicAuth = encodestring(username + ':' + password)
58                 authHeader = "Basic " + basicAuth.strip()
59                 headers = {"Authorization": authHeader}
60         else:
61                 headers = {}
62
63         return getPage(url, headers = headers)
64
65 class RemoteService:
66         def __init__(self, sref, sname):
67                 self.ref = eServiceReference(sref or "")
68                 self.sname = sname
69
70         getServiceName = lambda self: self.sname
71
72 class RemoteTimerScreen(Screen):
73         skin = """
74                 <screen position="center,center" size="585,410" title="Remote-Timer digest" >
75                         <widget name="text" position="0,10" zPosition="1" size="585,20" font="Regular;20" halign="center" valign="center" />
76                         <widget name="timerlist" position="5,40" size="560,275" scrollbarMode="showOnDemand" />
77                         <ePixmap name="red" position="5,365" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
78                         <widget name="key_red" position="5,365" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
79                         <ePixmap name="green" position="150,365" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
80                         <widget name="key_green" position="150,365" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
81                         <ePixmap name="yellow" position="295,365" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
82                         <widget name="key_yellow" position="295,365" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
83                         <ePixmap name="blue" position="440,365" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
84                         <widget name="key_blue" position="440,365" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
85                 </screen>"""
86
87         def __init__(self, session):
88                 Screen.__init__(self, session)
89
90                 # XXX: any reason not to use the skin from the local screen?
91                 # is the info line really that much of a gain to lose a skinned screen...
92
93                 self["actions"] = ActionMap(["SetupActions", "ColorActions"],
94                 {
95                         "green": self.settings,
96                         "blue": self.clean,
97                         "yellow": self.delete,
98                         "cancel": self.close,
99                 }, -1)
100
101                 self["timerlist"] = TimerList([])
102                 self["key_green"] = Button(_("Settings"))
103                 self["key_blue"] = Button(_("Cleanup"))
104                 self["key_yellow"] = Button(_("Delete"))
105                 self["key_red"] = Button(_("Cancel"))
106                 self["text"] = Label("")
107
108                 remoteip = "%d.%d.%d.%d" % tuple(config.plugins.remoteTimer.httpip.value)
109                 self.remoteurl = "%s:%s" % ( remoteip, str(config.plugins.remoteTimer.httpport.value))
110
111                 self.onLayoutFinish.append(self.getInfo)
112
113         def getInfo(self, *args):
114                 try:
115                         info = _("fetching remote data...")
116                         url = "http://%s/web/timerlist" % (self.remoteurl)
117                         localGetPage(url).addCallback(self._gotPageLoad).addErrback(self.errorLoad)
118                 except:
119                         info = _("not configured yet. please do so in the settings.")
120                 self["text"].setText(info)
121
122         def _gotPageLoad(self, data):
123                 # XXX: this call is not optimized away so it is easier to extend this functionality to support other kinds of receiver
124                 self["timerlist"].l.setList(self.generateTimerE2(data))
125                 info = _("finish fetching remote data...")
126                 self["text"].setText(info)
127
128         def errorLoad(self, error):
129                 print "[RemoteTimer] errorLoad ERROR:", error.getErrorMessage()
130
131         def clean(self):
132                 try:
133                         url = "http://%s/web/timercleanup?cleanup=true" % (self.remoteurl)
134                         localGetPage(url).addCallback(self.getInfo).addErrback(self.errorLoad)
135                 except:
136                         print "[RemoteTimer] ERROR Cleanup"
137
138         def delete(self):
139                 sel = self["timerlist"].getCurrent()
140                 if not sel:
141                         return
142                 self.session.openWithCallback(
143                         self.deleteTimerConfirmed,
144                         MessageBox,
145                         _("Do you really want to delete the timer \n%s ?") % sel.name
146                 )
147
148         def deleteTimerConfirmed(self, val):
149                 if val:
150                         sel = self["timerlist"].getCurrent()
151                         if not sel:
152                                 return
153                         sref = quote_plus(sel.service_ref.ref.toString().decode('utf-8'))
154                         url = "http://%s/web/timerdelete?sRef=%s&begin=%s&end=%s" % (self.remoteurl, sref, sel.begin, sel.end)
155                         print "[RemoteTimer] debug remote", url
156                         localGetPage(url).addCallback(self.getInfo).addErrback(self.errorLoad)
157
158         def settings(self):
159                 self.session.open(RemoteTimerSetup)
160
161         def generateTimerE2(self, data):
162                 try:
163                         root = cElementTree_fromstring(data)
164                 except Exception, e:
165                         print "[RemoteTimer] error: %s", e
166                         self["text"].setText(_("error parsing incoming data."))
167                 else:
168                         return [
169                                 (
170                                         E2Timer(
171                                                 sref = str(timer.findtext("e2servicereference", '').encode("utf-8", 'ignore')),
172                                                 sname = str(timer.findtext("e2servicename", 'n/a').encode("utf-8", 'ignore')),
173                                                 name = str(timer.findtext("e2name", '').encode("utf-8", 'ignore')),
174                                                 disabled = int(timer.findtext("e2disabled", 0)),
175                                                 timebegin = int(timer.findtext("e2timebegin", 0)),
176                                                 timeend = int(timer.findtext("e2timeend", 0)),
177                                                 duration = int(timer.findtext("e2duration", 0)),
178                                                 startprepare = int(timer.findtext("e2startprepare", 0)),
179                                                 state = int(timer.findtext("e2state", 0)),
180                                                 repeated = int(timer.findtext("e2repeated", 0)),
181                                                 justplay = int(timer.findtext("e2justplay", 0)),
182                                                 eventId = int(timer.findtext("e2eit", -1)),
183                                                 afterevent = int(timer.findtext("e2afterevent", 0)),
184                                                 dirname = str(timer.findtext("e2dirname", '').encode("utf-8", 'ignore')),
185                                                 description = str(timer.findtext("e2description", '').encode("utf-8", 'ignore'))
186                                         ),
187                                         False
188                                 )
189                                 for timer in root.findall("e2timer")
190                         ]
191
192 class E2Timer:
193         def __init__(self, sref = "", sname = "", name = "", disabled = 0, \
194                         timebegin = 0, timeend = 0, duration = 0, startprepare = 0, \
195                         state = 0, repeated = 0, justplay = 0, eventId = 0, afterevent = 0, \
196                         dirname = "", description = ""):
197                 self.service_ref = RemoteService(sref, sname)
198                 self.name = name
199                 self.disabled = disabled
200                 self.begin = timebegin
201                 self.end = timeend
202                 self.duration = duration
203                 self.startprepare = startprepare
204                 self.state = state
205                 self.repeated = repeated
206                 self.justplay = justplay
207                 self.eventId = eventId
208                 self.afterevent = afterevent
209                 self.dirname = dirname
210                 self.description = description
211
212 class RemoteTimerSetup(Screen, ConfigListScreen):
213         skin = """
214                 <screen position="center,center" size="560,410" title="Settings" >
215                         <widget name="config" position="5,40" size="480,335" scrollbarMode="showOnDemand" />
216                         <ePixmap name="red" position="120,280" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
217                         <ePixmap name="green" position="320,280" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
218                         <widget name="key_red" position="120,280" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
219                         <widget name="key_green" position="320,280" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
220                 </screen>"""
221
222         def __init__(self, session):
223                 Screen.__init__(self, session)
224
225                 self["SetupActions"] = ActionMap(["SetupActions", "ColorActions"],
226                 {
227                         "ok": self.keySave,
228                         "cancel": self.Exit,
229                         "green": self.keySave,
230                 }, -1)
231
232                 self["key_red"] = Button(_("Cancel"))
233                 self["key_green"] = Button(_("OK"))
234
235                 ConfigListScreen.__init__(self, [
236                         getConfigListEntry(_("Remote Timer - Hostname"), config.plugins.remoteTimer.httphost),
237                         getConfigListEntry(_("Remote Timer - Network IP"), config.plugins.remoteTimer.httpip),
238                         getConfigListEntry(_("Remote Timer - WebIf Port"), config.plugins.remoteTimer.httpport),
239                         getConfigListEntry(_("Remote Timer - Username"), config.plugins.remoteTimer.username),
240                         getConfigListEntry(_("Remote Timer - Password"), config.plugins.remoteTimer.password),
241                 ], session)
242
243         def keySave(self):
244                 config.plugins.remoteTimer.save()
245                 timerInit()
246                 self.close()
247
248         def Exit(self):
249                 self.close()
250
251 baseTimerEntrySetup = None
252 baseTimerEntryGo = None
253
254 def timerInit():
255         global baseTimerEntrySetup, baseTimerEntryGo
256         if baseTimerEntrySetup is None:
257                 baseTimerEntrySetup = TimerEntry.createSetup
258         if baseTimerEntryGo is None:
259                 baseTimerEntryGo = TimerEntry.keyGo
260         TimerEntry.createSetup = createNewnigma2Setup
261         TimerEntry.keyGo = newnigma2KeyGo
262
263 def createNewnigma2Setup(self, widget):
264         baseTimerEntrySetup(self, widget)
265         self.timerentry_remote = ConfigYesNo()
266         self.list.insert(0, getConfigListEntry(_("Remote Timer"), self.timerentry_remote))
267
268         # force re-reading the list
269         self[widget].list = self.list
270
271 def newnigma2SubserviceSelected(self, service):
272         if service is not None:
273                 # ouch, this hurts a little
274                 service_ref = timerentry_service_ref
275                 self.timerentry_service_ref = ServiceReference(service[1])
276                 eit = self.timer.eit
277                 self.timer.eit = None
278
279                 newnigma2KeyGo(self)
280
281                 self.timerentry_service_ref = service_ref
282                 self.timer.eit = eit
283
284 def newnigma2KeyGo(self):
285         if not self.timerentry_remote.value:
286                 baseTimerEntryGo(self)
287         else:
288                 service_ref = self.timerentry_service_ref
289                 if self.timer.eit is not None:
290                         event = eEPGCache.getInstance().lookupEventId(service_ref.ref, self.timer.eit)
291                         if event:
292                                 n = event.getNumOfLinkageServices()
293                                 if n > 1:
294                                         tlist = []
295                                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
296                                         parent = service_ref.ref
297                                         selection = 0
298                                         for x in range(n):
299                                                 i = event.getLinkageService(parent, x)
300                                                 if i.toString() == ref.toString():
301                                                         selection = x
302                                                 tlist.append((i.getName(), i))
303                                         self.session.openWithCallback(boundFunction(newnigma2SubserviceSelected, self), ChoiceBox, title=_("Please select a subservice to record..."), list = tlist, selection = selection)
304                                         return
305                                 elif n > 0:
306                                         parent = service_ref.ref
307                                         service_ref = ServiceReference(event.getLinkageService(parent, 0))
308
309                 # XXX: this will - without any hassle - ignore the value of repeated
310                 begin, end = self.getBeginEnd()
311
312                 # when a timer end is set before the start, add 1 day
313                 if end < begin:
314                         end += 86400
315
316                 rt_name = quote_plus(self.timerentry_name.value.decode('utf8').encode('utf8','ignore'))
317                 rt_description = quote_plus(self.timerentry_description.value.decode('utf8').encode('utf8','ignore'))
318                 rt_disabled = 0 # XXX: do we really want to hardcode this? why do we offer this option then?
319                 rt_repeated = 0 # XXX: same here
320
321                 if self.timerentry_justplay.value == "zap":
322                         rt_justplay = 1
323                 else:
324                         rt_justplay = 0
325
326                 # XXX: this one is tricky since we do not know if the remote box offers afterEventAuto so lets just keep it simple for now
327                 rt_afterEvent = {
328                         "deepstandby": AFTEREVENT.DEEPSTANDBY,
329                         "standby": AFTEREVENT.STANDBY,
330                 }.get(self.timerentry_afterevent.value, AFTEREVENT.NONE)
331
332                 # Add Timer on RemoteBox via WebIf Command
333                 # http://192.168.178.20/web/timeradd?sRef=&begin=&end=&name=&description=&disabled=&justplay=&afterevent=&repeated=
334                 remoteip = "%d.%d.%d.%d" % tuple(config.plugins.remoteTimer.httpip.value)
335                 sref = quote_plus(str(service_ref).decode('utf-8'))
336                 remoteurl = "http://%s:%s/web/timeradd?sRef=%s&begin=%s&end=%s&name=%s&description=%s&disabled=%s&justplay=%s&afterevent=%s&repeated=%s" % (
337                         remoteip,
338                         config.plugins.remoteTimer.httpport.value,
339                         sref,
340                         begin,
341                         end,
342                         rt_name,
343                         rt_description,
344                         rt_disabled,
345                         rt_justplay,
346                         rt_afterEvent,
347                         rt_repeated
348                 )
349                 print "[RemoteTimer] debug remote", remoteurl
350
351                 defer = localGetPage(remoteurl)
352                 defer.addCallback(boundFunction(_gotPageLoad, self.session, self))
353                 defer.addErrback(boundFunction(errorLoad, self.session))
354
355 def _gotPageLoadCb(timerEntry, doClose, *args):
356         if doClose:
357                 timerEntry.keyCancel()
358
359 def _gotPageLoad(session, timerEntry, html):
360         remoteresponse = parseXml( html)
361         #print "print _gotPageLoad remoteresponse:", remoteresponse
362         # XXX: should be improved...
363         doClose = remoteresponse == "Timer added successfully!"
364         session.openWithCallback(
365                 boundFunction(_gotPageLoadCb, timerEntry, doClose),
366                 MessageBox,
367                 _("Set Timer on Remote DreamBox via WebIf:\n%s") % (remoteresponse),
368                 MessageBox.TYPE_INFO
369         )
370
371 def errorLoad(session, error):
372         #print "[RemoteTimer] errorLoad ERROR:", error
373         session.open(
374                 MessageBox,
375                 _("ERROR - Set Timer on Remote DreamBox via WebIf:\n%s") % (error),
376                 MessageBox.TYPE_INFO
377         )
378
379 def parseXml(string):
380         try:
381                 dom = cElementTree_fromstring(string)
382                 entry = dom.findtext('e2statetext')
383                 if entry:
384                         return entry.encode("utf-8", 'ignore')
385                 return "No entry in XML from the webserver"
386         except:
387                 return "ERROR XML PARSE"
388
389 #------------------------------------------------------------------------------------------
390
391 def autostart(reason, **kwargs):
392         if "session" in kwargs:
393                 session = kwargs["session"]
394                 try:
395                         if config.plugins.remoteTimer.httpip.value:
396                                 timerInit()
397                 except:
398                         print "[RemoteTimer] NO remoteTimer.httpip.value"
399
400 def main(session, **kwargs):
401         session.open(RemoteTimerScreen)
402
403 def Plugins(**kwargs):
404         return [
405                 PluginDescriptor(name="Remote Timer",description="Remote Timer Setup", where = [ PluginDescriptor.WHERE_PLUGINMENU ], icon="remotetimer.png", fnc = main),
406                 PluginDescriptor(name="Remote Timer", where = PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=main),
407                 PluginDescriptor(where = PluginDescriptor.WHERE_SESSIONSTART, fnc = autostart)
408         ]