Back to index

plone3  3.1.7
rss.py
Go to the documentation of this file.
00001 from zope import schema
00002 from zope.component import getMultiAdapter, getUtility
00003 from zope.formlib import form
00004 from zope.interface import implements, Interface
00005 
00006 from plone.app.portlets.portlets import base
00007 from plone.memoize.instance import memoize
00008 from plone.app.portlets.utils import assignment_from_key
00009 from plone.portlets.interfaces import IPortletDataProvider
00010 from plone.portlets.utils import unhashPortletInfo
00011 from plone.portlets.interfaces import IPortletManager, IPortletRenderer
00012 
00013 from Acquisition import aq_inner
00014 from DateTime.DateTime import DateTime
00015 from Products.CMFCore.utils import getToolByName
00016 from Products.CMFPlone import PloneMessageFactory as _
00017 from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
00018 
00019 import feedparser
00020 import time, socket
00021 
00022 from DateTime import DateTime
00023 
00024 # store the feeds here (which means in RAM)
00025 FEED_DATA = {}  # url: ({date, title, url, itemlist})
00026 
00027 class IFeed(Interface):
00028     
00029     def __init__(url, timeout):
00030         """initialize the feed with the given url. will not automatically load it
00031            timeout defines the time between updates in minutes        
00032         """
00033         
00034     def loaded():
00035         """return if this feed is in a loaded state"""
00036         
00037     def title():
00038         """return the title of the feed"""
00039 
00040     def items():
00041         """return the items of the feed"""
00042         
00043     def feed_link():
00044         """return the url of this feed in feed:// format"""
00045         
00046     def site_url():
00047         """return the URL of the site"""
00048     
00049     def last_update_time_in_minutes():
00050         """return the time this feed was last updated in minutes since epoch"""
00051         
00052     def last_update_time():
00053         """return the time the feed was last updated as DateTime object"""
00054     
00055     def needs_update():
00056         """return if this feed needs to be updated"""
00057         
00058     def update():
00059         """update this feed. will automatically check failure state etc.
00060            returns True or False whether it succeeded or not
00061         """
00062                 
00063     def update_failed():
00064         """return if the last update failed or not"""
00065         
00066     def ok():
00067         """is this feed ok to display?"""
00068 
00069 class RSSFeed(object):
00070     """an RSS feed"""
00071     implements(IFeed)
00072     
00073     # TODO: discuss whether we want an increasing update time here, probably not though
00074     FAILURE_DELAY = 10  # time in minutes after which we retry to load it after a failure
00075     
00076     def __init__(self, url, timeout):
00077         self.url = url
00078         self.timeout = timeout
00079         
00080         self._items = []
00081         self._title = ""
00082         self._siteurl = ""
00083         self._loaded = False    # is the feed loaded
00084         self._failed = False    # does it fail at the last update?
00085         self._last_update_time_in_minutes = 0 # when was the feed last updated?
00086         self._last_update_time = None            # time as DateTime or Nonw
00087 
00088     @property
00089     def last_update_time_in_minutes(self):
00090         """return the time the last update was done in minutes"""
00091         return self._last_update_time_in_minutes
00092 
00093     @property
00094     def last_update_time(self):
00095         """return the time the last update was done in minutes"""
00096         return self._last_update_time
00097 
00098     @property
00099     def update_failed(self):
00100         return self._failed
00101 
00102     @property    
00103     def ok(self):
00104         return (not self._failed and self._loaded)
00105         
00106     @property
00107     def loaded(self):
00108         """return whether this feed is loaded or not"""
00109         return self._loaded
00110         
00111     @property
00112     def needs_update(self):
00113         """check if this feed needs updating"""
00114         now = time.time()/60
00115         return (self.last_update_time_in_minutes+self.timeout) < now
00116         
00117     def update(self):
00118         """update this feed"""
00119         now = time.time()/60    # time in minutes
00120          
00121         # check for failure and retry
00122         if self.update_failed:
00123             if (self.last_update_time_in_minutes+self.FAILURE_DELAY) < now:
00124                 return self._retrieveFeed()
00125             else:
00126                 return False
00127 
00128         # check for regular update
00129         if self.needs_update:
00130             return self._retrieveFeed()
00131             
00132         return self.ok
00133 
00134     def _retrieveFeed(self):
00135         """do the actual work and try to retrieve the feed"""
00136         url = self.url
00137         if url!='':
00138             self._last_update_time_in_minutes = time.time()/60
00139             self._last_update_time = DateTime()
00140             d = feedparser.parse(url)
00141             if d.bozo==1:
00142                 self._loaded = True # we tried at least but have a failed load
00143                 self._failed = True
00144                 return False
00145             self._title = d.feed.title
00146             self._siteurl = d.feed.link
00147             self._items = []
00148             for item in d['items']:
00149                 try:
00150                     link = item.links[0]['href']
00151                     itemdict = {
00152                         'title' : item.title,
00153                         'url' : link,
00154                         'summary' : item.get('description',''),
00155                     }
00156                     if hasattr(item, "updated"):
00157                         itemdict['updated']=DateTime(item.updated)
00158                 except AttributeError:
00159                     continue
00160                 self._items.append(itemdict)
00161             self._loaded = True
00162             self._failed = False
00163             return True
00164         self._loaded = True
00165         self._failed = True # no url set means failed 
00166         return False # no url set, although that actually should not really happen
00167 
00168         
00169     @property
00170     def items(self):
00171         return self._items
00172 
00173     # convenience methods for displaying
00174     # 
00175     
00176     @property
00177     def feed_link(self):
00178         """return rss url of feed for portlet"""
00179         return self.url.replace("http://","feed://")
00180 
00181     @property
00182     def title(self):
00183         """return title of feed for portlet"""
00184         return self._title
00185     
00186     @property    
00187     def siteurl(self):
00188         """return the link to the site the RSS feed points to"""
00189         return self._siteurl
00190 
00191 class IRSSPortlet(IPortletDataProvider):
00192 
00193     count = schema.Int(title=_(u'Number of items to display'),
00194                        description=_(u'How many items to list.'),
00195                        required=True,
00196                        default=5)
00197     url = schema.TextLine(title=_(u'URL of RSS feed'),
00198                         description=_(u'Link of the RSS feed to display.'),
00199                         required=True,
00200                         default=u'')
00201                         
00202     timeout = schema.Int(title=_(u'Feed reload timeout'),
00203                         description=_(u'Time in minutes after which the feed should be reloaded.'),
00204                         required=True,
00205                         default=100)                        
00206 
00207 class Assignment(base.Assignment):
00208     implements(IRSSPortlet)
00209     
00210     @property
00211     def title(self):
00212         """return the title with RSS feed title or from URL"""
00213         feed = FEED_DATA.get(self.data.url,None)
00214         if feed is None:
00215             return u'RSS: '+self.url[:20]
00216         else:
00217             return u'RSS: '+feed.title[:20]
00218 
00219     def __init__(self, count=5, url=u"", timeout=100):
00220         self.count = count
00221         self.url = url
00222         self.timeout = timeout
00223         
00224 class Renderer(base.DeferredRenderer):
00225     
00226     render_full = ZopeTwoPageTemplateFile('rss.pt')
00227     
00228     @property
00229     def initializing(self):
00230         """should return True if deferred template should be displayed"""
00231         feed=self._getFeed()
00232         if not feed.loaded:
00233             return True
00234         if feed.needs_update:
00235             return True
00236         return False
00237             
00238     def deferred_update(self):
00239         """refresh data for serving via KSS"""
00240         feed = self._getFeed()
00241         feed.update()
00242 
00243     def update(self):
00244         """update data before rendering. We can not wait for KSS since users
00245         may not be using KSS."""
00246         self.deferred_update()
00247 
00248     def _getFeed(self):
00249         """return a feed object but do not update it"""
00250         feed = FEED_DATA.get(self.data.url,None)
00251         if feed is None:
00252             # create it
00253             feed = FEED_DATA[self.data.url] = RSSFeed(self.data.url,self.data.timeout)
00254         return feed
00255         
00256     @property
00257     def url(self):
00258         """return url of feed for portlet"""
00259         return self._getFeed().url
00260      
00261      
00262     @property
00263     def siteurl(self):
00264         """return url of site for portlet"""
00265         return self._getFeed().siteurl
00266             
00267     @property    
00268     def feedlink(self):
00269         """return rss url of feed for portlet"""
00270         return self.data.url.replace("http://","feed://")
00271     
00272     @property    
00273     def title(self):
00274         """return title of feed for portlet"""
00275         return self._getFeed().title
00276     
00277     @property
00278     def feedAvailable(self):
00279         """checks if the feed data is available"""
00280         return self._getFeed().ok
00281     
00282     @property            
00283     def items(self):
00284         return self._getFeed().items[:self.data.count]
00285     
00286     @property    
00287     def enabled(self):
00288         return self._getFeed().ok
00289 
00290 class AddForm(base.AddForm):
00291     form_fields = form.Fields(IRSSPortlet)
00292     label = _(u"Add RSS Portlet")
00293     description = _(u"This portlet displays an RSS feed.")
00294     
00295 
00296     def create(self, data):
00297         return Assignment(count=data.get('count', 5),
00298                           url = data.get('url',''),
00299                           timeout = data.get('timeout',100))
00300 
00301 class EditForm(base.EditForm):
00302     form_fields = form.Fields(IRSSPortlet)
00303     label = _(u"Edit RSS Portlet")
00304     description = _(u"This portlet displays an RSS feed.")