view finch/libgnt/pygnt/example/rss/gntrss.py @ 19649:c6f1f9971c4a

When doing oscar server-side list management, do deletions before additions because if it's done the other way then it's possible for Pidgin to try to add one buddy to two different groups, which isn't allowed for ICQ. Fixes schoen's comment at http://developer.pidgin.im/ticket/576 References #576.
author Mark Doliner <mark@kingant.net>
date Wed, 05 Sep 2007 05:43:08 +0000
parents 2e41a68009ba
children 44b4e8bd759b 184d2fbcb4f0
line wrap: on
line source

#!/usr/bin/env python

"""
gr - An RSS-reader built using libgnt and feedparser.

Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>

This application is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This application 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this application; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
"""

"""
This file deals with the rss parsing part (feedparser) of the application
"""

import os
import tempfile, urllib2
import feedparser
import gobject
import sys
import time

##
# The FeedItem class. It will update emit 'delete' signal when it's
# destroyed.
##
class FeedItem(gobject.GObject):
    __gproperties__ = {
        'unread' : (gobject.TYPE_BOOLEAN, 'read',
            'The unread state of the item.',
            False, gobject.PARAM_READWRITE)
    }
    __gsignals__ = {
        'delete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
    }
    def __init__(self, item, parent):
        self.__gobject_init__()
        try:
            "Apparently some feed items don't have any dates in them"
            self.date = item['date']
            self.date_parsed = item['date_parsed']
        except:
            item['date'] = self.date = time.ctime()
            self.date_parsed = feedparser._parse_date(self.date)

        self.title = item['title'].encode('utf8')
        self.summary = item['summary'].encode('utf8')
        self.link = item['link']
        self.parent = parent
        self.unread = True

    def remove(self):
        self.emit('delete', self.parent)
        if self.unread:
            self.parent.set_property('unread', self.parent.unread - 1)

    def do_set_property(self, property, value):
        if property.name == 'unread':
            self.unread = value

    def mark_unread(self, unread):
        if self.unread == unread:
            return
        self.set_property('unread', unread)

gobject.type_register(FeedItem)

def item_hash(item):
    return str(item['title'])

"""
The Feed class. It will update the 'link', 'title', 'desc' and 'items'
attributes if/when they are updated (triggering 'notify::<attr>' signal)

TODO:
    - Add a 'count' attribute
    - Each feed will have a 'uidata', which will be its display window
    - Look into 'category'. Is it something that feed defines, or the user?
    - Have separate refresh times for each feed.
    - Have 'priority' for each feed. (somewhat like category, perhaps?)
"""
class Feed(gobject.GObject):
    __gproperties__ = {
        'link' : (gobject.TYPE_STRING, 'link',
            'The web page this feed is associated with.',
            '...', gobject.PARAM_READWRITE),
        'title' : (gobject.TYPE_STRING, 'title',
            'The title of the feed.',
            '...', gobject.PARAM_READWRITE),
        'desc' : (gobject.TYPE_STRING, 'description',
            'The description for the feed.',
            '...', gobject.PARAM_READWRITE),
        'items' : (gobject.TYPE_POINTER, 'items',
            'The items in the feed.', gobject.PARAM_READWRITE),
        'unread' : (gobject.TYPE_INT, 'unread',
            'Number of unread items in the feed.', 0, 10000, 0, gobject.PARAM_READWRITE)
    }
    __gsignals__ = {
        'added' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
    }

    def __init__(self, feed):
        self.__gobject_init__()
        url = feed['link']
        name = feed['name']
        self.url = url           # The url of the feed itself
        self.link = url          # The web page associated with the feed
        self.desc = url
        self.title = (name, url)[not name]
        self.customtitle = name
        self.unread = 0
        self.items = []
        self.hash = {}
        self.pending = False
        self._refresh = {'time' : 30, 'id' : 0}

    def do_set_property(self, property, value):
        if property.name == 'link':
            self.link = value
        elif property.name == 'desc':
            self.desc = value
        elif property.name == 'title':
            self.title = value
        elif property.name == 'unread':
            self.unread = value
        pass

    def set_result(self, result):
        # XXX Look at result['bozo'] first, and emit some signal that the UI can use
        # to indicate (dim the row?) that the feed has invalid XML format or something

        try:
            channel = result['channel']
            self.set_property('link', channel['link'])
            self.set_property('desc', channel['description'])
            self.set_property('title', channel['title'])
            items = result['items']
        except:
            items = ()

        tmp = {}
        for item in self.items:
            tmp[hash(item)] = item

        unread = self.unread
        for item in items:
            try:
                exist = self.hash[item_hash(item)]
                del tmp[hash(exist)]
            except:
                itm = FeedItem(item, self)
                self.items.append(itm)
                self.emit('added', itm)
                self.hash[item_hash(item)] = itm
                unread = unread + 1

        if unread != self.unread:
            self.set_property('unread', unread)

        for hv in tmp:
            self.items.remove(tmp[hv])
            tmp[hv].remove()
            "Also notify the UI about the count change"

        self.pending = False
        return False

    def refresh(self):
        if self.pending:
            return
        self.pending = True
        FeedReader(self).run()
        return True

    def mark_read(self):
        for item in self.items:
            item.mark_unread(False)

    def set_auto_refresh(self, auto):
        if auto:
            if self._refresh['id']:
                return
            if self._refresh['time'] < 1:
                self._refresh['time'] = 1
            self.id = gobject.timeout_add(self._refresh['time'] * 1000 * 60, self.refresh)
        else:
            if not self._refresh['id']:
                return
            gobject.source_remove(self._refresh['id'])
            self._refresh['id'] = 0

gobject.type_register(Feed)

"""
The FeedReader updates a Feed. It fork()s off a child to avoid blocking.
"""
class FeedReader:
    def __init__(self, feed):
        self.feed = feed

    def reap_child(self, pid, status):
        result = feedparser.parse(self.tmpfile.name)
        self.tmpfile.close()
        self.feed.set_result(result)

    def run(self):
        self.tmpfile = tempfile.NamedTemporaryFile()
        self.pid = os.fork()
        if self.pid == 0:
            tmp = urllib2.urlopen(self.feed.url)
            content = tmp.read()
            tmp.close()
            self.tmpfile.write(content)
            self.tmpfile.flush()
            # Do NOT close tmpfile here
            os._exit(os.EX_OK)
        gobject.child_watch_add(self.pid, self.reap_child)

feeds = []
urls = (
    {'name': '/.',
     'link': "http://rss.slashdot.org/Slashdot/slashdot"},
    {'name': 'KernelTrap',
     'link': "http://kerneltrap.org/node/feed"},
    {'name': None,
     'link': "http://pidgin.im/rss.php"},
    {'name': "F1",
     'link': "http://www.formula1.com/rss/news/latest.rss"},
    {'name': "Freshmeat",
     'link': "http://www.pheedo.com/f/freshmeatnet_announcements_unix"},
    {'name': "Cricinfo",
     'link': "http://www.cricinfo.com/rss/livescores.xml"}
)

for url in urls:
    feed = Feed(url)
    feeds.append(feed)