src/extras/examples/rssreader.py
changeset 0 ca70ae20a155
equal deleted inserted replaced
-1:000000000000 0:ca70ae20a155
       
     1 # A simple RSS reader application.
       
     2 
       
     3 # Copyright (c) 2005 Nokia Corporation
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 
       
    17 import anydbm
       
    18 
       
    19 import e32
       
    20 import appuifw
       
    21 from key_codes import *
       
    22 
       
    23 class RSSFeed:
       
    24     def __init__(self,url,title=None):
       
    25         self.url=url
       
    26         if title is None:
       
    27             title=url
       
    28         self.listeners=[]
       
    29         self.feed={'title': title,
       
    30                    'entries': [],
       
    31                    'busy': False}
       
    32         self.updating=False
       
    33     def listen(self,callback):
       
    34         self.listeners.append(callback)
       
    35     def _notify_listeners(self,*args):
       
    36         for x in self.listeners:
       
    37             x(*args)
       
    38     def start_update(self):
       
    39         if self.feed['busy']:
       
    40             appuifw.note(u'Update already in progress','info')
       
    41             return
       
    42         self.feed['busy']=True
       
    43         import thread
       
    44         thread.start_new_thread(self._update,())
       
    45     def _update(self):
       
    46         import dumbfeedparser as feedparser
       
    47         newfeed=feedparser.parse(self.url)
       
    48         self.feed.update(newfeed)
       
    49         self.feed['busy']=False
       
    50         self._notify_listeners()
       
    51     def __getitem__(self,key):
       
    52         return self.feed.__getitem__(key)
       
    53 
       
    54 class CachingRSSFeed(RSSFeed):
       
    55     def __init__(self,cache,url,title=None):
       
    56         RSSFeed.__init__(self,url,title)
       
    57         self.cache=cache
       
    58         if cache.has_key(url):
       
    59             self.feed=eval(cache[url])
       
    60         self.dirty=False
       
    61         RSSFeed.listen(self,self._invalidate_cache)
       
    62     def _invalidate_cache(self):
       
    63         self.dirty=True
       
    64     # This method can't simply be a listener called by the RSSFeed,
       
    65     # since that call is done in a different thread and currently the
       
    66     # e32dbm module can only be used from the same thread it was
       
    67     # opened in.
       
    68     def save(self):
       
    69         if self.dirty:
       
    70             self.cache[self.url]=repr(self.feed)
       
    71 
       
    72 
       
    73 def format_feed(feed):
       
    74     if feed['busy']:
       
    75         busyflag='(loading) '
       
    76     else:
       
    77         busyflag=''
       
    78     return unicode('%d: %s%s'%(len(feed['entries']),
       
    79                                busyflag,
       
    80                                feed['title']))
       
    81 
       
    82 def handle_screen(mode):
       
    83     appuifw.app.screen = mode
       
    84 
       
    85 
       
    86 class ReaderApp:
       
    87     def __init__(self,feedlist):
       
    88         self.lock=e32.Ao_lock()
       
    89         self.exit_flag=False
       
    90         self.old_exit_key_handler=appuifw.app.exit_key_handler
       
    91         self.old_app_body=appuifw.app.body
       
    92         appuifw.app.exit_key_handler=self.handle_exit
       
    93         self.feeds=feedlist
       
    94         self.articleviewer=appuifw.Text()
       
    95         self.feedmenu=appuifw.Listbox([u''],
       
    96                                       self.handle_feedmenu_select)
       
    97         self.articlemenu=appuifw.Listbox([u''],
       
    98                                          self.handle_articlemenu_select)
       
    99         screenmodemenu=(u'Screen mode',
       
   100                         ((u'full', lambda:handle_screen('full')),
       
   101                          (u'large', lambda:handle_screen('large')),
       
   102                          (u'normal', lambda:handle_screen('normal'))))
       
   103         self.statemap={
       
   104             'feedmenu':
       
   105             {'menu':[(u'Update this feed', self.handle_update),
       
   106                      (u'Update all feeds', self.handle_update_all),
       
   107                      (u'Debug',self.handle_debug),
       
   108                      screenmodemenu,
       
   109                      (u'Exit',self.abort)],
       
   110              'exithandler': self.abort},
       
   111             'articlemenu':
       
   112             {'menu':[(u'Update this feed', self.handle_update),
       
   113                      (u'Update all feeds', self.handle_update_all),
       
   114                      screenmodemenu,
       
   115                      (u'Exit',self.abort)],
       
   116              'exithandler':lambda:self.goto_state('feedmenu')},
       
   117             'articleview':
       
   118             {'menu':[(u'Next article',self.handle_next),
       
   119                      (u'Previous article',self.handle_prev),
       
   120                      screenmodemenu,
       
   121                      (u'Exit',self.abort)],
       
   122              'exithandler':lambda:self.goto_state('articlemenu')}}
       
   123         self.articleviewer.bind(EKeyDownArrow,self.handle_downarrow)
       
   124         self.articleviewer.bind(EKeyUpArrow,self.handle_uparrow)
       
   125         self.articleviewer.bind(EKeyLeftArrow,self.handle_exit)
       
   126         self.articlemenu.bind(EKeyRightArrow,
       
   127                               self.handle_articlemenu_select)
       
   128         self.articlemenu.bind(EKeyLeftArrow,self.handle_exit)
       
   129         self.feedmenu.bind(EKeyRightArrow,self.handle_feedmenu_select)
       
   130         for k in self.feeds:
       
   131             k.listen(self.notify)        
       
   132         self.goto_state('feedmenu')
       
   133     def abort(self):
       
   134         self.exit_flag=True
       
   135         self.lock.signal()
       
   136     def close(self):
       
   137         appuifw.app.menu=[]
       
   138         appuifw.app.exit_key_handler=self.old_exit_key_handler
       
   139         appuifw.app.body=self.old_app_body
       
   140     def run(self):
       
   141         try:
       
   142             while not self.exit_flag:
       
   143                 self.lock.wait()
       
   144                 self.refresh()
       
   145         finally:
       
   146             self.close()
       
   147     def notify(self):
       
   148         self.lock.signal()
       
   149     def refresh(self):
       
   150         self.goto_state(self.state)
       
   151     def goto_state(self,newstate):
       
   152         # Workaround for a Series 60 bug: Prevent the cursor from
       
   153         # showing up if the articleviewer widget is not visible.
       
   154         self.articleviewer.focus=False
       
   155         if newstate=='feedmenu':
       
   156             self.feedmenu.set_list(
       
   157                 [format_feed(x) for x in self.feeds])
       
   158             appuifw.app.body=self.feedmenu
       
   159             appuifw.app.title=u'RSS reader'
       
   160         elif newstate=='articlemenu':
       
   161             if len(self.current_feed['entries'])==0:
       
   162                 if appuifw.query(u'Download articles now?','query'):
       
   163                     self.handle_update()
       
   164                 self.goto_state('feedmenu')
       
   165                 return 
       
   166             self.articlemenu.set_list(
       
   167                 [self.format_article_title(x)
       
   168                  for x in self.current_feed['entries']])
       
   169             appuifw.app.body=self.articlemenu
       
   170             appuifw.app.title=format_feed(self.current_feed)
       
   171         elif newstate=='articleview':
       
   172             self.articleviewer.clear()
       
   173             self.articleviewer.add(
       
   174                 self.format_title_in_article(self.current_article()))
       
   175             self.articleviewer.add(
       
   176                 self.format_article(self.current_article()))
       
   177             self.articleviewer.set_pos(0)
       
   178             appuifw.app.body=self.articleviewer
       
   179             appuifw.app.title=self.format_article_title(
       
   180                 self.current_article())
       
   181         else:
       
   182             raise RuntimeError("Invalid state %s"%state)
       
   183         appuifw.app.menu=self.statemap[newstate]['menu']
       
   184         self.state=newstate
       
   185     def current_article(self):
       
   186         return self.current_feed['entries'][self.current_article_index]
       
   187     def handle_update(self):
       
   188         if self.state=='feedmenu':
       
   189             self.current_feed=self.feeds[self.feedmenu.current()]
       
   190         self.current_feed.start_update()
       
   191         self.refresh()
       
   192     def handle_update_all(self):
       
   193         for k in self.feeds:
       
   194             if not k['busy']:
       
   195                 k.start_update()
       
   196         self.refresh()
       
   197     def handle_feedmenu_select(self):
       
   198         self.current_feed=self.feeds[self.feedmenu.current()]
       
   199         self.goto_state('articlemenu')
       
   200     def handle_articlemenu_select(self):
       
   201         self.current_article_index=self.articlemenu.current()
       
   202         self.goto_state('articleview')
       
   203     def handle_debug(self):
       
   204         import btconsole
       
   205         btconsole.run('Entering debug mode.',locals())
       
   206     def handle_next(self):
       
   207         if (self.current_article_index >=
       
   208             len(self.current_feed['entries'])-1):
       
   209             return
       
   210         self.current_article_index += 1
       
   211         self.refresh()
       
   212     def handle_prev(self):
       
   213         if self.current_article_index == 0:
       
   214             return
       
   215         self.current_article_index -= 1
       
   216         self.refresh()
       
   217     def handle_downarrow(self):
       
   218         article_length=self.articleviewer.len()
       
   219         cursor_pos=self.articleviewer.get_pos()
       
   220         if cursor_pos==article_length:
       
   221             self.handle_next()
       
   222         else:
       
   223             self.articleviewer.set_pos(min(cursor_pos+100,
       
   224                                            article_length))
       
   225     def handle_uparrow(self):
       
   226         cursor_pos=self.articleviewer.get_pos()
       
   227         if cursor_pos==0:
       
   228             self.handle_prev()
       
   229             self.articleviewer.set_pos(self.articleviewer.len())
       
   230         else:
       
   231             self.articleviewer.set_pos(max(cursor_pos-100,0))
       
   232     def format_title_in_article(self, article):
       
   233         self.articleviewer.highlight_color = (255,240,80)
       
   234         self.articleviewer.style = (appuifw.STYLE_UNDERLINE|
       
   235                                     appuifw.HIGHLIGHT_ROUNDED)
       
   236         self.articleviewer.font = 'title'
       
   237         self.articleviewer.color = (0,0,255)
       
   238         return unicode("%(title)s\n"%article)
       
   239 
       
   240     def format_article(self, article):
       
   241         self.articleviewer.highlight_color = (0,0,0)
       
   242         self.articleviewer.style = 0
       
   243         self.articleviewer.font = 'normal'
       
   244         self.articleviewer.color = (0,0,0)
       
   245         return unicode("%(summary)s"%article)
       
   246 
       
   247     def format_article_title(self, article):
       
   248         return unicode("%(title)s"%article)
       
   249     def handle_exit(self):
       
   250         self.statemap[self.state]['exithandler']()
       
   251 
       
   252 class DummyFeed:
       
   253     def __init__(self,data): self.data=data
       
   254     def listen(self,callback): pass
       
   255     def start_update(self): pass
       
   256     def __getitem__(self,key): return self.data.__getitem__(key)
       
   257     def save(self): pass
       
   258 dummyfeed=DummyFeed({'title': 'Dummy feed',
       
   259                      'entries': [{'title':'Dummy story',
       
   260                                   'summary':'Blah blah blah.'},
       
   261                                  {'title':'Another dummy story',
       
   262                                   'summary':'Foo, bar and baz.'}],
       
   263                      'busy': False})
       
   264 
       
   265 def main():
       
   266     old_title=appuifw.app.title
       
   267     appuifw.app.title=u'RSS reader'
       
   268     cache=anydbm.open(u'c:\\rsscache','c')
       
   269     feeds=[ CachingRSSFeed(url='http://slashdot.org/index.rss',
       
   270                            title='Slashdot',
       
   271                            cache=cache),
       
   272             CachingRSSFeed(url='http://news.bbc.co.uk/rss/newsonline_world_edition/front_page/rss091.xml',
       
   273                            title='BBC',
       
   274                            cache=cache),
       
   275             dummyfeed]
       
   276     app = ReaderApp(feeds)
       
   277     # Import heavyweight modules in the background to improve application
       
   278     # startup time.
       
   279     def import_modules():
       
   280         import dumbfeedparser as feedparser
       
   281         import btconsole
       
   282     import thread
       
   283     thread.start_new_thread(import_modules,())
       
   284     try:
       
   285         app.run()
       
   286     finally:
       
   287         for feed in feeds:
       
   288             feed.save()
       
   289         cache.close()
       
   290         appuifw.app.title=old_title
       
   291 
       
   292 if __name__=='__main__':
       
   293     main()
       
   294