simplerss: reread feeds on callback
[enigma2-plugins.git] / simplerss / src / RSSScreens.py
1 # for localized messages
2 from . import _
3
4 from enigma import eTimer
5
6 from Screens.Screen import Screen
7 from Screens.MessageBox import MessageBox
8
9 from Components.ActionMap import ActionMap
10 from Components.ScrollLabel import ScrollLabel
11 from Components.Sources.List import List
12 from Components.Sources.StaticText import StaticText
13
14 from RSSList import RSSFeedList
15
16 class RSSSummary(Screen):
17         skin = """
18         <screen position="0,0" size="132,64">
19                 <widget source="parent.Title" render="Label" position="6,4" size="120,21" font="Regular;18" />
20                 <widget source="entry" render="Label" position="6,25" size="120,21" font="Regular;16" />
21                 <widget source="global.CurrentTime" render="Label" position="56,46" size="82,18" font="Regular;16" >
22                         <convert type="ClockToText">WithSeconds</convert>
23                 </widget>
24         </screen>"""
25
26         def __init__(self, session, parent):
27                 Screen.__init__(self, session, parent = parent)
28                 self["entry"] = StaticText("")
29                 parent.onChangedEntry.append(self.selectionChanged)
30                 self.onShow.append(parent.updateInfo)
31                 self.onClose.append(self.removeWatcher)
32
33         def removeWatcher(self):
34                 self.parent.onChangedEntry.remove(self.selectionChanged)
35
36         def selectionChanged(self, text):
37                 self["entry"].text = text
38
39 class RSSBaseView(Screen):
40         """Base Screen for all Screens used in SimpleRSS"""
41
42         def __init__(self, session, poller, parent = None):
43                 Screen.__init__(self, session, parent)
44                 self.onChangedEntry = []
45                 self.rssPoller = poller
46                 self.pollDialog = None
47
48         def createSummary(self):
49                 return RSSSummary
50
51         def errorPolling(self, errmsg = ""):
52                 # An error occured while polling
53                 self.session.open(
54                         MessageBox,
55                         _("Error while parsing Feed, this usually means there is something wrong with it."),
56                         type = MessageBox.TYPE_ERROR,
57                         timeout = 3
58                 )
59
60                 # Don't show "we're updating"-dialog any longer
61                 if self.pollDialog:
62                         self.pollDialog.close()
63                         self.pollDialog = None
64
65         def singleUpdate(self, feedid, errback = None):
66                 # Don't do anything if we have no poller
67                 if self.rssPoller is None:
68                         return
69
70                 # Default errorback to self.errorPolling
71                 # If an empty errorback is wanted the Screen needs to provide it
72                 if errback is None:
73                         errback = self.errorPolling
74
75                 # Tell Poller to poll
76                 self.rssPoller.singlePoll(feedid, callback=True, errorback=errback)
77
78                 # Open Dialog and save locally
79                 self.pollDialog = self.session.open(
80                         MessageBox,
81                         _("Update is being done in Background.\nContents will automatically be updated when it's done."),
82                         type = MessageBox.TYPE_INFO,
83                         timeout = 5
84                 )
85
86         def selectEnclosure(self, enclosures):
87                 # Empty List
88                 if enclosures is None:
89                         return
90
91                 from Components.Scanner import openList
92
93                 if not openList(self.session, enclosures):
94                         self.session.open(
95                                 MessageBox,
96                                 _("Found no Enclosure we can display."),
97                                 type = MessageBox.TYPE_INFO,
98                                 timeout = 5
99                         )
100
101 class RSSEntryView(RSSBaseView):
102         """Shows a RSS Item"""
103
104         skin = """
105                 <screen position="center,center" size="460,420" title="Simple RSS Reader" >
106                         <widget source="info" render="Label" position="0,0" size="460, 20" halign="right" font="Regular; 18" />
107                         <widget name="content" position="0,20" size="460,400" font="Regular; 22" />
108                 </screen>"""
109
110         def __init__(self, session, data, feedTitle="", cur_idx=None, entries=None, parent=None):
111                 RSSBaseView.__init__(self, session, None, parent)
112
113                 self.data = data
114                 self.feedTitle = feedTitle
115                 self.cur_idx = cur_idx
116                 self.entries = entries
117
118                 if cur_idx is not None and entries is not None:
119                         self["info"] = StaticText(_("Entry %s/%s") % (cur_idx+1, entries))
120                 else:
121                         self["info"] = StaticText()
122
123                 if data:
124                         self["content"] = ScrollLabel(''.join((data[0], '\n\n', data[2], '\n\n', str(len(data[3])), ' ',  _("Enclosures"))))
125                 else:
126                         self["content"] = ScrollLabel()
127
128                 self["actions"] = ActionMap([ "OkCancelActions", "ChannelSelectBaseActions", "ColorActions", "DirectionActions" ],
129                 {
130                         "cancel": self.close,
131                         "ok": self.selectEnclosure,
132                         "yellow": self.selectEnclosure,
133                         "up": self.up,
134                         "down": self.down,
135                         "right": self.next,
136                         "left": self.previous,
137                         "nextBouquet": self.nextFeed,
138                         "prevBouquet": self.previousFeed,
139                 })
140
141                 self.onLayoutFinish.append(self.setConditionalTitle)
142
143         def setConditionalTitle(self):
144                 self.setTitle(_("Simple RSS Reader: %s") % (self.feedTitle))
145
146         def updateInfo(self):
147                 if self.data:
148                         text = self.data[0]
149                 else:
150                         text = _("No such Item.")
151
152                 for x in self.onChangedEntry:
153                         try:
154                                 x(text)
155                         except Exception:
156                                 pass
157
158         def up(self):
159                 self["content"].pageUp()
160
161         def down(self):
162                 self["content"].pageDown()
163
164         def next(self):
165                 if self.parent is not None:
166                         (self.data, self.cur_idx, self.entries) = self.parent.nextEntry()
167                         self.setContent()
168
169         def previous(self):
170                 if self.parent is not None:
171                         (self.data, self.cur_idx, self.entries) = self.parent.previousEntry()
172                         self.setContent()
173
174         def nextFeed(self):
175                 # Show next Feed
176                 if self.parent is not None:
177                         result = self.parent.next()
178                         self.feedTitle = result[0]
179                         self.entries = len(result[1])
180                         if self.entries:
181                                 self.cur_idx = 0
182                                 self.data = result[1][0]
183                         else:
184                                 self.cur_idx = None
185                                 self.data = None
186                         self.setConditionalTitle()
187                         self.setContent()
188
189         def previousFeed(self):
190                 # Show previous Feed
191                 if self.parent is not None:
192                         result = self.parent.previous()
193                         self.feedTitle = result[0]
194                         self.entries = len(result[1])
195                         if self.entries:
196                                 self.cur_idx = 0
197                                 self.data = result[1][0]
198                         else:
199                                 self.cur_idx = None
200                                 self.data = None
201                         self.setConditionalTitle()
202                         self.setContent()
203
204         def setContent(self):
205                 if self.cur_idx is not None and self.entries is not None:
206                         self["info"].text = _("Entry %s/%s") % (self.cur_idx+1, self.entries)
207                 else:
208                         self["info"].text = ""
209                 data = self.data
210                 if data:
211                         self["content"].setText(''.join((data[0], '\n\n', data[2], '\n\n', str(len(data[3])), ' ',  _("Enclosures"))))
212                 else:
213                         self["content"].setText(_("No such Item."))
214                 self.updateInfo()
215
216         def selectEnclosure(self):
217                 if self.data is not None:
218                         RSSBaseView.selectEnclosure(self, self.data[3])
219
220 class RSSFeedView(RSSBaseView):
221         """Shows a RSS-Feed"""
222
223         skin = """
224                 <screen position="center,center" size="460,415" title="Simple RSS Reader" >
225                         <widget source="info" render="Label" position="0,0" size="460,20" halign="right" font="Regular; 18" />
226                         <widget source="content" render="Listbox" position="0,20" size="460,300" scrollbarMode="showOnDemand">
227                                 <convert type="TemplatedMultiContent">
228                                         {"template": [
229                                                         MultiContentEntryText(pos=(0, 3), size=(460, 294), font=0, flags = RT_HALIGN_LEFT|RT_WRAP, text = 0)
230                                                 ],
231                                          "fonts": [gFont("Regular", 22)],
232                                          "itemHeight": 50
233                                         }
234                                 </convert>
235                         </widget>
236                         <widget source="summary" render="Label" position="0,320" size="460,95" font="Regular;16" />
237                 </screen>"""
238
239         def __init__(self, session, feed=None, newItems=False, parent=None, rssPoller=None,id=None):
240                 RSSBaseView.__init__(self, session, rssPoller, parent)
241
242                 self.feed = feed
243                 self.newItems = newItems
244                 self.id = id
245
246                 self["content"] = List(self.feed.history)
247                 self["summary"] = StaticText()
248                 self["info"] = StaticText()
249
250                 if not newItems:
251                         self["actions"] = ActionMap([ "OkCancelActions", "ChannelSelectBaseActions", "MenuActions", "ColorActions" ],
252                         {
253                                 "ok": self.showCurrentEntry,
254                                 "cancel": self.close,
255                                 "nextBouquet": self.next,
256                                 "prevBouquet": self.previous,
257                                 "menu": self.menu,
258                                 "yellow": self.selectEnclosure,
259                         })
260                         self.onLayoutFinish.append(self.__show)
261                         self.onClose.append(self.__close)
262
263                         self.timer = None
264                 else:
265                         self["actions"] = ActionMap([ "OkCancelActions" ],
266                         {
267                                 "cancel": self.close,
268                         })
269
270                         self.timer = eTimer()
271                         self.timer.callback.append(self.timerTick)
272                         self.onExecBegin.append(self.startTimer)
273
274                 self["content"].onSelectionChanged.append(self.updateInfo)
275                 self.onLayoutFinish.extend((
276                         self.updateInfo,
277                         self.setConditionalTitle
278                 ))
279
280         def startTimer(self):
281                 self.timer.startLongTimer(5)
282
283         def timerTick(self):
284                 self.timer.callback.remove(self.timerTick)
285
286                 self.close()
287
288         def __show(self):
289                 self.rssPoller.addCallback(self.pollCallback)
290
291         def __close(self):
292                 if self.timer is not None:
293                         self.timer.callback.remove(self.timerTick)
294                         self.timer = None
295                 self.rssPoller.removeCallback(self.pollCallback)
296
297         def pollCallback(self, id = None):
298                 print "[SimpleRSS] SimpleRSSFeed called back"
299
300                 if id is None or id+1 == self.id:
301                         self["content"].updateList(self.feed.history)
302                         self.setConditionalTitle()
303                         self.updateInfo()
304
305         def setConditionalTitle(self):
306                 self.setTitle(_("Simple RSS Reader: %s") % (self.feed.title))
307
308         def updateInfo(self):
309                 current_entry = self["content"].current
310                 if current_entry:
311                         self["summary"].text = current_entry[2]
312
313                         cur_idx = self["content"].index
314                         self["info"].text = _("Entry %s/%s") % (cur_idx+1, len(self.feed.history))
315                         summary_text = current_entry[0]
316                 else:
317                         self["summary"].text = _("Feed is empty.")
318                         self["info"].text = ""
319                         summary_text = _("Feed is empty.")
320
321                 for x in self.onChangedEntry:
322                         try:
323                                 x(summary_text)
324                         except Exception:
325                                 pass
326
327         def menu(self):
328                 if self.id > 0:
329                         self.singleUpdate(self.id-1)
330
331         def nextEntry(self):
332                 self["content"].selectNext()
333                 return (self["content"].current, self["content"].index, len(self.feed.history))
334
335         def previousEntry(self):
336                 self["content"].selectPrevious()
337                 return (self["content"].current, self["content"].index, len(self.feed.history))
338
339         def next(self):
340                 # Show next Feed
341                 if self.parent is not None:
342                         (self.feed, self.id) = self.parent.nextFeed()
343                         self["content"].list = self.feed.history
344                         self["content"].index = 0
345                         self.updateInfo()
346                         self.setConditionalTitle() # Update title
347                         return (self.feed.title, self.feed.history, self.id)
348                 return (self.feed.title, self.feed.history, self.id)
349
350         def previous(self):
351                 # Show previous Feed
352                 if self.parent is not None:
353                         (self.feed, self.id) = self.parent.previousFeed()
354                         self["content"].list = self.feed.history
355                         self["content"].index = 0
356                         self.updateInfo()
357                         self.setConditionalTitle() # Update title
358                         return (self.feed.title, self.feed.history, self.id)
359                 return (self.feed.title, self.feed.history, self.id)
360
361         def checkEmpty(self):
362                 if self.id > 0 and not len(self.feed.history):
363                         self.singleUpdate(self.id-1)
364
365         def showCurrentEntry(self):
366                 current_entry = self["content"].current
367                 if not current_entry: # empty list
368                         return
369
370                 self.session.openWithCallback(
371                         self.updateInfo,
372                         RSSEntryView,
373                         current_entry,
374                         cur_idx = self["content"].index,
375                         entries = len(self.feed.history),
376                         feedTitle = self.feed.title,
377                         parent = self
378                 )
379
380         def selectEnclosure(self):
381                 current_entry = self["content"].current
382                 if not current_entry: # empty list
383                         return
384
385                 RSSBaseView.selectEnclosure(self, current_entry[3])
386
387 class RSSOverview(RSSBaseView):
388         """Shows an Overview over all RSS-Feeds known to rssPoller"""
389
390         skin = """
391                 <screen position="center,center" size="460,415" title="Simple RSS Reader" >
392                         <widget source="info" render="Label" position="0,0" size="460,20" halign="right" font="Regular; 18" />
393                         <widget name="content" position="0,20" size="460,300" scrollbarMode="showOnDemand" />
394                         <widget source="summary" render="Label" position="0,320" size="460,95" font="Regular;16" />
395                 </screen>"""
396
397         def __init__(self, session, poller):
398                 RSSBaseView.__init__(self, session, poller)
399
400                 self["actions"] = ActionMap([ "OkCancelActions", "MenuActions", "ColorActions" ],
401                 {
402                         "ok": self.showCurrentEntry,
403                         "cancel": self.close,
404                         "menu": self.menu,
405                         "yellow": self.selectEnclosure,
406                 })
407
408                 self.fillFeeds()
409
410                 # We always have at least "New Items"-Feed
411                 self["content"] = RSSFeedList(self.feeds)
412                 self["summary"] = StaticText(' '.join((str(len(self.feeds[0][0].history)), _("Entries"))))
413                 self["info"] = StaticText(_("Feed %s/%s") % (1, len(self.feeds)))
414
415                 self["content"].connectSelChanged(self.updateInfo)
416                 self.onLayoutFinish.append(self.__show)
417                 self.onClose.append(self.__close)
418
419         def __show(self):
420                 self.rssPoller.addCallback(self.pollCallback)
421                 self.setTitle(_("Simple RSS Reader"))
422
423         def __close(self):
424                 self.rssPoller.removeCallback(self.pollCallback)
425
426         def fillFeeds(self):
427                 # Feedlist contains our virtual Feed and all real ones
428                 self.feeds = [(self.rssPoller.newItemFeed,)]
429                 self.feeds.extend([(feed,) for feed in self.rssPoller.feeds])
430
431         def pollCallback(self, id = None):
432                 print "[SimpleRSS] SimpleRSS called back"
433                 self.fillFeeds()
434                 self["content"].setList(self.feeds)
435                 self.updateInfo()
436                 self["content"].invalidate()
437
438         def updateInfo(self):
439                 current_entry = self["content"].getCurrent()
440                 self["summary"].text = ' '.join((str(len(current_entry.history)), _("Entries")))
441                 self["info"].text = _("Feed %s/%s") % (self["content"].getSelectedIndex()+1, len(self.feeds))
442                 summary_text = current_entry.title
443
444                 for x in self.onChangedEntry:
445                         try:
446                                 x(summary_text)
447                         except Exception:
448                                 pass
449
450         def menu(self):
451                 from Screens.ChoiceBox import ChoiceBox
452
453                 cur_idx = self["content"].getSelectedIndex()
454                 if cur_idx > 0:
455                         possible_actions = (
456                                 (_("Update Feed"), "update"),
457                                 (_("Setup"), "setup"),
458                                 (_("Close"), "close")
459                         )
460                 else:
461                         possible_actions = (
462                                 (_("Setup"), "setup"),
463                                 (_("Close"), "close")
464                         )
465
466                 self.session.openWithCallback(
467                         self.menuChoice,
468                         ChoiceBox,
469                         _("What to do?"),
470                         possible_actions
471                 )
472
473         def menuChoice(self, result):
474                 if result:
475                         if result[1] == "update":
476                                 cur_idx = self["content"].getSelectedIndex()
477                                 if cur_idx > 0:
478                                         self.singleUpdate(cur_idx-1)
479                         elif result[1] == "setup":
480                                 from RSSSetup import RSSSetup
481
482                                 self.session.openWithCallback(
483                                         self.refresh,
484                                         RSSSetup,
485                                         rssPoller=self.rssPoller
486                                 )
487                         elif result[1] == "close":
488                                 self.close()
489
490         def refresh(self):
491                 current_entry = self["content"].getCurrent()
492
493                 self.fillFeeds()
494                 self["content"].setList(self.feeds)
495
496                 self["content"].moveToEntry(current_entry)
497                 self.updateInfo()
498
499         def nextFeed(self):
500                 self["content"].up()
501                 return (self["content"].getCurrent(), self["content"].getSelectedIndex())
502
503         def previousFeed(self):
504                 self["content"].down()
505                 return (self["content"].getCurrent(), self["content"].getSelectedIndex())
506
507         def showCurrentEntry(self):
508                 current_entry = self["content"].getCurrent()
509                 self.session.openWithCallback(
510                         self.updateInfo,
511                         RSSFeedView,
512                         feed=current_entry,
513                         parent=self,
514                         rssPoller=self.rssPoller,
515                         id=self["content"].getSelectedIndex()
516                 )
517
518         def selectEnclosure(self):
519                 # Build a list of all enclosures in this feed
520                 enclosures = []
521                 for entry in self["content"].getCurrent().history:
522                                 enclosures.extend(entry[3])
523                 RSSBaseView.selectEnclosure(self, enclosures)
524