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