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