0.4.3-5 Added GetStatus dbus call, and bugfixes
[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.3
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 editFeed(self, url):
51         self.url = url
52
53     def saveFeed(self, configdir):
54         file = open(configdir+getId(self.name), "w")
55         pickle.dump(self, file )
56         file.close()
57
58     def updateFeed(self, configdir, expiryTime=24):
59         # Expiry time is in hours
60         tmp=feedparser.parse(self.url)
61         # Check if the parse was succesful (number of entries > 0, else do nothing)
62         if len(tmp["entries"])>0:
63            #reversedEntries = self.getEntries()
64            #reversedEntries.reverse()
65            tmpIds = []
66            for entry in tmp["entries"]:
67                tmpIds.append(self.getUniqueId(-1, entry))
68            for entry in self.getEntries():
69                currentTime = time.time()
70                expiry = float(expiryTime) * 3600.
71                if entry.has_key("updated_parsed"):
72                    articleTime = time.mktime(entry["updated_parsed"])
73                    if currentTime - articleTime < expiry:
74                        id = self.getUniqueId(-1, entry)
75                        if not id in tmpIds:
76                            tmp["entries"].append(entry)
77                    
78            self.entries = tmp["entries"]
79            self.countUnread = 0
80            # Initialize the new articles to unread
81            tmpReadItems = self.readItems
82            self.readItems = {}
83            for index in range(self.getNumberOfEntries()):
84                if not tmpReadItems.has_key(self.getUniqueId(index)):
85                    self.readItems[self.getUniqueId(index)] = False
86                else:
87                    self.readItems[self.getUniqueId(index)] = tmpReadItems[self.getUniqueId(index)]
88                if self.readItems[self.getUniqueId(index)]==False:
89                   self.countUnread = self.countUnread + 1
90            del tmp
91            self.updateTime = time.asctime()
92            self.saveFeed(configdir)
93     
94     def setEntryRead(self, index):
95         if self.readItems[self.getUniqueId(index)]==False:
96             self.countUnread = self.countUnread - 1
97             self.readItems[self.getUniqueId(index)] = True
98     
99     def isEntryRead(self, index):
100         return self.readItems[self.getUniqueId(index)]
101     
102     def getTitle(self, index):
103         return self.entries[index]["title"]
104     
105     def getLink(self, index):
106         return self.entries[index]["link"]
107     
108     def getDate(self, index):
109         try:
110             return self.entries[index]["updated_parsed"]
111         except:
112             return time.localtime()
113     
114     def getUniqueId(self, index, entry=None):
115         if index >=0:
116             entry = self.entries[index]
117         if entry.has_key("updated_parsed"):
118             return getId(time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"]) + entry["title"])
119         elif entry.has_key("link"):
120             return getId(entry["link"] + entry["title"])
121         else:
122             return getId(entry["title"])
123     
124     def getUpdateTime(self):
125         return self.updateTime
126     
127     def getEntries(self):
128         try:
129             return self.entries
130         except:
131             return []
132     
133     def getNumberOfUnreadItems(self):
134         return self.countUnread
135     
136     def getNumberOfEntries(self):
137         return len(self.entries)
138     
139     def getItem(self, index):
140         try:
141             return self.entries[index]
142         except:
143             return []
144     
145     def getContent(self, index):
146         content = ""
147         entry = self.entries[index]
148         if entry.has_key('summary'):
149             content = entry.get('summary', '')
150         if entry.has_key('content'):
151             if len(entry.content[0].value) > len(content):
152                 content = entry.content[0].value
153         else:
154             content = entry.get('description', '')
155         return content
156     
157     def getArticle(self, index):
158         self.setEntryRead(index)
159         entry = self.entries[index]
160         title = entry.get('title', 'No title')
161         #content = entry.get('content', entry.get('summary_detail', {}))
162         content = self.getContent(index)
163
164         link = entry.get('link', 'NoLink')
165         if entry.has_key("updated_parsed"):
166             date = time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"])
167         elif entry.has_key("published_parsed"):
168             date = time.strftime("%a, %d %b %Y %H:%M:%S", entry["published_parsed"])
169         else:
170             date = ""
171         #text = '''<div style="color: black; background-color: white;">'''
172         text = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>'
173         text += '<div><a href=\"' + link + '\">' + title + "</a>"
174         text += "<BR /><small><i>Date: " + date + "</i></small></div>"
175         text += "<BR /><BR />"
176         text += content
177         return text    
178
179 class ArchivedArticles(Feed):
180     def addArchivedArticle(self, title, link, updated_parsed, configdir):
181         entry = {}
182         entry["title"] = title
183         entry["link"] = link
184         entry["downloaded"] = False
185         entry["summary"] = '<a href=\"' + link + '\">' + title + "</a>"
186         entry["updated_parsed"] = updated_parsed
187         entry["time"] = time.time()
188         self.entries.append(entry)
189         self.readItems[self.getUniqueId(len(self.entries)-1)] = False
190         self.countUnread = self.countUnread + 1
191         self.saveFeed(configdir)
192         #print entry
193         
194     def updateFeed(self, configdir, expiryTime=24):
195         for entry in self.getEntries():
196             if not entry["downloaded"]:
197                 try:
198                     f = urllib2.urlopen(entry["link"])
199                     entry["summary"] = f.read()
200                     f.close()
201                     if len(entry["summary"]) > 0:
202                         entry["downloaded"] = True
203                         entry["time"] = time.time()
204                 except:
205                     pass
206             currentTime = time.time()
207             expiry = float(expiryTime) * 3600
208             if currentTime - entry["time"] > expiry:
209                 self.entries.remove(entry)
210         self.updateTime = time.asctime()
211         self.saveFeed(configdir)
212
213     def getArticle(self, index):
214         self.setEntryRead(index)
215         content = self.getContent(index)
216         return content
217
218
219 class Listing:
220     # Lists all the feeds in a dictionary, and expose the data
221     def __init__(self, configdir):
222         self.configdir = configdir
223         self.feeds = {}
224         if isfile(self.configdir+"feeds.pickle"):
225             file = open(self.configdir+"feeds.pickle")
226             self.listOfFeeds = pickle.load(file)
227             file.close()
228         else:
229             self.listOfFeeds = {getId("Slashdot"):{"title":"Slashdot", "url":"http://rss.slashdot.org/Slashdot/slashdot"}, }
230         if self.listOfFeeds.has_key("font"):
231             del self.listOfFeeds["font"]
232         if self.listOfFeeds.has_key("feedingit-order"):
233             self.sortedKeys = self.listOfFeeds["feedingit-order"]
234         else:
235             self.sortedKeys = self.listOfFeeds.keys()
236             if "font" in self.sortedKeys:
237                 self.sortedKeys.remove("font")
238             self.sortedKeys.sort(key=lambda obj: self.getFeedTitle(obj))
239         for key in self.sortedKeys:
240             try:
241                 self.loadFeed(key)
242             except:
243                 self.sortedKeys.remove(key)
244         self.closeCurrentlyDisplayedFeed()
245         #self.saveConfig()
246
247     def addArchivedArticle(self, key, index):
248         title = self.getFeed(key).getTitle(index)
249         link = self.getFeed(key).getLink(index)
250         date = self.getFeed(key).getDate(index)
251         if not self.listOfFeeds.has_key(getId("Archived Articles")):
252             self.listOfFeeds[getId("Archived Articles")] = {"title":"Archived Articles", "url":""}
253             self.sortedKeys.append(getId("Archived Articles"))
254             self.feeds[getId("Archived Articles")] = ArchivedArticles("Archived Articles", "")
255             self.saveConfig()
256             
257         self.getFeed(getId("Archived Articles")).addArchivedArticle(title, link, date, self.configdir)
258         
259     def loadFeed(self, key):
260             if isfile(self.configdir+key):
261                 file = open(self.configdir+key)
262                 self.feeds[key] = pickle.load(file)
263                 file.close()
264             else:
265                 title = self.listOfFeeds[key]["title"]
266                 url = self.listOfFeeds[key]["url"]
267                 self.feeds[key] = Feed(title, url)
268         
269     def updateFeeds(self, expiryTime=24):
270         for key in self.getListOfFeeds():
271             self.feeds[key].updateFeed(self.configdir, expiryTime)
272             
273     def updateFeed(self, key, expiryTime=24):
274         self.feeds[key].updateFeed(self.configdir, expiryTime)
275         
276     def editFeed(self, key, title, url):
277         self.listOfFeeds[key]["title"] = title
278         self.listOfFeeds[key]["url"] = url
279         self.feeds[key].editFeed(url)
280             
281     def getFeed(self, key):
282         return self.feeds[key]
283     
284     def getFeedUpdateTime(self, key):
285         return self.feeds[key].getUpdateTime()
286     
287     def getFeedNumberOfUnreadItems(self, key):
288         return self.feeds[key].getNumberOfUnreadItems()
289    
290     def getFeedTitle(self, key):
291         return self.listOfFeeds[key]["title"]
292     
293     def getFeedUrl(self, key):
294         return self.listOfFeeds[key]["url"]
295     
296     def getListOfFeeds(self):
297         return self.sortedKeys
298     
299     def addFeed(self, title, url):
300         if not self.listOfFeeds.has_key(getId(title)):
301             self.listOfFeeds[getId(title)] = {"title":title, "url":url}
302             self.sortedKeys.append(getId(title))
303             self.saveConfig()
304             self.feeds[getId(title)] = Feed(title, url)
305             return True
306         else:
307             return False
308         
309     def removeFeed(self, key):
310         del self.listOfFeeds[key]
311         self.sortedKeys.remove(key)
312         del self.feeds[key]
313         if isfile(self.configdir+key):
314            remove(self.configdir+key)
315     
316     def saveConfig(self):
317         self.listOfFeeds["feedingit-order"] = self.sortedKeys
318         file = open(self.configdir+"feeds.pickle", "w")
319         pickle.dump(self.listOfFeeds, file)
320         file.close()
321         
322     def moveUp(self, key):
323         index = self.sortedKeys.index(key)
324         self.sortedKeys[index] = self.sortedKeys[index-1]
325         self.sortedKeys[index-1] = key
326         
327     def moveDown(self, key):
328         index = self.sortedKeys.index(key)
329         index2 = (index+1)%len(self.sortedKeys)
330         self.sortedKeys[index] = self.sortedKeys[index2]
331         self.sortedKeys[index2] = key
332         
333     def setCurrentlyDisplayedFeed(self, key):
334         self.currentlyDisplayedFeed = key
335     def closeCurrentlyDisplayedFeed(self):
336         self.currentlyDisplayedFeed = False
337     def getCurrentlyDisplayedFeed(self):
338         return self.currentlyDisplayedFeed