'Fetch a feed from its URL and update its metadata if necessary.
Returns stream if feed had updates, None otherwise.'

def fetch_feed(self, feed):

    dirty_feed = False
    has_updates = False
    if (feed.etag is None):
        feed.etag = ''
    if (feed.last_modified is None):
        feed.last_modified = datetime.datetime(1975, 1, 10)
    log.debug(('feed    id=%s    feed    url=%s    etag=%s    last_modified=%s' % (feed.shortname, feed.url, feed.etag, str(feed.last_modified))))
    try:
        stream = feedparser.parse(feed.url, etag=feed.etag, modified=feed.last_modified.timetuple())
    except UnicodeDecodeError:
        print feed.url
    url_status = '500'
    if ('status' in stream):
        url_status = str(stream.status)
    elif (('entries' in stream) and (len(stream.entries) > 0)):
        url_status = '200'
    elif (stream.bozo and (stream.bozo_exception.__class__.__name__ == 'Timeout')):
        url_status = '408'
    if ((url_status == '301') and (('entries' in stream) and (len(stream.entries) > 0))):
        log.info('Feed    has    moved    from    <%s>    to    <%s>', feed.url, stream.url)
        feed.url = stream.url
        dirty_feed = True
    elif (url_status == '304'):
        log.debug('Feed    unchanged')
        if (not feed.enabled):
            feed.enabled = True
            dirty_feed = True
    elif (url_status == '404'):
        log.info('Not    a    Feed    or    Feed    %s    is    gone', feed.url)
        feed.enabled = False
        feed.disabled_reason = "This    is    not    a    feed    or    it's    been    removed    removed!"
        dirty_feed = True
    elif (url_status == '410'):
        log.info('Feed    %s    gone', feed.url)
        feed.enabled = False
        feed.disabled_reason = 'This    feed    has    been    removed!'
        dirty_feed = True
    elif (url_status == '408'):
        feed.enabled = False
        feed.disabled_reason = ("This    feed    didn't    respond    after    %d    seconds" % settings.FEEDER_TIMEOUT)
        dirty_feed = True
    elif (int(url_status) >= 400):
        feed.enabled = False
        bozo_msg = ''
        if ((1 == stream.bozo) and ('bozo_exception' in stream.keys())):
            log.error('Unable    to    fetch    %s    Exception:    %s', feed.url, stream.bozo_exception)
            bozo_msg = stream.bozo_exception
        feed.disabled_reason = ('Error    while    reading    the    feed:    %s    __    %s' % (url_status, bozo_msg))
        dirty_feed = True
    else:
        if ((not feed.enabled) or feed.disabled_reason):
            feed.enabled = True
            feed.disabled_reason = ''
            dirty_feed = True
        has_updates = True
    if (('etag' in stream) and (stream.etag != feed.etag) and (stream.etag is not None)):
        log.info(('New    etag    %s' % stream.etag))
        feed.etag = stream.etag
        dirty_feed = True
    if (('modified' in stream) and (stream.modified_parsed != feed.last_modified)):
        feed.last_modified = time.strftime('%Y-%m-%d    %H:%M:%S', stream.modified_parsed)
        log.info(('New    last_modified    %s' % feed.last_modified))
        dirty_feed = True
    if dirty_feed:
        try:
            dirty_feed = False
            log.debug(('Feed    %s    changed,    updating    db' % feed))
            feed.save()
        except KeyboardInterrupt:
            raise
        except Exception as x:
            log.error('Unable    to    update    feed')
            log.exception(x)
    return ((has_updates and stream) or None)
