0.4.1-2: auto-update and testing of window animation
[feedingit] / src / rss.py
1 #!/usr/bin/env python2.5
2
3
4 # Copyright (c) 2007-2008 INdT.
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 #  This program is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #  GNU Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public License
16 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 # ============================================================================
20 # Name        : FeedingIt.py
21 # Author      : Yves Marcoz
22 # Version     : 0.4.1
23 # Description : Simple RSS Reader
24 # ============================================================================
25
26 from os.path import isfile
27 from os.path import isdir
28 from os import remove
29 import pickle
30 import md5
31 import feedparser
32 import time
33 import urllib2
34
35 #CONFIGDIR="/home/user/.feedingit/"
36
37 def getId(string):
38     return md5.new(string).hexdigest()
39
40 class Feed:
41     # Contains all the info about a single feed (articles, ...), and expose the data
42     def __init__(self, name, url):
43         self.entries = []
44         self.readItems = {}
45         self.countUnread = 0
46         self.name = name
47         self.url = url
48         self.updateTime = "Never"
49
50     def saveFeed(self, configdir):
51         file = open(configdir+getId(self.name), "w")
52         pickle.dump(self, file )
53         file.close()
54
55     def updateFeed(self, configdir, expiryTime=24):
56         # Expiry time is in hours
57         tmp=feedparser.parse(self.url)
58         # Check if the parse was succesful (number of entries > 0, else do nothing)
59         if len(tmp["entries"])>0:
60            #reversedEntries = self.getEntries()
61            #reversedEntries.reverse()
62            tmpIds = []
63            for entry in tmp["entries"]:
64                tmpIds.append(self.getUniqueId(-1, entry))
65            for entry in self.getEntries():
66                currentTime = time.time()
67                expiry = float(expiryTime) * 3600.
68                if entry.has_key("updated_parsed"):
69                    articleTime = time.mktime(entry["updated_parsed"])
70                    if currentTime - articleTime < expiry:
71                        id = self.getUniqueId(-1, entry)
72                        if not id in tmpIds:
73                            tmp["entries"].append(entry)
74                    
75            self.entries = tmp["entries"]
76            self.countUnread = 0
77            # Initialize the new articles to unread
78            tmpReadItems = self.readItems
79            self.readItems = {}
80            for index in range(self.getNumberOfEntries()):
81                if not tmpReadItems.has_key(self.getUniqueId(index)):
82                    self.readItems[self.getUniqueId(index)] = False
83                else:
84                    self.readItems[self.getUniqueId(index)] = tmpReadItems[self.getUniqueId(index)]
85                if self.readItems[self.getUniqueId(index)]==False:
86                   self.countUnread = self.countUnread + 1
87            del tmp
88            self.updateTime = time.asctime()
89            self.saveFeed(configdir)
90     
91     def setEntryRead(self, index):
92         if self.readItems[self.getUniqueId(index)]==False:
93             self.countUnread = self.countUnread - 1
94             self.readItems[self.getUniqueId(index)] = True
95     
96     def isEntryRead(self, index):
97         return self.readItems[self.getUniqueId(index)]
98     
99     def getTitle(self, index):
100         return self.entries[index]["title"]
101     
102     def getLink(self, index):
103         return self.entries[index]["link"]
104     
105     def getDate(self, index):
106         try:
107             return self.entries[index]["updated_parsed"]
108         except:
109             return time.localtime()
110     
111     def getUniqueId(self, index, entry=None):
112         if index >=0:
113             entry = self.entries[index]
114         if entry.has_key("updated_parsed"):
115             return getId(time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"]) + entry["title"])
116         elif entry.has_key("link"):
117             return getId(entry["link"] + entry["title"])
118         else:
119             return getId(entry["title"])
120     
121     def getUpdateTime(self):
122         return self.updateTime
123     
124     def getEntries(self):
125         try:
126             return self.entries
127         except:
128             return []
129     
130     def getNumberOfUnreadItems(self):
131         return self.countUnread
132     
133     def getNumberOfEntries(self):
134         return len(self.entries)
135     
136     def getItem(self, index):
137         try:
138             return self.entries[index]
139         except:
140             return []
141     
142     def getContent(self, index):
143         entry = self.entries[index]
144         if entry.has_key('content'):
145             content = entry.content[0].value
146         else:
147             content = entry.get('summary', '')
148         return content
149     
150     def getArticle(self, index):
151         self.setEntryRead(index)
152         entry = self.entries[index]
153         title = entry.get('title', 'No title')
154         #content = entry.get('content', entry.get('summary_detail', {}))
155         content = self.getContent(index)
156
157         link = entry.get('link', 'NoLink')
158         if entry.has_key("updated_parsed"):
159             date = time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"])
160         elif entry.has_key("published_parsed"):
161             date = time.strftime("%a, %d %b %Y %H:%M:%S", entry["published_parsed"])
162         else:
163             date = ""
164         #text = '''<div style="color: black; background-color: white;">'''
165         text = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>'
166         text += '<div><a href=\"' + link + '\">' + title + "</a>"
167         text += "<BR /><small><i>Date: " + date + "</i></small></div>"
168         text += "<BR /><BR />"
169         text += content
170         return text    
171
172 class ArchivedArticles(Feed):
173     def addArchivedArticle(self, title, link, updated_parsed, configdir):
174         entry = {}
175         entry["title"] = title
176         entry["link"] = link
177         entry["downloaded"] = False
178         entry["summary"] = '<a href=\"' + link + '\">' + title + "</a>"
179         entry["updated_parsed"] = updated_parsed
180         self.entries.append(entry)
181         self.readItems[self.getUniqueId(len(self.entries)-1)] = False
182         self.countUnread = self.countUnread + 1
183         self.saveFeed(configdir)
184         #print entry
185         
186     def updateFeed(self, configdir, expiryTime=24):
187         for entry in self.getEntries():
188             if not entry["downloaded"]:
189                 try:
190                     f = urllib2.urlopen(entry["link"])
191                     entry["summary"] = f.read()
192                     f.close()
193                     entry["downloaded"] = True
194                     entry["time"] = time.time()
195                 except:
196                     pass
197             currentTime = time.time()
198             expiry = float(expiryTime) * 3600
199             if currentTime - entry["time"] > expiry:
200                 self.entries.remove(entry)
201         self.saveFeed(configdir)
202
203     def getArticle(self, index):
204         self.setEntryRead(index)
205         content = self.getContent(index)
206         return content
207
208
209 class Listing:
210     # Lists all the feeds in a dictionary, and expose the data
211     def __init__(self, configdir):
212         self.configdir = configdir
213         self.feeds = {}
214         if isfile(self.configdir+"feeds.pickle"):
215             file = open(self.configdir+"feeds.pickle")
216             self.listOfFeeds = pickle.load(file)
217             file.close()
218         else:
219             self.listOfFeeds = {getId("Slashdot"):{"title":"Slashdot", "url":"http://rss.slashdot.org/Slashdot/slashdot"}, }
220         if self.listOfFeeds.has_key("font"):
221             del self.listOfFeeds["font"]
222         if self.listOfFeeds.has_key("feedingit-order"):
223             self.sortedKeys = self.listOfFeeds["feedingit-order"]
224         else:
225             self.sortedKeys = self.listOfFeeds.keys()
226             if "font" in self.sortedKeys:
227                 self.sortedKeys.remove("font")
228             self.sortedKeys.sort(key=lambda obj: self.getFeedTitle(obj))
229         for key in self.sortedKeys:
230             try:
231                 self.loadFeed(key)
232             except:
233                 self.sortedKeys.remove(key)
234         #self.saveConfig()
235
236     def addArchivedArticle(self, key, index):
237         title = self.getFeed(key).getTitle(index)
238         link = self.getFeed(key).getLink(index)
239         date = self.getFeed(key).getDate(index)
240         if not self.listOfFeeds.has_key(getId("Archived Articles")):
241             self.listOfFeeds[getId("Archived Articles")] = {"title":"Archived Articles", "url":""}
242             self.sortedKeys.append(getId("Archived Articles"))
243             self.feeds[getId("Archived Articles")] = ArchivedArticles("Archived Articles", "")
244             self.saveConfig()
245             
246         self.getFeed(getId("Archived Articles")).addArchivedArticle(title, link, date, self.configdir)
247         
248     def loadFeed(self, key):
249             if isfile(self.configdir+key):
250                 file = open(self.configdir+key)
251                 self.feeds[key] = pickle.load(file)
252                 file.close()
253             else:
254                 title = self.listOfFeeds[key]["title"]
255                 url = self.listOfFeeds[key]["url"]
256                 self.feeds[key] = Feed(title, url)
257         
258     def updateFeeds(self, expiryTime=24):
259         for key in self.getListOfFeeds():
260             self.feeds[key].updateFeed(self.configdir, expiryTime)
261             
262     def updateFeed(self, key, expiryTime=24):
263         self.feeds[key].updateFeed(self.configdir, expiryTime)
264             
265     def getFeed(self, key):
266         return self.feeds[key]
267     
268     def getFeedUpdateTime(self, key):
269         return self.feeds[key].getUpdateTime()
270     
271     def getFeedNumberOfUnreadItems(self, key):
272         return self.feeds[key].getNumberOfUnreadItems()
273    
274     def getFeedTitle(self, key):
275         return self.listOfFeeds[key]["title"]
276     
277     def getFeedUrl(self, key):
278         return self.listOfFeeds[key]["url"]
279     
280     def getListOfFeeds(self):
281         return self.sortedKeys
282     
283     def addFeed(self, title, url):
284         if not self.listOfFeeds.has_key(getId(title)):
285             self.listOfFeeds[getId(title)] = {"title":title, "url":url}
286             self.sortedKeys.append(getId(title))
287             self.saveConfig()
288             self.feeds[getId(title)] = Feed(title, url)
289         
290     def removeFeed(self, key):
291         del self.listOfFeeds[key]
292         self.sortedKeys.remove(key)
293         del self.feeds[key]
294         if isfile(self.configdir+key):
295            remove(self.configdir+key)
296     
297     def saveConfig(self):
298         self.listOfFeeds["feedingit-order"] = self.sortedKeys
299         file = open(self.configdir+"feeds.pickle", "w")
300         pickle.dump(self.listOfFeeds, file)
301         file.close()
302         
303     def moveUp(self, key):
304         index = self.sortedKeys.index(key)
305         self.sortedKeys[index] = self.sortedKeys[index-1]
306         self.sortedKeys[index-1] = key
307         
308     def moveDown(self, key):
309         index = self.sortedKeys.index(key)
310         index2 = (index+1)%len(self.sortedKeys)
311         self.sortedKeys[index] = self.sortedKeys[index2]
312         self.sortedKeys[index2] = key