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