fix file permissions of non-executable files
[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
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 import urllib
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.sref = sref
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                         url = "http://%s/web/timerdelete?sRef=%s&begin=%s&end=%s" % (self.remoteurl, sel.service_ref.sref, sel.begin, sel.end)
154                         localGetPage(url).addCallback(self.getInfo).addErrback(self.errorLoad)
155
156         def settings(self):
157                 self.session.open(RemoteTimerSetup)
158
159         def generateTimerE2(self, data):
160                 try:
161                         root = cElementTree_fromstring(data)
162                 except Exception, e:
163                         print "[RemoteTimer] error: %s", e
164                         self["text"].setText(_("error parsing incoming data."))
165                 else:
166                         return [
167                                 (
168                                         E2Timer(
169                                                 sref = str(timer.findtext("e2servicereference", '').encode("utf-8", 'ignore')),
170                                                 sname = str(timer.findtext("e2servicename", 'n/a').encode("utf-8", 'ignore')),
171                                                 name = str(timer.findtext("e2name", '').encode("utf-8", 'ignore')),
172                                                 disabled = int(timer.findtext("e2disabled", 0)),
173                                                 timebegin = int(timer.findtext("e2timebegin", 0)),
174                                                 timeend = int(timer.findtext("e2timeend", 0)),
175                                                 duration = int(timer.findtext("e2duration", 0)),
176                                                 startprepare = int(timer.findtext("e2startprepare", 0)),
177                                                 state = int(timer.findtext("e2state", 0)),
178                                                 repeated = int(timer.findtext("e2repeated", 0)),
179                                                 justplay = int(timer.findtext("e2justplay", 0)),
180                                                 eventId = int(timer.findtext("e2eit", -1)),
181                                                 afterevent = int(timer.findtext("e2afterevent", 0)),
182                                                 dirname = str(timer.findtext("e2dirname", '').encode("utf-8", 'ignore')),
183                                                 description = str(timer.findtext("e2description", '').encode("utf-8", 'ignore'))
184                                         ),
185                                         False
186                                 )
187                                 for timer in root.findall("e2timer")
188                         ]
189
190 class E2Timer:
191         def __init__(self, sref = "", sname = "", name = "", disabled = 0, \
192                         timebegin = 0, timeend = 0, duration = 0, startprepare = 0, \
193                         state = 0, repeated = 0, justplay = 0, eventId = 0, afterevent = 0, \
194                         dirname = "", description = ""):
195                 self.service_ref = RemoteService(sref, sname)
196                 self.name = name
197                 self.disabled = disabled
198                 self.begin = timebegin
199                 self.end = timeend
200                 self.duration = duration
201                 self.startprepare = startprepare
202                 self.state = state
203                 self.repeated = repeated
204                 self.justplay = justplay
205                 self.eventId = eventId
206                 self.afterevent = afterevent
207                 self.dirname = dirname
208                 self.description = description
209
210 class RemoteTimerSetup(Screen, ConfigListScreen):
211         skin = """
212                 <screen position="center,center" size="560,410" title="Settings" >
213                         <widget name="config" position="5,40" size="480,335" scrollbarMode="showOnDemand" />
214                         <ePixmap name="red" position="120,280" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
215                         <ePixmap name="green" position="320,280" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
216                         <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" />
217                         <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" />
218                 </screen>"""
219
220         def __init__(self, session):
221                 Screen.__init__(self, session)
222
223                 self["SetupActions"] = ActionMap(["SetupActions", "ColorActions"],
224                 {
225                         "ok": self.keySave,
226                         "cancel": self.Exit,
227                         "green": self.keySave,
228                 }, -1)
229
230                 self["key_red"] = Button(_("Cancel"))
231                 self["key_green"] = Button(_("OK"))
232
233                 ConfigListScreen.__init__(self, [
234                         getConfigListEntry(_("Remote Timer - Hostname"), config.plugins.remoteTimer.httphost),
235                         getConfigListEntry(_("Remote Timer - Network IP"), config.plugins.remoteTimer.httpip),
236                         getConfigListEntry(_("Remote Timer - WebIf Port"), config.plugins.remoteTimer.httpport),
237                         getConfigListEntry(_("Remote Timer - Username"), config.plugins.remoteTimer.username),
238                         getConfigListEntry(_("Remote Timer - Password"), config.plugins.remoteTimer.password),
239                 ], session)
240
241         def keySave(self):
242                 config.plugins.remoteTimer.save()
243                 timerInit()
244                 self.close()
245
246         def Exit(self):
247                 self.close()
248
249 baseTimerEntrySetup = None
250 baseTimerEntryGo = None
251
252 def timerInit():
253         global baseTimerEntrySetup, baseTimerEntryGo
254         if baseTimerEntrySetup is None:
255                 baseTimerEntrySetup = TimerEntry.createSetup
256         if baseTimerEntryGo is None:
257                 baseTimerEntryGo = TimerEntry.keyGo
258         TimerEntry.createSetup = createNewnigma2Setup
259         TimerEntry.keyGo = newnigma2KeyGo
260
261 def createNewnigma2Setup(self, widget):
262         baseTimerEntrySetup(self, widget)
263         self.timerentry_remote = ConfigYesNo()
264         self.list.insert(0, getConfigListEntry(_("Remote Timer"), self.timerentry_remote))
265
266         # force re-reading the list
267         self[widget].list = self.list
268
269 def newnigma2SubserviceSelected(self, service):
270         if service is not None:
271                 # ouch, this hurts a little
272                 service_ref = timerentry_service_ref
273                 self.timerentry_service_ref = ServiceReference(service[1])
274                 eit = self.timer.eit
275                 self.timer.eit = None
276
277                 newnigma2KeyGo(self)
278
279                 self.timerentry_service_ref = service_ref
280                 self.timer.eit = eit
281
282 def newnigma2KeyGo(self):
283         if not self.timerentry_remote.value:
284                 baseTimerEntryGo(self)
285         else:
286                 service_ref = self.timerentry_service_ref
287                 if self.timer.eit is not None:
288                         event = eEPGCache.getInstance().lookupEventId(service_ref.ref, self.timer.eit)
289                         if event:
290                                 n = event.getNumOfLinkageServices()
291                                 if n > 1:
292                                         tlist = []
293                                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
294                                         parent = service_ref.ref
295                                         selection = 0
296                                         for x in range(n):
297                                                 i = event.getLinkageService(parent, x)
298                                                 if i.toString() == ref.toString():
299                                                         selection = x
300                                                 tlist.append((i.getName(), i))
301                                         self.session.openWithCallback(boundFunction(newnigma2SubserviceSelected, self), ChoiceBox, title=_("Please select a subservice to record..."), list = tlist, selection = selection)
302                                         return
303                                 elif n > 0:
304                                         parent = service_ref.ref
305                                         service_ref = ServiceReference(event.getLinkageService(parent, 0))
306
307                 # XXX: this will - without any hassle - ignore the value of repeated
308                 begin, end = self.getBeginEnd()
309
310                 # when a timer end is set before the start, add 1 day
311                 if end < begin:
312                         end += 86400
313
314                 rt_name = urllib.quote(self.timerentry_name.value.decode('utf8').encode('utf8','ignore'))
315                 rt_description = urllib.quote(self.timerentry_description.value.decode('utf8').encode('utf8','ignore'))
316                 rt_disabled = 0 # XXX: do we really want to hardcode this? why do we offer this option then?
317                 rt_repeated = 0 # XXX: same here
318
319                 if self.timerentry_justplay.value == "zap":
320                         rt_justplay = 1
321                 else:
322                         rt_justplay = 0
323
324                 # XXX: this one is tricky since we do not know if the remote box offers afterEventAuto so lets just keep it simple for now
325                 rt_afterEvent = {
326                         "deepstandby": AFTEREVENT.DEEPSTANDBY,
327                         "standby": AFTEREVENT.STANDBY,
328                 }.get(self.timerentry_afterevent.value, AFTEREVENT.NONE)
329
330                 # Add Timer on RemoteBox via WebIf Command
331                 # http://192.168.178.20/web/timeradd?sRef=&begin=&end=&name=&description=&disabled=&justplay=&afterevent=&repeated=
332                 remoteip = "%d.%d.%d.%d" % tuple(config.plugins.remoteTimer.httpip.value)
333                 remoteurl = "http://%s:%s/web/timeradd?sRef=%s&begin=%s&end=%s&name=%s&description=%s&disabled=%s&justplay=%s&afterevent=%s&repeated=%s" % (
334                         remoteip,
335                         config.plugins.remoteTimer.httpport.value,
336                         service_ref,
337                         begin,
338                         end,
339                         rt_name,
340                         rt_description,
341                         rt_disabled,
342                         rt_justplay,
343                         rt_afterEvent,
344                         rt_repeated
345                 )
346                 print "[RemoteTimer] debug remote", remoteurl
347
348                 defer = localGetPage(remoteurl)
349                 defer.addCallback(boundFunction(_gotPageLoad, self.session, self))
350                 defer.addErrback(boundFunction(errorLoad, self.session))
351
352 def _gotPageLoadCb(timerEntry, doClose, *args):
353         if doClose:
354                 timerEntry.keyCancel()
355
356 def _gotPageLoad(session, timerEntry, html):
357         remoteresponse = parseXml( html)
358         #print "print _gotPageLoad remoteresponse:", remoteresponse
359         # XXX: should be improved...
360         doClose = remoteresponse == "Timer added successfully!"
361         session.openWithCallback(
362                 boundFunction(_gotPageLoadCb, timerEntry, doClose),
363                 MessageBox,
364                 _("Set Timer on Remote DreamBox via WebIf:\n%s") % (remoteresponse),
365                 MessageBox.TYPE_INFO
366         )
367
368 def errorLoad(session, error):
369         #print "[RemoteTimer] errorLoad ERROR:", error
370         session.open(
371                 MessageBox,
372                 _("ERROR - Set Timer on Remote DreamBox via WebIf:\n%s") % (error),
373                 MessageBox.TYPE_INFO
374         )
375
376 def parseXml(string):
377         try:
378                 dom = cElementTree_fromstring(string)
379                 entry = dom.findtext('e2statetext')
380                 if entry:
381                         return entry.encode("utf-8", 'ignore')
382                 return "No entry in XML from the webserver"
383         except:
384                 return "ERROR XML PARSE"
385
386 #------------------------------------------------------------------------------------------
387
388 def autostart(reason, **kwargs):
389         if "session" in kwargs:
390                 session = kwargs["session"]
391                 try:
392                         if config.plugins.remoteTimer.httpip.value:
393                                 timerInit()
394                 except:
395                         print "[RemoteTimer] NO remoteTimer.httpip.value"
396
397 def main(session, **kwargs):
398         session.open(RemoteTimerScreen)
399
400 def Plugins(**kwargs):
401         return [
402                 PluginDescriptor(name="Remote Timer",description="Remote Timer Setup", where = [ PluginDescriptor.WHERE_PLUGINMENU ], icon="remotetimer.png", fnc = main),
403                 PluginDescriptor(name="Remote Timer", where = PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=main),
404                 PluginDescriptor(where = PluginDescriptor.WHERE_SESSIONSTART, fnc = autostart)
405         ]