Changed references from "Dreambox" to more general terms
[enigma2-plugins.git] / podcast / src / plugin.py
1 ##
2 ## Podcast
3 ## by AliAbdul
4 ##
5 from Components.ActionMap import ActionMap
6 from Components.config import config, ConfigSelection, ConfigSubsection, ConfigText, ConfigYesNo, getConfigListEntry
7 from Components.ConfigList import ConfigListScreen
8 from Components.FileList import FileList
9 from Components.Label import Label
10 from Components.Language import language
11 from Components.MenuList import MenuList
12 from Components.PluginComponent import plugins
13 from Components.ProgressBar import ProgressBar
14 from enigma import eServiceReference, eTimer, eEnv
15 from os import environ, system
16 from Plugins.Plugin import PluginDescriptor
17 from Screens.InfoBar import MoviePlayer
18 from Screens.MessageBox import MessageBox
19 from Screens.TextBox import TextBox
20 from Screens.Screen import Screen
21 from Tools.BoundFunction import boundFunction
22 from Tools.Directories import fileExists, resolveFilename, SCOPE_LANGUAGE, SCOPE_PLUGINS
23 from Tools.Downloader import downloadWithProgress
24 from twisted.web.client import getPage
25 from xml.etree.cElementTree import parse
26 from xml.dom.minidom import parseString as xmlparseString, parse as xmlparse
27 import gettext, re, urllib2
28
29 ###################################################
30
31 configDir = eEnv.resolve("${sysconfdir}") + "/podcast/"
32
33 def localeInit():
34         lang = language.getLanguage()
35         environ["LANGUAGE"] = lang[:2]
36         gettext.bindtextdomain("enigma2", resolveFilename(SCOPE_LANGUAGE))
37         gettext.textdomain("enigma2")
38         gettext.bindtextdomain("Podcast", "%s%s" % (resolveFilename(SCOPE_PLUGINS), "Extensions/Podcast/locale/"))
39
40 def _(txt):
41         t = gettext.dgettext("Podcast", txt)
42         if t == txt:
43                 t = gettext.gettext(txt)
44         return t
45
46 localeInit()
47 language.addCallback(localeInit)
48
49 ###################################################
50
51 def remove(file):
52         system('rm "' + file + '"')
53
54 ###################################################
55
56 class ChangedMoviePlayer(MoviePlayer):
57         def __init__(self, session, service):
58                 MoviePlayer.__init__(self, session, service)
59                 self.skinName = "MoviePlayer"
60
61         def leavePlayer(self):
62                 self.session.openWithCallback(self.leavePlayerConfirmed, MessageBox, _("Stop playing this movie?"))
63
64         def leavePlayerConfirmed(self, answer):
65                 if answer:
66                         self.close()
67
68         def doEofInternal(self, playing):
69                 pass
70
71         def getPluginList(self):
72                 list = []
73                 for p in plugins.getPlugins(where=PluginDescriptor.WHERE_EXTENSIONSMENU):
74                         if p.name != _("Podcast"):
75                                 list.append(((boundFunction(self.getPluginName, p.name), boundFunction(self.runPlugin, p), lambda: True), None))
76                 return list
77
78         def showMovies(self):
79                 pass
80
81 ###################################################
82
83 config.plugins.Podcast = ConfigSubsection()
84 config.plugins.Podcast.buffer = ConfigYesNo(default=True)
85 config.plugins.Podcast.bufferDevice = ConfigText(default="/media/hdd/", fixed_size=False)
86 config.plugins.Podcast.keepStored = ConfigSelection(choices={"delete": _("delete"), "keep": _("keep on device"), "ask": _("ask me")}, default="delete")
87
88 ###################################################
89
90 def encodeUrl(url):
91         url = url.replace("&", "&")
92         url = url.replace("&lt;", "<")
93         url = url.replace("&gt;", ">")
94         url = url.replace("&#39;", "'")
95         url = url.replace("&quot;", '"')
96         url = url.replace("&#42;", "*")
97         url = url.replace("&#124;", "|")
98         url = url.replace("&#039;", "'")
99         url = url.replace("&#187;", ">>")
100         return url
101
102 def getText(nodelist):
103         rc = []
104         for node in nodelist:
105                 if node.nodeType == node.TEXT_NODE:
106                         rc.append(node.data)
107         return ''.join(rc)
108                                 
109 ###################################################
110
111 class BufferThread():
112         def __init__(self):
113                 self.progress = 0
114                 self.downloading = False
115                 self.error = ""
116                 self.download = None
117
118         def startDownloading(self, url, file):
119                 self.progress = 0
120                 self.downloading = True
121                 self.error = ""
122                 self.download = downloadWithProgress(url, file)
123                 self.download.addProgress(self.httpProgress)
124                 self.download.start().addCallback(self.httpFinished).addErrback(self.httpFailed)
125
126         def httpProgress(self, recvbytes, totalbytes):
127                 self.progress = int(100 * recvbytes / float(totalbytes))
128
129         def httpFinished(self, string=""):
130                 self.downloading = False
131                 if string is not None:
132                         self.error = str(string)
133                 else:
134                         self.error = ""
135
136         def httpFailed(self, failure_instance=None, error_message=""):
137                 self.downloading = False
138                 if error_message == "" and failure_instance is not None:
139                         error_message = failure_instance.getErrorMessage()
140                         self.error = str(error_message)
141
142         def stop(self):
143                 self.progress = 0
144                 self.downloading = False
145                 self.error = ""
146                 self.download.stop()
147
148 bufferThread = BufferThread()
149
150 ###################################################
151
152 class PodcastBuffer(Screen):
153         skin = """
154                 <screen position="center,center" size="520,180" title="%s" >
155                         <widget name="info" position="5,5" size="510,140" font="Regular;18" halign="center" valign="center" />
156                         <widget name="progress" position="100,150" size="320,14" pixmap="skin_default/progress_big.png" borderWidth="2" borderColor="#cccccc" />
157                 </screen>""" % _("Podcast")
158
159         def __init__(self, session, url, file):
160                 self.session = session
161                 Screen.__init__(self, session)
162                 
163                 self.url = url
164                 self.file = file
165                 
166                 self.infoTimer = eTimer()
167                 self.infoTimer.timeout.get().append(self.updateInfo)
168                 
169                 self["info"] = Label(_("Downloading movie: %s") % self.file)
170                 self["progress"] = ProgressBar()
171                 
172                 self["actions"] = ActionMap(["OkCancelActions"], {"ok": self.okClicked, "cancel": self.exit}, -1)
173                 
174                 self.onLayoutFinish.append(self.downloadMovie)
175
176         def downloadMovie(self):
177                 bufferThread.startDownloading(self.url, self.file)
178                 self.infoTimer.start(300, False)
179
180         def updateInfo(self):
181                 if bufferThread.error != "":
182                         self["info"].setText(bufferThread.error)
183                         self.infoTimer.stop()
184                 else:
185                         progress = int(bufferThread.progress)
186                         self["progress"].setValue(progress)
187                         if progress == 100:
188                                 self.infoTimer.stop()
189                                 self.close(True)
190
191         def okClicked(self):
192                 if int(bufferThread.progress) > 0:
193                         self.infoTimer.stop()
194                         self.close(True)
195
196         def exit(self):
197                 bufferThread.download.stop()
198                 self.close(None)
199
200 ###################################################
201
202 class PodcastMovies(Screen):
203         skin = """
204                 <screen position="center,center" size="600,460" title="%s" >
205                         <widget name="list" position="5,5" size="590,250" scrollbarMode="showOnDemand" />
206                         <eLabel position="5,260" size="600,2" backgroundColor="#ffffff" />
207                         <widget name="info" position="5,265" size="600,190" font="Regular;18" />
208                 </screen>""" % _("Podcast")
209
210         def __init__(self, session, url):
211                 self.session = session
212                 Screen.__init__(self, session)
213                 
214                 self.url = url
215                 self.list = []
216                 self.movies = []
217                 self.working = True
218                 
219                 self["list"] = MenuList([])
220                 self["list"].onSelectionChanged.append(self.showInfo)
221                 self["info"] = Label()
222                 
223                 self["actions"] = ActionMap(["OkCancelActions"], {"ok": self.ok, "cancel": self.exit}, -1)
224                 
225                 self.onLayoutFinish.append(self.downloadMovies)
226
227         def ok(self):
228                 if self.working == False:
229                         if len(self.list) > 0:
230                                 idx = self["list"].getSelectionIndex()
231                                 (url, length, type) = self.movies[idx][1]
232                                 if config.plugins.Podcast.buffer.value:
233                                         file = url
234                                         while file.__contains__("/"):
235                                                 idx = file.index("/")
236                                                 file = file[idx+1:]
237                                         self.file = "%s%s" % (config.plugins.Podcast.bufferDevice.value, file)
238                                         self.session.openWithCallback(self.bufferCallback, PodcastBuffer, url, self.file)
239                                 else:
240                                         ref = eServiceReference(4097, 0, url)
241                                         self.session.open(ChangedMoviePlayer, ref)
242
243         def bufferCallback(self, callback):
244                 if callback is not None:
245                         ref = eServiceReference(4097, 0, self.file)
246                         self.session.openWithCallback(self.delete, ChangedMoviePlayer, ref)
247
248         def delete(self, callback=None):
249                 if bufferThread.downloading: #still downloading?
250                         bufferThread.stop()
251                 if config.plugins.Podcast.keepStored.value == "delete":
252                         remove(self.file)
253                 elif config.plugins.Podcast.keepStored.value == "ask":
254                         self.session.openWithCallback(self.deleteCallback, MessageBox, _("Delete this movie?"))
255
256         def deleteCallback(self, callback):
257                 if callback:
258                         remove(self.file)
259
260         def exit(self):
261                 if self.working == False:
262                         self.close()
263
264         def downloadMovies(self):
265                 getPage(self.url).addCallback(self.showMovies).addErrback(self.error)
266
267         def showMovies(self, page):
268                 if '<item>' in page:
269                         dom = xmlparseString(page)
270                         items = dom.getElementsByTagName("item")
271                 else:
272                         item = xmlparseString(page)
273                         items = [item]  
274                 
275                 for item in items:
276                         title = getText(item.getElementsByTagName("title")[0].childNodes).encode('utf8')
277                         description = getText(item.getElementsByTagName("description")[0].childNodes).encode('utf8')
278                         url = item.getElementsByTagName("enclosure")[0].getAttribute("url").encode('utf8')
279                         if url == "":
280                                 url = "N/A"
281                         length = item.getElementsByTagName("enclosure")[0].getAttribute("length").encode('utf8')
282                         if length == "":
283                                 length = "N/A" 
284                         type = item.getElementsByTagName("enclosure")[0].getAttribute("type").encode('utf8')
285                         if type == "":
286                                 type = "N/A"
287                         self.list.append(encodeUrl(title))
288                         self.movies.append([description, (url, length, type)])
289                         
290                 self["list"].setList(self.list)
291                 self.showInfo()
292                 self.working = False
293
294         def error(self, error=""):
295                 print "[Podcast] Error:", error
296                 self.instance.setTitle(_("Error getting movies"))
297                 self.working = False
298
299         def showInfo(self):
300                 if len(self.list) > 0:
301                         idx = self["list"].getSelectionIndex()
302                         description = self.movies[idx][0]
303                         (url, length, type) = self.movies[idx][1]
304                         self["info"].setText("%s: %s   %s: %s\n%s" % (_("Length"), length, _("Type"), type, encodeUrl(description)))
305
306 ###################################################
307
308 class PodcastPodcasts(Screen):
309         skin = """
310                 <screen position="center,center" size="420,360" title="%s" >
311                         <widget name="list" position="0,0" size="420,350" scrollbarMode="showOnDemand" />
312                 </screen>""" % _("Podcast")
313
314         def __init__(self, session, provider):
315                 self.session = session
316                 Screen.__init__(self, session)
317                 
318                 self["actions"] = ActionMap(["OkCancelActions"], {"ok": self.ok, "cancel": self.close}, -1)
319                 
320                 self.urls = []
321                 list = []
322                 for podcast in provider.findall("podcast"):
323                         name = podcast.get("name") or None
324                         name = name.encode("UTF-8") or name
325                         url = podcast.get("url") or None
326                         if name and url:
327                                 list.append(name)
328                                 self.urls.append(url)
329                 self["list"] = MenuList(list)
330
331         def ok(self):
332                 if len(self.urls) > 0:
333                         cur = self.urls[self["list"].getSelectedIndex()]
334                         self.session.open(PodcastMovies, cur)
335
336 ###################################################
337
338 class PodcastProvider(Screen):
339         skin = """
340                 <screen position="center,center" size="420,360" title="%s" >
341                         <widget name="list" position="0,0" size="420,350" scrollbarMode="showOnDemand" />
342                 </screen>""" % _("Podcast")
343
344         def __init__(self, session, language):
345                 self.session = session
346                 Screen.__init__(self, session)
347                 
348                 self["actions"] = ActionMap(["OkCancelActions"], {"ok": self.ok, "cancel": self.close}, -1)
349                 
350                 self.providers = []
351                 list = []
352                 for provider in language.findall("provider"):
353                         name = provider.get("name") or None
354                         name = name.encode("UTF-8") or name
355                         if name:
356                                 list.append(name)
357                                 self.providers.append(provider)
358                 self["list"] = MenuList(list)
359
360         def ok(self):
361                 if len(self.providers) > 0:
362                         cur = self.providers[self["list"].getSelectedIndex()]
363                         self.session.open(PodcastPodcasts, cur)
364
365 ###################################################
366
367 class PodcastXML(Screen):
368         skin = """
369                 <screen position="center,center" size="420,360" title="%s" >
370                         <widget name="list" position="0,0" size="420,350" scrollbarMode="showOnDemand" />
371                 </screen>""" % _("Podcast")
372
373         def __init__(self, session):
374                 self.session = session
375                 Screen.__init__(self, session)
376                 
377                 self["actions"] = ActionMap(["OkCancelActions"], {"ok": self.ok, "cancel": self.close}, -1)
378                 
379                 self.languages = []
380                 list = []
381                 # file = open("/etc/podcast/podcasts_local.xml")
382                 # try user defined list, else fall back to default
383                 if fileExists(configDir + "podcasts_user.xml"):
384                         file = open(configDir + "podcasts_user.xml")
385                 else:
386                         if fileExists(configDir + "podcasts.xml"):
387                                 file = open(configDir + "podcasts.xml")
388                 if file:
389                         # check if file is just a proxy to an external XML
390                         head = file.readline()
391                         if head.startswith("http"):
392                                 source = urllib2.urlopen(head)
393                         else:
394                                 source = file
395                                 
396                         xml = parse(source).getroot()
397                         for language in xml.findall("language"):
398                                 name = language.get("name") or None
399                                 name = name.encode("UTF-8") or name
400                                 if name:
401                                         list.append(name)
402                                         self.languages.append(language)
403                         source.close()
404                 self["list"] = MenuList(list)
405
406         def ok(self):
407                 if len(self.languages) > 0:
408                         cur = self.languages[self["list"].getSelectedIndex()]
409                         self.session.open(PodcastProvider, cur)
410
411 ###################################################
412
413 class PodcastFeedly(Screen):
414         skin = """
415                 <screen position="center,center" size="420,360" title="%s" >
416                         <widget name="list" position="0,0" size="420,350" scrollbarMode="showOnDemand" />
417                 </screen>""" % _("Podcast")
418                 
419         def __init__(self, session):
420                 self.session = session
421                 Screen.__init__(self, session)
422                 
423                 self["actions"] = ActionMap(["OkCancelActions"], {"ok": self.ok, "cancel": self.close}, -1)
424                 self.urls = []
425                 list = []
426                 
427                 if fileExists(configDir + "feedly.opml"):
428                         file = open(configDir + "feedly.opml")
429                 else:
430                         list.append(_("No Feedly configuration"))
431
432                 if file:
433                         # check if file is just a proxy to an external XML
434                         head = file.readline()
435                         if head.startswith("http"):
436                                 source = urllib2.urlopen(head)
437                         else:
438                                 source = file
439                                 
440                         dom = xmlparse(source)
441                         for item in dom.getElementsByTagName("outline"):
442                                 if str(item.getAttribute("title")) == "PodcastPlugin":
443                                         for podcast in item.getElementsByTagName("outline"):
444                                                 list.append(str(podcast.getAttribute("title")))
445                                                 self.urls.append(str(podcast.getAttribute("xmlUrl")))
446
447                 self["list"] = MenuList(list)
448
449         def ok(self):
450                 if len(self.urls) > 0:
451                         cur = self.urls[self["list"].getSelectedIndex()]
452                         self.session.open(PodcastMovies, cur)
453                                                                                                 
454 ###################################################
455
456 class LocationSelection(Screen):
457         skin = """
458         <screen position="center,center" size="560,300" title="%s">
459                 <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" transparent="1" alphatest="on" />
460                 <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" transparent="1" alphatest="on" />
461                 <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" size="140,40" transparent="1" alphatest="on" />
462                 <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" size="140,40" transparent="1" alphatest="on" />
463                 <widget name="key_green" position="140,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
464                 <widget name="filelist" position="10,45" size="550,255" scrollbarMode="showOnDemand" />
465         </screen>""" % _("Podcast")
466
467         def __init__(self, session, dir="/"):
468                 Screen.__init__(self, session)
469                 
470                 self["key_green"] = Label(_("Select"))
471                 
472                 try: self["filelist"] = FileList(dir, showDirectories=True, showFiles=False)
473                 except: self["filelist"] = FileList("/", showDirectories, showFiles)
474                 
475                 self["actions"] = ActionMap(["ColorActions", "OkCancelActions"],
476                         {
477                                 "ok": self.okClicked,
478                                 "cancel": self.exit,
479                                 "green": self.select
480                         }, -1)
481                 
482                 self.onLayoutFinish.append(self.updateDirectoryName)
483                 
484         def okClicked(self):
485                 if self["filelist"].canDescent():
486                         self["filelist"].descent()
487                         self["filelist"].instance.moveSelectionTo(0)
488                         self.updateDirectoryName()
489
490         def exit(self):
491                 self.close(None)
492
493         def select(self):
494                 dir = self["filelist"].getCurrentDirectory()
495                 if dir is not None:
496                         self.close(dir)
497                 else:
498                         self.close(None)
499
500         def updateDirectoryName(self):
501                 try:
502                         dir = self["filelist"].getCurrentDirectory()
503                         self.instance.setTitle(dir)
504                 except:
505                         self.instance.setTitle("?")
506
507 ###################################################
508
509 class PodcastConfig(ConfigListScreen, Screen):
510         skin = """
511         <screen position="center,center" size="560,180" title="%s">
512                 <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" transparent="1" alphatest="on" />
513                 <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" transparent="1" alphatest="on" />
514                 <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" size="140,40" transparent="1" alphatest="on" />
515                 <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" size="140,40" transparent="1" alphatest="on" />
516                 <widget name="key_green" position="140,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
517                 <widget name="config" position="10,45" size="550,125" scrollbarMode="showOnDemand" />
518         </screen>""" % _("Podcast")
519
520         def __init__(self, session):
521                 Screen.__init__(self, session)
522                 
523                 self["key_green"] = Label(_("Save"))
524                 
525                 ConfigListScreen.__init__(self, [])
526                         
527                 
528                 self["actions"] = ActionMap(["OkCancelActions", "ColorActions"], {"green": self.save, "cancel": self.exit}, -1)
529                 
530                 self.onLayoutFinish.append(self.createConfig)
531
532         def createConfig(self):
533                 self.deviceEntry = ConfigSelection(choices=[config.plugins.Podcast.bufferDevice.value], default=config.plugins.Podcast.bufferDevice.value)
534                 self["config"].list = [
535                         getConfigListEntry(_("Buffer:"), config.plugins.Podcast.buffer),
536                         getConfigListEntry(_("Buffer device:"), self.deviceEntry),
537                         getConfigListEntry(_("Buffer file handling:"), config.plugins.Podcast.keepStored)]
538
539         def keyLeft(self):
540                 ConfigListScreen.keyLeft(self)
541                 self.handleKeysLeftAndRight()
542
543         def keyRight(self):
544                 ConfigListScreen.keyRight(self)
545                 self.handleKeysLeftAndRight()
546
547         def handleKeysLeftAndRight(self):
548                 sel = self["config"].getCurrent()[1]
549                 if sel == self.deviceEntry:
550                         self.session.openWithCallback(self.locationSelected, LocationSelection, config.plugins.Podcast.bufferDevice.value)
551
552         def locationSelected(self, dir):
553                 if dir is not None and dir != "?":
554                         config.plugins.Podcast.bufferDevice.value = dir
555                         config.plugins.Podcast.bufferDevice.save()
556                         self.createConfig()
557
558         def save(self):
559                 for x in self["config"].list:
560                         x[1].save()
561                 self.close()
562
563         def exit(self):
564                 for x in self["config"].list:
565                         x[1].cancel()
566                 self.close()
567
568 ###################################################
569
570 class Podcast(Screen):
571         skin = """
572                 <screen position="center,center" size="560,360" title="%s" >
573                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" transparent="1" alphatest="on" />
574                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" transparent="1" alphatest="on" />
575                         <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" size="140,40" transparent="1" alphatest="on" />
576                         <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" size="140,40" transparent="1" alphatest="on" />
577                         <widget name="key_blue" position="420,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
578                         <widget name="list" position="0,45" size="560,305" scrollbarMode="showOnDemand" />
579                 </screen>""" % _("Podcast")
580
581         def __init__(self, session):
582                 self.session = session
583                 Screen.__init__(self, session)
584                 
585                 self["key_blue"] = Label(_("Help"))
586                 
587                 self["actions"] = ActionMap(["ColorActions", "OkCancelActions"], {"ok": self.ok, "cancel": self.close, "blue": self.help}, -1)
588                 
589                 self["list"] = MenuList([
590                         _("from xml"),
591                         _("Feedly OPML"),
592                         _("configuration")])
593
594         def ok(self):
595                 cur = self["list"].getCurrent()
596                 if cur == _("from xml"):
597                         self.session.open(PodcastXML)
598                 elif cur == _("Feedly OPML"):
599                         self.session.open(PodcastFeedly)
600                 else:
601                         self.session.open(PodcastConfig)
602
603         def help(self):
604                 localehelpfile = "%s/Extensions/Podcast/help/help_%s" % (resolveFilename(SCOPE_PLUGINS), language.getLanguage()[:2])
605                 fallbackhelpfile = "%s/Extensions/Podcast/help/help_en" % resolveFilename(SCOPE_PLUGINS)
606                 if fileExists(localehelpfile):
607                         helpfile = localehelpfile
608                 else:
609                         helpfile = fallbackhelpfile
610                 h = open(helpfile)
611                 helptext = h.read()
612                 h.close
613                 self.session.open(TextBox, helptext)
614
615 ###################################################
616
617 def main(session, **kwargs):
618         session.open(Podcast)
619
620 def Plugins(**kwargs):
621         return PluginDescriptor(name=_("Podcast"), where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=main)