""" PollManager.py
Module for polling the feeds.
"""
__copyright__ = "Copyright (c) 2002-4 Juri Pakaste <juri@iki.fi>"
__license__ = """ GNU General Public License
Straw is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
Straw is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA. """
import Config
import FeedList
import Event
from MainloopManager import MainloopManager
import MessageManager
import NetworkConstants
import URLFetch
import Feed
import FeedCategoryList
import SummaryParser
import utils
import time
import socket
import error
class PollStopper(object, Event.SignalEmitter):
def __init__(self, stopper, object):
Event.SignalEmitter.__init__(self)
self.initialize_slots(Event.PollingStoppedSignal)
self._stopper = stopper
self._object = object
stopper = property(lambda self: self._stopper)
object = property(lambda self: self._object)
def stop(self):
if self._stopper is not None:
self._stopper()
self._stopper = None
self.emit_signal(Event.PollingStoppedSignal(self))
class PollManager:
def __init__(self):
self._config = Config.get_instance()
self._mmgr = MessageManager.get_instance()
self._feedlist = FeedList.get_instance()
self._config.signal_connect(Event.PollFrequencyChangedSignal, self.poll_changed)
self._config.signal_connect(Event.OfflineModeChangedSignal, self.offline_changed)
self._feedlist.signal_connect(Event.FeedsChangedSignal, self.feeds_changed)
self._is_offline = self._config.offline
self._poll_frequency = self._config.poll_frequency
self._last_poll = self._config.last_poll
self._feeds = self._feedlist.flatten_list()
self._pollers = {}
def poll_changed(self, signal):
""" called when global poll frequency has changed"""
self._poll_frequency = signal.value
def offline_changed(self, signal):
self._is_offline = self._config.offline
def feeds_changed(self, *args):
self._feedlist = FeedList.get_instance()
self._feeds = self._feedlist.flatten_list()
def start_polling_loop(self):
mlmgr = MainloopManager.get_instance()
mlmgr.set_repeating_timer(NetworkConstants.POLL_INTERVAL, self.maybe_poll)
##########################
# Why both pollcontext and self._pollers?
# self._pollers is there to make sure that there are no simultaneous
# pollers on the same object. So the user doesn't accidentally start
# a poller for an object being polled.
# pollcontext is there so that we can avoid starting pollers many
# times - even though they wouldn't overlap - on the same object within
# one polling run.
# because maybe_poll is used as an idle handler, it must always return
# True or else it's removed from the handler list
def maybe_poll(self):
self._maybe_poll()
def _maybe_poll(self):
now = int(time.time())
if self._is_offline:
return True
try:
context = {}
config_pf = self._config.poll_frequency
feed_use_default = Feed.Feed.DEFAULT
self.poll([feed for feed, timediff, fpf in
[(feed, now - feed.last_poll, feed.poll_frequency)
for feed in self._feeds]
if ((timediff > fpf > 0) or
(fpf == feed_use_default and
timediff > config_pf > 0))], context)
self.poll_categories([cat for cat, sub, timediff in
[(cat, cat.subscription, now - cat.subscription.last_poll)
for cat in FeedCategoryList.get_instance().all_categories
if cat.subscription is not None]
if ((timediff > sub.frequency > 0) or
(sub.frequency == sub.REFRESH_DEFAULT and
timediff > config_pf > 0))], context, False)
except:
error.log_exc("Caught an exception while polling")
URLFetch.connection_manager.poll(NetworkConstants.POLL_TIMEOUT)
return True
def poll(self, obj, pollcontext = None):
""" obj must be a list of Feed objects"""
if pollcontext is None:
pollcontext = {}
stoppers = []
for f in obj:
sf = None
if not self._pollers.has_key(f) and not pollcontext.has_key(f):
self._pollers[f] = True
pollcontext[f] = True
sf = FeedPoller(f).poll()
stoppers.append(sf)
return stoppers
def poll_categories(self, cats, pollcontext = None, feeds = True):
"""Polls categories and, if feeds is True, the feeds
associated with them. Returns a list of list containing
PollStopper objects. poll_categories([c1, c2, c3]) -> [[sc1,
sc1f1, sc1f2 ...], [sc2, sc2f1, sc2f2 ...], ...] where cN is a
category, scN is a PollStopper for cN, scNfM is a stopper for
a feed in cN. If no polling was started for that cN or cNfM,
in its place is None. """
stoppers = []
if pollcontext is None:
pollcontext = {}
for c in cats:
category_stoppers = []
sc = None
if c.subscription is not None and not self._pollers.has_key(c) and not pollcontext.has_key(c):
self._pollers[c] = True
pollcontext[c] = True
sc = CategoryPoller(c, pollcontext).poll()
category_stoppers.append(sc)
if feeds:
for f in c.feeds:
sf = None
if not self._pollers.has_key(f) and not pollcontext.has_key(f):
self._pollers[f] = True
pollcontext[f] = True
sf = FeedPoller(f).poll(), f
category_stoppers.append(sf)
stoppers.append(category_stoppers)
return stoppers
def poll_done(self, obj):
if self._pollers.has_key(obj):
del self._pollers[obj]
class FeedPoller:
def __init__(self, feed):
self._feed = feed
self._mmgr = MessageManager.get_instance()
def poll(self):
MainloopManager.get_instance().call_pending()
url, user, pw = self._feed.access_info
parsed = None
headers = {}
config = Config.get_instance()
if not config.no_etags and self._feed.previous_etag is not None:
headers['If-None-Match'] = self._feed.previous_etag
self._feed.last_poll = int (time.time())
ps = None
try:
try:
stopper = URLFetch.connection_manager.request(
url, self, headers, user, pw,
priority=NetworkConstants.PRIORITY_RSS)
ps = PollStopper(stopper, self._feed)
except Exception, e:
error.log_exc("Caught an exception while polling")
self.http_failed(e)
else:
self._feed.router.start_polling()
self._feed.router.stopper = ps
finally:
return ps
def http_results(self, status, header, data):
MainloopManager.get_instance().call_pending()
try:
try:
import pdb
#pdb.set_trace()
self._feed.poll_done()
err = ""
read_data = True
if status is None:
err = _("No data")
elif status[1] == 304:
self._feed.router.route_no_data()
read_data = False
elif status[1] == 410 or status[1] == 404:
err = _("Unable to find the feed (%s: %s)") % (
status[1], status[2].strip())
elif status[1] == 401:
err = _("Invalid username and password.")
elif status[1] > 299:
err = _("Updating feed resulted in abnormal status '%s' (code %d)") % (
status[2].strip(), status[1])
if err:
self._feed.router.set_error(err)
elif read_data:
try:
parsed = SummaryParser.parse(data, self._feed)
except Exception, e:
error.log_exc("exception in summaryparser")
self._feed.router.set_error(
_("An error occurred while processing feed: %s") %
str(e))
else:
self._feed.router.route_all(header, parsed)
self._mmgr.post_message(_("Updating %s done.") % self._feed.title)
except Exception, ex:
error.log_exc("error while parsing results")
self._feed.router.set_error(str(ex))
finally:
get_instance().poll_done(self._feed)
def http_failed(self, exception):
try:
if isinstance(exception, socket.error):
self._feed.router.route_no_data()
else:
self._feed.router.set_error(str(exception))
self._mmgr.post_message(_("Updating %s failed") % self._feed.title)
self._feed.poll_done()
finally:
get_instance().poll_done(self._feed)
def http_permanent_redirect(self, location):
(oldloc, u, p) = self._feed.access_info
self._feed.access_info = (location, u, p)
def operation_stopped(self):
self._feed.router.route_no_data()
self._feed.poll_done()
get_instance().poll_done(self._feed)
class CategoryPoller:
def __init__(self, category, pollcontext):
self._category = category
self._mmgr = MessageManager.get_instance()
self._pollcontext = pollcontext
def poll(self):
headers = {}
sub = self._category.subscription
if sub is None or sub.location is None or len(sub.location) == 0:
return None
sub.last_poll = int(time.time())
if sub.previous_etag is not None:
headers['If-None-Match'] = sub.previous_etag
ps = None
try:
try:
stopper = URLFetch.connection_manager.request(
sub.location, self, headers, sub.username, sub.password,
priority=NetworkConstants.PRIORITY_RSS)
ps = PollStopper(stopper, self._category)
except Exception, e:
error.log_exc("Caught an exception while polling category")
finally:
return ps
def http_results(self, status, header, data):
sub = self._category.subscription
try:
try:
err = None
if status is None:
err = _("No data (%s)" % status[1])
elif status[1] == 410 or status[1] == 404:
err = _("Unable to find the category (%s: %s)") % (
status[1], status[2].strip())
elif status[1] == 401:
err = _("Invalid username and password.")
elif status[1] > 299:
err = _("Updating category resulted in abnormal status '%s' (code %d)") % (status[2].strip(), status[1])
if err is not None:
self._category.subscription.error = err
else:
sub.parse(data)
old_feeds = self._category.feeds
self._category.read_contents_from_subscription()
common, inold, innew = utils.listdiff(
old_feeds, self._category.feeds)
if len(innew) > 0:
get_instance().poll(innew, self._pollcontext)
except Exception, e:
self._category.subscription.error = str(e)
finally:
get_instance().poll_done(self._category)
def http_failed(self, exception):
self._category.subscription.error = str(exception)
get_instance().poll_done(self._category)
def http_permanent_redirect(self, location):
error.logparam(locals(), "location")
def operation_stopped(self):
get_instance().poll_done(self._category)
pollmanager_instance = None
def get_instance():
global pollmanager_instance
if pollmanager_instance is None:
pollmanager_instance = PollManager()
return pollmanager_instance
syntax highlighted by Code2HTML, v. 0.9.1