add support for cyclic garbage collection to eTimer and eSocketNotifier
[vuplus_dvbapp] / lib / python / Plugins / Extensions / SimpleRSS / plugin.py
1 # warning, this is work in progress.
2 # plus, the "global_session" stuff is of course very lame.
3 # plus, the error handling sucks.
4 from Screens.Screen import Screen
5 from Screens.MessageBox import MessageBox
6 from Components.ActionMap import ActionMap
7 from Components.GUIComponent import GUIComponent
8 from Components.MultiContent import MultiContentEntryText
9 from Plugins.Plugin import PluginDescriptor
10 from enigma import eListboxPythonMultiContent, eListbox, gFont, RT_HALIGN_LEFT, RT_WRAP
11
12 from twisted.web.client import getPage
13 import xml.dom.minidom
14
15 from Tools.XMLTools import mergeText, elementsWithTag
16
17 from enigma import eTimer
18 from sets import Set
19
20 my_global_session = None
21
22 #urls = ["http://www.heise.de/newsticker/heise.rdf", "http://rss.slashdot.org/Slashdot/slashdot/to"]
23 urls = ["http://mastermaq.podcastspot.com/episodes/rss/mpg1"]
24
25 from Components.config import config, ConfigSubsection, ConfigSelection, getConfigListEntry
26 from Components.ConfigList import ConfigListScreen
27 config.simpleRSS = ConfigSubsection()
28 config.simpleRSS.hostname = ConfigSelection(choices = urls)
29
30 class SimpleRSS(ConfigListScreen, Screen):
31         skin = """
32                 <screen position="100,100" size="550,400" title="Simple RSS Reader" >
33                 <widget name="config" position="20,10" size="460,350" scrollbarMode="showOnDemand" />
34                 </screen>"""
35
36         def __init__(self, session, args = None):
37                 print "screen init"
38                 Screen.__init__(self, session)
39                 self.skin = SimpleRSS.skin
40                 
41                 self.onClose.append(self.abort)
42                 
43                 # nun erzeugen wir eine liste von elementen fuer die menu liste.
44                 self.list = [ ]
45                 self.list.append(getConfigListEntry(_("RSS Feed URI"), config.simpleRSS.hostname))
46                 
47                 # die liste selbst
48                 ConfigListScreen.__init__(self, self.list)
49
50                 self["actions"] = ActionMap([ "OkCancelActions" ], 
51                 {
52                         "ok": self.close,
53 #                       "cancel": self.close,
54                 })
55
56                 self["setupActions"] = ActionMap(["SetupActions"],
57                 {
58                         "save": self.save,
59                         "cancel": self.close
60                 }, -1)
61         
62         def abort(self):
63                 print "aborting"
64
65         def save(self):
66                 for x in self["config"].list:
67                         x[1].save()
68                 self.close()
69
70         def cancel(self):
71                 for x in self["config"].list:
72                         x[1].cancel()
73                 self.close()
74
75 class RSSList(GUIComponent):
76         def __init__(self, entries):
77                 GUIComponent.__init__(self)
78                 self.l = eListboxPythonMultiContent()
79                 self.l.setFont(0, gFont("Regular", 22))
80                 self.l.setFont(1, gFont("Regular", 18))
81                 self.list = [self.buildListboxEntry(x) for x in entries]
82                 self.l.setList(self.list)
83
84         GUI_WIDGET = eListbox
85
86         def postWidgetCreate(self, instance):
87                 instance.setContent(self.l)
88                 instance.setItemHeight(100)
89
90         def buildListboxEntry(self, rss_entry):
91                 res = [ rss_entry ]
92                 res.append(MultiContentEntryText(pos=(0, 0), size=(460, 75), font=0, flags = RT_HALIGN_LEFT|RT_WRAP, text = rss_entry[0]))
93                 res.append(MultiContentEntryText(pos=(0, 75), size=(460, 20), font=1, flags = RT_HALIGN_LEFT, text = rss_entry[1]))
94                 return res
95
96
97         def getCurrentEntry(self):
98                 return self.l.getCurrentSelection()
99
100 class RSSDisplay(Screen):
101         skin = """
102                 <screen position="100,100" size="460,400" title="Simple RSS Reader" >
103                 <widget name="content" position="0,0" size="460,400" />
104                 </screen>"""
105
106         def __init__(self, session, data, interactive = False):
107                 Screen.__init__(self, session)
108                 self.skin = RSSDisplay.skin
109                 
110                 if interactive:
111                         self["actions"] = ActionMap([ "OkCancelActions" ], 
112                         {
113                                 "ok": self.showCurrentEntry,
114                                 "cancel": self.close,
115                         })
116
117                 self["content"] = RSSList(data) 
118
119         def showCurrentEntry(self):
120                 current_entry = self["content"].getCurrentEntry()
121                 if current_entry is None: # empty list
122                         return
123
124                 (title, link, enclosure) = current_entry[0]
125                 
126                 if len(enclosure):
127                         (url, type) = enclosure[0] # TODO: currently, we used the first enclosure. there can be multiple.
128                         
129                         print "enclosure: url=%s, type=%s" % (url, type)
130                         
131                         if type in ["video/mpeg", "audio/mpeg"]:
132                                 from enigma import eServiceReference
133                                 # we should better launch a player or so...
134                                 self.session.nav.playService(eServiceReference(4097, 0, url))
135
136 class RSSPoller:
137
138         MAX_HISTORY_ELEMENTS = 100
139
140         def __init__(self):
141                 self.poll_timer = eTimer()
142                 self.poll_timer.callback.append(self.poll)
143                 self.poll_timer.start(0, 1)
144                 self.last_links = Set()
145                 self.dialog = None
146                 self.history = [ ]
147                 
148         def error(self, error):
149                 if not my_global_session:
150                         print "error polling"
151                 else:
152                         my_global_session.open(MessageBox, "Sorry, failed to fetch feed.\n" + error)
153         
154         def _gotPage(self, data):
155                 # workaround: exceptions in gotPage-callback were ignored
156                 try:
157                         self.gotPage(data)
158                 except:
159                         import traceback, sys
160                         traceback.print_exc(file=sys.stdout)
161                         raise e
162         
163         def gotPage(self, data):
164                 print "parsing.."
165                 
166                 new_items = [ ]
167                 
168                 dom = xml.dom.minidom.parseString(data)
169                 for r in elementsWithTag(dom.childNodes, "rss"):
170                         rss = r
171
172                 items = [ ]
173                 
174                 # RSS 1.0
175                 for item in elementsWithTag(r.childNodes, "item"):
176                         items.append(item)
177
178                 # RSS 2.0
179                 for channel in elementsWithTag(r.childNodes, "channel"):
180                         for item in elementsWithTag(channel.childNodes, "item"):
181                                 items.append(item)
182
183                 for item in items:
184                         title = None
185                         link = ""
186                         enclosure = [ ]
187                         
188                         print "got item"
189
190                         for s in elementsWithTag(item.childNodes, lambda x: x in ["title", "link", "enclosure"]):
191                                 if s.tagName == "title":
192                                         title = mergeText(s.childNodes)
193                                 elif s.tagName == "link":
194                                         link = mergeText(s.childNodes)
195                                 elif s.tagName == "enclosure":
196                                         enclosure.append((s.getAttribute("url").encode("UTF-8"), s.getAttribute("type").encode("UTF-8")))
197
198                         print title, link, enclosure
199                         if title is None:
200                                 continue
201
202                         rss_entry = (title.encode("UTF-8"), link.encode("UTF-8"), enclosure)
203
204                         self.history.insert(0, rss_entry)
205
206                         if link not in self.last_links:
207                                 self.last_links.add(link)
208                                 new_items.append(rss_entry)
209                                 print "NEW", rss_entry[0], rss_entry[1]
210
211                 self.history = self.history[:self.MAX_HISTORY_ELEMENTS]
212                 
213                 if len(new_items):
214                         self.dialog = my_global_session.instantiateDialog(RSSDisplay, new_items)
215                         self.dialog.show()
216                         self.poll_timer.start(5000, 1)
217                 else:
218                         self.poll_timer.start(60000, 1)
219
220         def poll(self):
221                 if self.dialog:
222                         print "hiding"
223                         self.dialog.hide()
224                         self.dialog = None
225                         self.poll_timer.start(60000, 1)
226                 elif not my_global_session:
227                         print "no session yet."
228                         self.poll_timer.start(10000, 1)
229                 else:
230                         print "yes, session ok. starting"
231                         self.d = getPage(config.simpleRSS.hostname.value).addCallback(self._gotPage).addErrback(self.error)
232
233         def shutdown(self):
234                 self.poll_timer.timeout.get().remove(self.poll)
235                 self.poll_timer = None
236
237 def main(session, **kwargs):
238         print "session.open"
239         session.open(SimpleRSS)
240         print "done"
241
242 rssPoller = None
243
244 def autostart(reason, **kwargs):
245         global rssPoller
246         
247         print "autostart"
248
249         # ouch, this is a hack  
250         if kwargs.has_key("session"):
251                 global my_global_session
252                 print "session now available"
253                 my_global_session = kwargs["session"]
254                 return
255         
256         print "autostart"
257         if reason == 0:
258                 rssPoller = RSSPoller()
259         elif reason == 1:
260                 rssPoller.shutdown()
261                 rssPoller = None
262
263 def showCurrent(session, **kwargs):
264         global rssPoller
265         if rssPoller is None:
266                 return
267         session.open(RSSDisplay, rssPoller.history, interactive = True)
268
269 def Plugins(**kwargs):
270         return [ PluginDescriptor(name="RSS Reader", description="A (really) simple RSS reader", where = PluginDescriptor.WHERE_PLUGINMENU, fnc=main),
271                 PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
272                 PluginDescriptor(name="View RSS", description="Let's you view current RSS entries", where = PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=showCurrent) ]