diff -r 000000000000 -r ca70ae20a155 src/extras/examples/rssreader.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extras/examples/rssreader.py Tue Feb 16 10:07:05 2010 +0530 @@ -0,0 +1,294 @@ +# A simple RSS reader application. + +# Copyright (c) 2005 Nokia Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import anydbm + +import e32 +import appuifw +from key_codes import * + +class RSSFeed: + def __init__(self,url,title=None): + self.url=url + if title is None: + title=url + self.listeners=[] + self.feed={'title': title, + 'entries': [], + 'busy': False} + self.updating=False + def listen(self,callback): + self.listeners.append(callback) + def _notify_listeners(self,*args): + for x in self.listeners: + x(*args) + def start_update(self): + if self.feed['busy']: + appuifw.note(u'Update already in progress','info') + return + self.feed['busy']=True + import thread + thread.start_new_thread(self._update,()) + def _update(self): + import dumbfeedparser as feedparser + newfeed=feedparser.parse(self.url) + self.feed.update(newfeed) + self.feed['busy']=False + self._notify_listeners() + def __getitem__(self,key): + return self.feed.__getitem__(key) + +class CachingRSSFeed(RSSFeed): + def __init__(self,cache,url,title=None): + RSSFeed.__init__(self,url,title) + self.cache=cache + if cache.has_key(url): + self.feed=eval(cache[url]) + self.dirty=False + RSSFeed.listen(self,self._invalidate_cache) + def _invalidate_cache(self): + self.dirty=True + # This method can't simply be a listener called by the RSSFeed, + # since that call is done in a different thread and currently the + # e32dbm module can only be used from the same thread it was + # opened in. + def save(self): + if self.dirty: + self.cache[self.url]=repr(self.feed) + + +def format_feed(feed): + if feed['busy']: + busyflag='(loading) ' + else: + busyflag='' + return unicode('%d: %s%s'%(len(feed['entries']), + busyflag, + feed['title'])) + +def handle_screen(mode): + appuifw.app.screen = mode + + +class ReaderApp: + def __init__(self,feedlist): + self.lock=e32.Ao_lock() + self.exit_flag=False + self.old_exit_key_handler=appuifw.app.exit_key_handler + self.old_app_body=appuifw.app.body + appuifw.app.exit_key_handler=self.handle_exit + self.feeds=feedlist + self.articleviewer=appuifw.Text() + self.feedmenu=appuifw.Listbox([u''], + self.handle_feedmenu_select) + self.articlemenu=appuifw.Listbox([u''], + self.handle_articlemenu_select) + screenmodemenu=(u'Screen mode', + ((u'full', lambda:handle_screen('full')), + (u'large', lambda:handle_screen('large')), + (u'normal', lambda:handle_screen('normal')))) + self.statemap={ + 'feedmenu': + {'menu':[(u'Update this feed', self.handle_update), + (u'Update all feeds', self.handle_update_all), + (u'Debug',self.handle_debug), + screenmodemenu, + (u'Exit',self.abort)], + 'exithandler': self.abort}, + 'articlemenu': + {'menu':[(u'Update this feed', self.handle_update), + (u'Update all feeds', self.handle_update_all), + screenmodemenu, + (u'Exit',self.abort)], + 'exithandler':lambda:self.goto_state('feedmenu')}, + 'articleview': + {'menu':[(u'Next article',self.handle_next), + (u'Previous article',self.handle_prev), + screenmodemenu, + (u'Exit',self.abort)], + 'exithandler':lambda:self.goto_state('articlemenu')}} + self.articleviewer.bind(EKeyDownArrow,self.handle_downarrow) + self.articleviewer.bind(EKeyUpArrow,self.handle_uparrow) + self.articleviewer.bind(EKeyLeftArrow,self.handle_exit) + self.articlemenu.bind(EKeyRightArrow, + self.handle_articlemenu_select) + self.articlemenu.bind(EKeyLeftArrow,self.handle_exit) + self.feedmenu.bind(EKeyRightArrow,self.handle_feedmenu_select) + for k in self.feeds: + k.listen(self.notify) + self.goto_state('feedmenu') + def abort(self): + self.exit_flag=True + self.lock.signal() + def close(self): + appuifw.app.menu=[] + appuifw.app.exit_key_handler=self.old_exit_key_handler + appuifw.app.body=self.old_app_body + def run(self): + try: + while not self.exit_flag: + self.lock.wait() + self.refresh() + finally: + self.close() + def notify(self): + self.lock.signal() + def refresh(self): + self.goto_state(self.state) + def goto_state(self,newstate): + # Workaround for a Series 60 bug: Prevent the cursor from + # showing up if the articleviewer widget is not visible. + self.articleviewer.focus=False + if newstate=='feedmenu': + self.feedmenu.set_list( + [format_feed(x) for x in self.feeds]) + appuifw.app.body=self.feedmenu + appuifw.app.title=u'RSS reader' + elif newstate=='articlemenu': + if len(self.current_feed['entries'])==0: + if appuifw.query(u'Download articles now?','query'): + self.handle_update() + self.goto_state('feedmenu') + return + self.articlemenu.set_list( + [self.format_article_title(x) + for x in self.current_feed['entries']]) + appuifw.app.body=self.articlemenu + appuifw.app.title=format_feed(self.current_feed) + elif newstate=='articleview': + self.articleviewer.clear() + self.articleviewer.add( + self.format_title_in_article(self.current_article())) + self.articleviewer.add( + self.format_article(self.current_article())) + self.articleviewer.set_pos(0) + appuifw.app.body=self.articleviewer + appuifw.app.title=self.format_article_title( + self.current_article()) + else: + raise RuntimeError("Invalid state %s"%state) + appuifw.app.menu=self.statemap[newstate]['menu'] + self.state=newstate + def current_article(self): + return self.current_feed['entries'][self.current_article_index] + def handle_update(self): + if self.state=='feedmenu': + self.current_feed=self.feeds[self.feedmenu.current()] + self.current_feed.start_update() + self.refresh() + def handle_update_all(self): + for k in self.feeds: + if not k['busy']: + k.start_update() + self.refresh() + def handle_feedmenu_select(self): + self.current_feed=self.feeds[self.feedmenu.current()] + self.goto_state('articlemenu') + def handle_articlemenu_select(self): + self.current_article_index=self.articlemenu.current() + self.goto_state('articleview') + def handle_debug(self): + import btconsole + btconsole.run('Entering debug mode.',locals()) + def handle_next(self): + if (self.current_article_index >= + len(self.current_feed['entries'])-1): + return + self.current_article_index += 1 + self.refresh() + def handle_prev(self): + if self.current_article_index == 0: + return + self.current_article_index -= 1 + self.refresh() + def handle_downarrow(self): + article_length=self.articleviewer.len() + cursor_pos=self.articleviewer.get_pos() + if cursor_pos==article_length: + self.handle_next() + else: + self.articleviewer.set_pos(min(cursor_pos+100, + article_length)) + def handle_uparrow(self): + cursor_pos=self.articleviewer.get_pos() + if cursor_pos==0: + self.handle_prev() + self.articleviewer.set_pos(self.articleviewer.len()) + else: + self.articleviewer.set_pos(max(cursor_pos-100,0)) + def format_title_in_article(self, article): + self.articleviewer.highlight_color = (255,240,80) + self.articleviewer.style = (appuifw.STYLE_UNDERLINE| + appuifw.HIGHLIGHT_ROUNDED) + self.articleviewer.font = 'title' + self.articleviewer.color = (0,0,255) + return unicode("%(title)s\n"%article) + + def format_article(self, article): + self.articleviewer.highlight_color = (0,0,0) + self.articleviewer.style = 0 + self.articleviewer.font = 'normal' + self.articleviewer.color = (0,0,0) + return unicode("%(summary)s"%article) + + def format_article_title(self, article): + return unicode("%(title)s"%article) + def handle_exit(self): + self.statemap[self.state]['exithandler']() + +class DummyFeed: + def __init__(self,data): self.data=data + def listen(self,callback): pass + def start_update(self): pass + def __getitem__(self,key): return self.data.__getitem__(key) + def save(self): pass +dummyfeed=DummyFeed({'title': 'Dummy feed', + 'entries': [{'title':'Dummy story', + 'summary':'Blah blah blah.'}, + {'title':'Another dummy story', + 'summary':'Foo, bar and baz.'}], + 'busy': False}) + +def main(): + old_title=appuifw.app.title + appuifw.app.title=u'RSS reader' + cache=anydbm.open(u'c:\\rsscache','c') + feeds=[ CachingRSSFeed(url='http://slashdot.org/index.rss', + title='Slashdot', + cache=cache), + CachingRSSFeed(url='http://news.bbc.co.uk/rss/newsonline_world_edition/front_page/rss091.xml', + title='BBC', + cache=cache), + dummyfeed] + app = ReaderApp(feeds) + # Import heavyweight modules in the background to improve application + # startup time. + def import_modules(): + import dumbfeedparser as feedparser + import btconsole + import thread + thread.start_new_thread(import_modules,()) + try: + app.run() + finally: + for feed in feeds: + feed.save() + cache.close() + appuifw.app.title=old_title + +if __name__=='__main__': + main() +