Add possibility to show image-enclosures,
[enigma2-plugins.git] / simplerss / src / RSSScreens.py
1 from Screens.Screen import Screen
2 from Screens.MessageBox import MessageBox
3 from Screens.ChoiceBox import ChoiceBox
4 from Components.ActionMap import ActionMap
5 from Components.Label import Label
6 from Components.ScrollLabel import ScrollLabel
7 from Components.Pixmap import Pixmap
8
9 from RSSList import RSSList
10 from RSSSetup import RSSSetup
11
12 class PictureView(Screen):
13         """Downloads a Picture, shows it and delete the temporary file"""
14
15         skin = """
16                 <screen position="100,100" size="460,400" title="Simple RSS Reader" >
17                         <widget name="content" position="0,0" size="460,400" alphatest="on"/>
18                 </screen>"""
19
20         filename = '/tmp/simplerss_enclosure'
21
22         def __init__(self, session, url):
23                 Screen.__init__(self, session)
24
25                 self.url = url
26
27                 self["actions"] = ActionMap([ "OkCancelActions" ], 
28                 {
29                         "ok": self.close,
30                         "cancel": self.close,
31                 })
32
33                 self["content"] = Pixmap()
34
35                 self.onLayoutFinish.append(self.fetchFile)
36
37         def fetchFile(self):
38                 # Fetch file
39                 from httpclient import getFile
40                 getFile(self.filename, self.url, callback=self.gotFile, errorback=self.error)
41
42         def gotFile(self, data = ""):
43                 # Determine Aspect
44                 from Components.AVSwitch import AVSwitch
45                 aspect = AVSwitch().getAspectRatioSetting()/2
46                 # Load Picture
47                 from enigma import loadPic
48                 ptr = loadPic(self.filename, 460, 400, aspect)
49                 # Show Picture
50                 self["content"].instance.setPixmap(ptr)
51                 # Remove Temporary File
52                 from os import unlink
53                 unlink(self.filename)
54
55         def error(self):
56                 self.session.open(
57                         MessageBox,
58                         "Error while loading Picture.",
59                         type = MessageBox.TYPE_ERROR,
60                         timeout = 3
61                 )
62                 self.close()
63
64 class RSSBaseView(Screen):
65         """Base Screen for all Screens used in SimpleRSS"""
66
67         def __init__(self, session):
68                 Screen.__init__(self, session)
69
70         def errorPolling(self, errmsg = ""):
71                 self.session.open(
72                         MessageBox,
73                         "Error while parsing Feed, this usually means there is something wrong with it.",
74                         type = MessageBox.TYPE_ERROR,
75                         timeout = 3
76                 )
77
78         def singleUpdate(self, feedid, errback = None):
79                 # Default errorback to self.errorPolling
80                 # If an empty errorback is wanted the Screen needs to provide it
81                 if errback is None:
82                         errback = self.errorPolling
83                 self.rssPoller.singlePoll(feedid, callback=True, errorback=errback)
84                 self.session.open(
85                         MessageBox,
86                         "Update is being done in Background.\nContents will automatically be updated when it's done.",
87                         type = MessageBox.TYPE_INFO,
88                         timeout = 5
89                 )
90
91         def selectEnclosure(self, enclosures):
92                 # Empty List
93                 if enclosures is None:
94                         return
95
96                 count = len(enclosures)
97                 # Select stream in ChoiceBox if more than one present
98                 if count > 1:
99                         self.session.openWithCallback(
100                                 self.enclosureSelected,
101                                 ChoiceBox,
102                                 "Select enclosure to play",
103                                 [(x[0][x[0].rfind("/")+1:].replace('%20', ' '), x) for x in enclosures]
104                         )
105                 # Play if one present
106                 elif count:
107                         self.enclosureSelected((None, enclosures[0]))
108
109         def enclosureSelected(self, enclosure):
110                 if enclosure:
111                         (url, type) = enclosure[1]
112
113                         print "[SimpleRSS] Trying to play back enclosure: url=%s, type=%s" % (url, type)
114
115                         if type in ["video/mpeg", "audio/mpeg"]:
116                                 # We should launch a Player here, but the MediaPlayer gets angry about our non-local sources
117                                 from enigma import eServiceReference
118                                 self.session.nav.playService(eServiceReference(4097, 0, url))
119                         elif type in ["image/jpeg", "image/png", "image/gif", "image/bmp"]:
120                                 self.session.open(PictureView, url)
121
122 class RSSEntryView(RSSBaseView):
123         """Shows a RSS Item"""
124         skin = """
125                 <screen position="100,100" size="460,420" title="Simple RSS Reader" >
126                         <widget name="info" position="0,0" size="460, 20" halign="right" font="Regular; 18" />
127                         <widget name="content" position="0,20" size="460,420" font="Regular; 22" />
128                 </screen>"""
129
130         def __init__(self, session, data, feedTitle="", cur_idx=None, entries=None, nextEntryCB=None, previousEntryCB=None, nextFeedCB=None, previousFeedCB=None):
131                 RSSBaseView.__init__(self, session)
132
133                 self.data = data
134                 self.feedTitle = feedTitle
135                 self.nextEntryCB = nextEntryCB
136                 self.previousEntryCB = previousEntryCB
137                 self.nextFeedCB = nextFeedCB
138                 self.previousFeedCB = previousFeedCB
139                 self.cur_idx = cur_idx
140                 self.entries = entries
141
142                 if cur_idx is not None and entries is not None:
143                         self["info"] = Label("Entry %s/%s" % (cur_idx+1, entries))
144                 else:
145                         self["info"] = Label()
146
147                 if data is not None:
148                         self["content"] = ScrollLabel("\n\n".join([data[0], data[2], " ".join([str(len(data[3])), "Enclosures"])]))
149                 else:
150                         self["content"] = ScrollLabel()
151
152                 self["actions"] = ActionMap([ "OkCancelActions", "ChannelSelectBaseActions", "ColorActions", "DirectionActions" ],
153                 {
154                         "cancel": self.close,
155                         "ok": self.selectEnclosure,
156                         "yellow": self.selectEnclosure,
157                         "up": self.up,
158                         "down": self.down,
159                         "right": self.next,
160                         "left": self.previous,
161                         "nextBouquet": self.nextFeed,
162                         "prevBouquet": self.previousFeed,
163                 })
164
165                 self.onLayoutFinish.append(self.setConditionalTitle)
166
167         def setConditionalTitle(self):
168                 self.setTitle(': '.join(["Simple RSS Reader", self.feedTitle]))
169
170         def up(self):
171                 self["content"].pageUp()
172
173         def down(self):
174                 self["content"].pageDown()
175
176         def next(self):
177                 if self.nextEntryCB is not None:
178                         (self.data, self.cur_idx, self.entries) = self.nextEntryCB()
179                         self.setContent()
180
181         def previous(self):
182                 if self.previousEntryCB is not None:
183                         (self.data, self.cur_idx, self.entries) = self.previousEntryCB()
184                         self.setContent()
185
186         def nextFeed(self):
187                 # Show next Feed
188                 if self.nextFeedCB is not None:
189                         result = self.nextFeedCB()
190                         self.feedTitle = result[0]
191                         self.entries = len(result[1])
192                         if self.entries:
193                                 self.cur_idx = 0
194                                 self.data = result[1][0]
195                         else:
196                                 self.cur_idx = None
197                                 self.data = None
198                         self.setConditionalTitle()
199                         self.setContent()
200
201         def previousFeed(self):
202                 # Show previous Feed
203                 if self.previousFeedCB is not None:
204                         result = self.previousFeedCB()
205                         self.feedTitle = result[0]
206                         self.entries = len(result[1])
207                         if self.entries:
208                                 self.cur_idx = 0
209                                 self.data = result[1][0]
210                         else:
211                                 self.cur_idx = None
212                                 self.data = None
213                         self.setConditionalTitle()
214                         self.setContent()
215
216         def setContent(self):
217                 if self.cur_idx is not None and self.entries is not None:
218                         self["info"].setText("Entry %s/%s" % (self.cur_idx+1, self.entries))
219                 else:
220                         self["info"].setText("")
221                 if self.data is not None:
222                         self["content"].setText("\n\n".join([self.data[0], self.data[2], " ".join([str(len(self.data[3])), "Enclosures"])]))
223                 else:
224                         self["content"].setText("No such Item.")
225
226         def selectEnclosure(self):
227                 if self.data is not None:
228                         RSSBaseView.selectEnclosure(self, self.data[3])
229
230 class RSSFeedView(RSSBaseView):
231         """Shows a RSS-Feed"""
232         skin = """
233                 <screen position="100,100" size="460,420" title="Simple RSS Reader" >
234                         <widget name="info" position="0,0" size="460,20" halign="right" font="Regular; 18" />
235                         <widget name="content" position="0,20" size="460,324" scrollbarMode="showOnDemand" />
236                         <widget name="summary" position="0,325" size="460,95" font="Regular;16" />
237                 </screen>"""
238
239         def __init__(self, session, data, feedTitle = "", newItems=False, nextFeedCB=None, previousFeedCB=None, rssPoller=None, id = None):
240                 RSSBaseView.__init__(self, session)
241
242                 self.data = data
243                 self.feedTitle = feedTitle
244                 self.newItems = newItems
245                 self.id = id
246                 self.nextFeedCB=nextFeedCB
247                 self.previousFeedCB=previousFeedCB
248                 self.rssPoller=rssPoller
249
250                 self["content"] = RSSList(data)
251                 self["summary"] = Label()
252                 self["info"] = Label()
253
254                 if not newItems:
255                         self["actions"] = ActionMap([ "OkCancelActions", "ChannelSelectBaseActions", "MenuActions", "ColorActions" ], 
256                         {
257                                 "ok": self.showCurrentEntry,
258                                 "cancel": self.close,
259                                 "nextBouquet": self.next,
260                                 "prevBouquet": self.previous,
261                                 "menu": self.menu,
262                                 "yellow": self.selectEnclosure,
263                         })
264                         self.onShown.append(self.__show)
265                         self.onClose.append(self.__close)
266                 
267                 self["content"].connectSelChanged(self.updateInfo)
268                 self.onLayoutFinish.extend([self.updateInfo, self.setConditionalTitle])
269
270         def __show(self):
271                 self.rssPoller.addCallback(self.pollCallback)
272
273         def __close(self):
274                 self.rssPoller.removeCallback(self.pollCallback)
275
276         def pollCallback(self, id = None):
277                 print "[SimpleRSS] SimpleRSSFeed called back"
278                 current_entry = self["content"].getCurrentEntry()
279
280                 if id is not None and self.id == id+1:
281                         print "[SimpleRSS] pollCallback recieved local feed", self.id
282                         self.feedTitle = self.rssPoller.feeds[id].title
283                         self.data = self.rssPoller.feeds[id].history
284                 elif self.id == 0:
285                         print "[SimpleRSS] pollCallback recieved all or non-local feed, updating active view (new_items)"
286                         self.data = self.rssPoller.new_items
287                 else:
288                         print "[SimpleRSS] pollCallback recieved all or non-local feed, updating", self.id
289                         self.feedTitle = self.rssPoller.feeds[self.id-1].title
290                         self.data = self.rssPoller.feeds[self-id-1].history
291
292                 self["content"].l.setList(self.data)
293                 self["content"].moveToEntry(current_entry)
294
295                 self.setConditionalTitle()
296                 self.updateInfo()
297
298         def setConditionalTitle(self):
299                 if not self.newItems:
300                         self.setTitle(': '.join(["Simple RSS Reader", self.feedTitle]))
301                 else:
302                         self.setTitle("Simple RSS Reader: New Items")
303
304         def updateInfo(self):
305                 current_entry = self["content"].getCurrentEntry()
306                 if current_entry:
307                         self["summary"].setText(current_entry[2])
308
309                         cur_idx = self["content"].getCurrentIndex()
310                         self["info"].setText("Entry %s/%s" % (cur_idx+1, len(self.data)))
311                 else:
312                         self["summary"].setText("Feed is empty.")
313                         self["info"].setText("")
314
315         def menu(self):
316                 if self.id > 0:
317                         self.singleUpdate(self.id-1)
318
319         def nextEntryCB(self):
320                 self["content"].moveDown()
321                 return (self["content"].getCurrentEntry(), self["content"].getCurrentIndex(), len(self.data))
322
323         def previousEntryCB(self):
324                 self["content"].moveUp()
325                 return (self["content"].getCurrentEntry(), self["content"].getCurrentIndex(), len(self.data))
326
327         # TODO: Fix moving back to previously marked entry (same goes for self.previous)
328         def next(self):
329                 # Show next Feed
330                 if self.nextFeedCB is not None:
331                         result = self.nextFeedCB()
332                         (self.feedTitle, self.data, self.id) = result
333                         #current_entry = self["content"].getCurrentEntry()
334                         self["content"].l.setList(self.data) # Update list
335                         self["content"].moveToIndex(0)
336                         #self["content"].moveToEntry(current_entry)
337                         self.updateInfo() # In case entry is no longer in history
338                         self.setConditionalTitle() # Update title
339                         return result
340                 return (self.feedTitle, self.data, self.id)
341
342         def previous(self):
343                 # Show previous Feed
344                 if self.previousFeedCB is not None:
345                         result = self.previousFeedCB()
346                         (self.feedTitle, self.data, self.id) = result
347                         #current_entry = self["content"].getCurrentEntry()
348                         self["content"].l.setList(self.data) # Update list
349                         self["content"].moveToIndex(0)
350                         #self["content"].moveToEntry(current_entry)
351                         self.updateInfo() # In case entry is no longer in history
352                         self.setConditionalTitle() # Update title
353                         return result
354                 return (self.feedTitle, self.data, self.id)
355
356         def showCurrentEntry(self):
357                 current_entry = self["content"].getCurrentEntry()
358                 if current_entry is None: # empty list
359                         return
360
361                 self.session.openWithCallback(
362                         self.updateInfo,
363                         RSSEntryView,
364                         current_entry,
365                         cur_idx=self["content"].getCurrentIndex(),
366                         entries=len(self.data),
367                         feedTitle=self.feedTitle,
368                         nextEntryCB=self.nextEntryCB,
369                         previousEntryCB=self.previousEntryCB,
370                         nextFeedCB=self.next,
371                         previousFeedCB=self.previous
372                 )
373
374         def selectEnclosure(self):
375                 current_entry = self["content"].getCurrentEntry()
376                 if current_entry is None: # empty list
377                         return
378
379                 RSSBaseView.selectEnclosure(self, current_entry[3])
380
381 class RSSOverview(RSSBaseView):
382         """Shows an Overview over all RSS-Feeds known to rssPoller"""
383         skin = """
384                 <screen position="100,100" size="460,420" title="Simple RSS Reader" >
385                         <widget name="info" position="0,0" size="460,20" halign="right" font="Regular; 18" />
386                         <widget name="content" position="0,20" size="460,304" scrollbarMode="showOnDemand" />
387                         <widget name="summary" position="0,325" size="460,95" font="Regular;16" />
388                 </screen>"""
389
390         def __init__(self, session, poller):
391                 RSSBaseView.__init__(self, session)
392
393                 self.rssPoller = poller
394                 
395                 self["actions"] = ActionMap([ "OkCancelActions", "MenuActions", "ColorActions" ], 
396                 {
397                         "ok": self.showCurrentEntry,
398                         "cancel": self.close,
399                         "menu": self.menu,
400                         "yellow": self.selectEnclosure,
401                 })
402
403                 self.fillFeeds()
404
405                 # We always have at least "New Items"-Feed
406                 self["content"] = RSSList(self.feeds)
407                 self["summary"] = Label(self.feeds[0][2])
408                 self["info"] = Label("Feed 1/%s" % len(self.feeds))
409
410                 self["content"].connectSelChanged(self.updateInfo)
411                 self.onShown.append(self.__show)
412                 self.onClose.append(self.__close)
413
414         def __show(self):
415                 self.rssPoller.addCallback(self.pollCallback)
416
417         def __close(self):
418                 self.rssPoller.removeCallback(self.pollCallback)
419
420         def fillFeeds(self):
421                 self.feeds = [(
422                         "New Items",
423                         "New Items since last Auto-Update",
424                         ' '.join([str(len(self.rssPoller.new_items)), "Entries"]),
425                         self.rssPoller.new_items
426                 )]
427                 self.feeds.extend([
428                         (
429                                 feed.title,
430                                 feed.description,
431                                 ' '.join([str(len(feed.history)), "Entries"]),
432                                 feed.history
433                         )
434                                 for feed in self.rssPoller.feeds
435                 ])
436
437         def pollCallback(self, id = None):
438                 print "[SimpleRSS] SimpleRSS called back"
439                 current_entry = self["content"].getCurrentEntry()
440
441                 if id is not None:
442                         print "[SimpleRSS] pollCallback updating feed", id
443                         self.feeds[id+1] = (
444                                 self.rssPoller.feeds[id].title,
445                                 self.rssPoller.feeds[id].description,
446                                 ' '.join([str(len(self.rssPoller.feeds[id].history)), "Entries"]),
447                                 self.rssPoller.feeds[id].history
448                         )
449                 else:
450                         print "[SimpleRSS] pollCallback updating all feeds"
451                         self.fillFeeds()
452
453                 self["content"].l.setList(self.feeds)
454                 self["content"].moveToEntry(current_entry)
455
456                 self.updateInfo()
457
458         def updateInfo(self):
459                 current_entry = self["content"].getCurrentEntry()
460                 if current_entry:
461                         self["summary"].setText(current_entry[2])
462                         self["info"].setText("Feed %s/%s" % (self["content"].getCurrentIndex()+1, len(self.feeds)))
463                 # Should never happen
464                 else:
465                         self["summary"].setText("")
466                         self["info"].setText("")
467
468         def menu(self):
469                 cur_idx = self["content"].getCurrentIndex()
470                 if cur_idx > 0:
471                         possible_actions = [
472                                 (_("Update Feed"), "update"),
473                                 (_("Setup"), "setup"),
474                                 (_("Close"), "close")
475                         ]
476                 else:
477                         possible_actions = [
478                                 (_("Setup"), "setup"),
479                                 (_("Close"), "close")
480                         ]
481                 self.session.openWithCallback(
482                         self.menuChoice,
483                         ChoiceBox,
484                         "What to do?",
485                         possible_actions
486                 )
487
488         def menuChoice(self, result):
489                 if result:
490                         if result[1] == "update":
491                                 cur_idx = self["content"].getCurrentIndex()
492                                 if cur_idx > 0:
493                                         self.singleUpdate(cur_idx-1)
494                         elif result[1] == "setup":
495                                 self.session.openWithCallback(self.refresh, RSSSetup, rssPoller=self.rssPoller)
496                         elif result[1] == "close":
497                                 self.close()
498
499         def refresh(self):
500                 current_entry = self["content"].getCurrentEntry()
501
502                 self.fillFeeds()
503                 self["content"].l.setList(self.feeds)
504
505                 self["content"].moveToEntry(current_entry)
506                 self.updateInfo()
507
508         def nextFeedCB(self):
509                 self["content"].moveUp()
510                 current_entry = self["content"].getCurrentEntry()
511                 return (current_entry[0], current_entry[3], self["content"].getCurrentIndex())
512
513         def previousFeedCB(self):
514                 self["content"].moveDown()
515                 current_entry = self["content"].getCurrentEntry()
516                 return (current_entry[0], current_entry[3], self["content"].getCurrentIndex())
517
518         def showCurrentEntry(self):
519                 current_entry = self["content"].getCurrentEntry()
520                 if current_entry is None: # empty list
521                         return
522
523                 self.session.openWithCallback(
524                         self.refresh,
525                         RSSFeedView,
526                         current_entry[3],
527                         feedTitle=current_entry[0],
528                         nextFeedCB=self.nextFeedCB,
529                         previousFeedCB=self.previousFeedCB,
530                         rssPoller=self.rssPoller,
531                         id=self["content"].getCurrentIndex()
532                 )
533
534         def selectEnclosure(self):
535                 current_entry = self["content"].getCurrentEntry()
536                 if current_entry is None: # empty list
537                         return
538
539                 # Build a list of all enclosures in this feed
540                 enclosures = []
541                 for entry in current_entry[3]:
542                                 enclosures.extend(entry[3])
543                 RSSBaseView.selectEnclosure(self, enclosures)