* Fix BSOD when opening enclosures of Feed
[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                 current_entry = self["content"].getCurrentEntry()
245
246                 if id is not None and self.id == id+1:
247                         print "[SimpleRSS] pollCallback recieved local feed", self.id
248                         self.feedTitle = self.rssPoller.feeds[id].title
249                         self.data = self.rssPoller.feeds[id].history
250                 elif self.id == 0:
251                         print "[SimpleRSS] pollCallback recieved all or non-local feed, updating active view (new_items)"
252                         self.data = self.rssPoller.new_items
253                 else:
254                         print "[SimpleRSS] pollCallback recieved all or non-local feed, updating", self.id
255                         self.feedTitle = self.rssPoller.feeds[self.id-1].title
256                         self.data = self.rssPoller.feeds[self-id-1].history
257
258                 self["content"].l.setList(self.data)
259                 self["content"].moveToEntry(current_entry)
260
261                 self.setConditionalTitle()
262                 self.updateInfo()
263
264         def setConditionalTitle(self):
265                 if not self.newItems:
266                         self.setTitle(': '.join(["Simple RSS Reader", self.feedTitle]))
267                 else:
268                         self.setTitle("Simple RSS Reader: New Items")
269
270         def updateInfo(self):
271                 current_entry = self["content"].getCurrentEntry()
272                 if current_entry:
273                         self["summary"].setText(current_entry[2])
274
275                         cur_idx = self["content"].getCurrentIndex()
276                         self["info"].setText("Entry %s/%s" % (cur_idx+1, len(self.data)))
277                 else:
278                         self["summary"].setText("Feed is empty.")
279                         self["info"].setText("")
280
281         def menu(self):
282                 if self.id > 0:
283                         self.singleUpdate(self.id-1)
284
285         def nextEntry(self):
286                 self["content"].moveDown()
287                 return (self["content"].getCurrentEntry(), self["content"].getCurrentIndex(), len(self.data))
288
289         def previousEntry(self):
290                 self["content"].moveUp()
291                 return (self["content"].getCurrentEntry(), self["content"].getCurrentIndex(), len(self.data))
292
293         # TODO: Fix moving back to previously marked entry (same goes for self.previous)
294         def next(self):
295                 # Show next Feed
296                 if self.parent is not None:
297                         result = self.parent.nextFeed()
298                         (self.feedTitle, self.data, self.id) = result
299                         #current_entry = self["content"].getCurrentEntry()
300                         self["content"].l.setList(self.data) # Update list
301                         self["content"].moveToIndex(0)
302                         #self["content"].moveToEntry(current_entry)
303                         self.updateInfo() # In case entry is no longer in history
304                         self.setConditionalTitle() # Update title
305                         return result
306                 return (self.feedTitle, self.data, self.id)
307
308         def previous(self):
309                 # Show previous Feed
310                 if self.parent is not None:
311                         result = self.parent.previousFeed()
312                         (self.feedTitle, self.data, self.id) = result
313                         #current_entry = self["content"].getCurrentEntry()
314                         self["content"].l.setList(self.data) # Update list
315                         self["content"].moveToIndex(0)
316                         #self["content"].moveToEntry(current_entry)
317                         self.updateInfo() # In case entry is no longer in history
318                         self.setConditionalTitle() # Update title
319                         return result
320                 return (self.feedTitle, self.data, self.id)
321
322         def checkEmpty(self):
323                 if self.id > 0 and not len(self.data):
324                         self.singleUpdate(self.id-1)
325
326         def showCurrentEntry(self):
327                 current_entry = self["content"].getCurrentEntry()
328                 if current_entry is None: # empty list
329                         return
330
331                 self.session.openWithCallback(
332                         self.updateInfo,
333                         RSSEntryView,
334                         current_entry,
335                         cur_idx=self["content"].getCurrentIndex(),
336                         entries=len(self.data),
337                         feedTitle=self.feedTitle,
338                         parent=self
339                 )
340
341         def selectEnclosure(self):
342                 current_entry = self["content"].getCurrentEntry()
343                 if current_entry is None: # empty list
344                         return
345
346                 RSSBaseView.selectEnclosure(self, current_entry[3])
347
348 class RSSOverview(RSSBaseView):
349         """Shows an Overview over all RSS-Feeds known to rssPoller"""
350         skin = """
351                 <screen position="100,100" size="460,415" title="Simple RSS Reader" >
352                         <widget name="info" position="0,0" size="460,20" halign="right" font="Regular; 18" />
353                         <widget name="content" position="0,20" size="460,300" scrollbarMode="showOnDemand" />
354                         <widget name="summary" position="0,320" size="460,95" font="Regular;16" />
355                 </screen>"""
356
357         def __init__(self, session, poller):
358                 RSSBaseView.__init__(self, session, poller)
359
360                 self["actions"] = ActionMap([ "OkCancelActions", "MenuActions", "ColorActions" ], 
361                 {
362                         "ok": self.showCurrentEntry,
363                         "cancel": self.close,
364                         "menu": self.menu,
365                         "yellow": self.selectEnclosure,
366                 })
367
368                 self.fillFeeds()
369
370                 # We always have at least "New Items"-Feed
371                 self["content"] = RSSFeedList(self.feeds)
372                 self["summary"] = Label(' '.join([str(len(self.feeds[0][0].history)), "Entries"]))
373                 self["info"] = Label("Feed 1/%s" % len(self.feeds))
374
375                 self["content"].connectSelChanged(self.updateInfo)
376                 self.onLayoutFinish.append(self.__show)
377                 self.onClose.append(self.__close)
378
379         def __show(self):
380                 self.rssPoller.addCallback(self.pollCallback)
381
382         def __close(self):
383                 self.rssPoller.removeCallback(self.pollCallback)
384
385         def fillFeeds(self):
386                 # Build virtual "new item"-Feed
387                 newItemFeed = BaseFeed("", False)
388                 newItemFeed.title = "New Items"
389                 newItemFeed.description = "New Items since last Auto-Update"
390                 newItemFeed.history = self.rssPoller.new_items
391
392                 # Feedlist contains our virtual Feed and all real ones
393                 self.feeds = [(newItemFeed,)]
394                 self.feeds.extend([(feed,) for feed in self.rssPoller.feeds])
395
396         def pollCallback(self, id = None):
397                 print "[SimpleRSS] SimpleRSS called back"
398                 self.updateInfo()
399                 self["content"].invalidate()
400
401         def updateInfo(self):
402                 current_entry = self["content"].getCurrentEntry()
403                 self["summary"].setText(' '.join([str(len(current_entry.history)), "Entries"]))
404                 self["info"].setText("Feed %s/%s" % (self["content"].getCurrentIndex()+1, len(self.feeds)))
405
406         def menu(self):
407                 cur_idx = self["content"].getCurrentIndex()
408                 if cur_idx > 0:
409                         possible_actions = [
410                                 (_("Update Feed"), "update"),
411                                 (_("Setup"), "setup"),
412                                 (_("Close"), "close")
413                         ]
414                 else:
415                         possible_actions = [
416                                 (_("Setup"), "setup"),
417                                 (_("Close"), "close")
418                         ]
419                 self.session.openWithCallback(
420                         self.menuChoice,
421                         ChoiceBox,
422                         "What to do?",
423                         possible_actions
424                 )
425
426         def menuChoice(self, result):
427                 if result:
428                         if result[1] == "update":
429                                 cur_idx = self["content"].getCurrentIndex()
430                                 if cur_idx > 0:
431                                         self.singleUpdate(cur_idx-1)
432                         elif result[1] == "setup":
433                                 self.session.openWithCallback(
434                                         self.refresh,
435                                         RSSSetup,
436                                         rssPoller=self.rssPoller
437                                 )
438                         elif result[1] == "close":
439                                 self.close()
440
441         def refresh(self):
442                 current_entry = self["content"].getCurrentEntry()
443
444                 self.fillFeeds()
445                 self["content"].l.setList(self.feeds)
446
447                 self["content"].moveToEntry(current_entry)
448                 self.updateInfo()
449
450         def nextFeed(self):
451                 self["content"].moveUp()
452                 current_entry = self["content"].getCurrentEntry()
453                 return (current_entry.title, current_entry.history, self["content"].getCurrentIndex())
454
455         def previousFeed(self):
456                 self["content"].moveDown()
457                 current_entry = self["content"].getCurrentEntry()
458                 return (current_entry.title, current_entry.history, self["content"].getCurrentIndex())
459
460         def showCurrentEntry(self):
461                 current_entry = self["content"].getCurrentEntry()
462                 self.session.openWithCallback(
463                         self.updateInfo,
464                         RSSFeedView,
465                         current_entry.history,
466                         parent=self,
467                         feedTitle=current_entry.title,
468                         rssPoller=self.rssPoller,
469                         id=self["content"].getCurrentIndex()
470                 )
471
472         def selectEnclosure(self):
473                 # Build a list of all enclosures in this feed
474                 enclosures = []
475                 for entry in self["content"].getCurrentEntry().history:
476                                 enclosures.extend(entry[3])
477                 RSSBaseView.selectEnclosure(self, enclosures)