5083ce7690c57f8363ac365070418402a2986343
[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 setEntryUnread(self, index):
100         if self.readItems[self.getUniqueId(index)]==True:
101             self.countUnread = self.countUnread + 1
102             self.readItems[self.getUniqueId(index)] = False
103     
104     def isEntryRead(self, index):
105         return self.readItems[self.getUniqueId(index)]
106     
107     def getTitle(self, index):
108         return self.entries[index]["title"]
109     
110     def getLink(self, index):
111         return self.entries[index]["link"]
112     
113     def getDate(self, index):
114         try:
115             return self.entries[index]["updated_parsed"]
116         except:
117             return time.localtime()
118     
119     def getUniqueId(self, index, entry=None):
120         if index >=0:
121             entry = self.entries[index]
122         if entry.has_key("updated_parsed"):
123             return getId(time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"]) + entry["title"])
124         elif entry.has_key("link"):
125             return getId(entry["link"] + entry["title"])
126         else:
127             return getId(entry["title"])
128     
129     def getUpdateTime(self):
130         return self.updateTime
131     
132     def getEntries(self):
133         try:
134             return self.entries
135         except:
136             return []
137     
138     def getNumberOfUnreadItems(self):
139         return self.countUnread
140     
141     def getNumberOfEntries(self):
142         return len(self.entries)
143     
144     def getItem(self, index):
145         try:
146             return self.entries[index]
147         except:
148             return []
149     
150     def getContent(self, index):
151         content = ""
152         entry = self.entries[index]
153         if entry.has_key('summary'):
154             content = entry.get('summary', '')
155         if entry.has_key('content'):
156             if len(entry.content[0].value) > len(content):
157                 content = entry.content[0].value
158         if content == "":
159             content = entry.get('description', '')
160         return content
161     
162     def getArticle(self, index):
163         self.setEntryRead(index)
164         entry = self.entries[index]
165         title = entry.get('title', 'No title')
166         #content = entry.get('content', entry.get('summary_detail', {}))
167         content = self.getContent(index)
168
169         link = entry.get('link', 'NoLink')
170         if entry.has_key("updated_parsed"):
171             date = time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"])
172         elif entry.has_key("published_parsed"):
173             date = time.strftime("%a, %d %b %Y %H:%M:%S", entry["published_parsed"])
174         else:
175             date = ""
176         #text = '''<div style="color: black; background-color: white;">'''
177         text = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>'
178         text += '<head><style> body {-webkit-user-select: none;} </style></head>'
179         text += '<body><div><a href=\"' + link + '\">' + title + "</a>"
180         text += "<BR /><small><i>Date: " + date + "</i></small></div>"
181         text += "<BR /><BR />"
182         text += content
183         text += "</body>"
184         return text
185
186 class ArchivedArticles(Feed):
187     def addArchivedArticle(self, title, link, updated_parsed, configdir):
188         entry = {}
189         entry["title"] = title
190         entry["link"] = link
191         entry["downloaded"] = False
192         entry["summary"] = '<a href=\"' + link + '\">' + title + "</a>"
193         entry["updated_parsed"] = updated_parsed
194         entry["time"] = time.time()
195         self.entries.append(entry)
196         self.readItems[self.getUniqueId(len(self.entries)-1)] = False
197         self.countUnread = self.countUnread + 1
198         self.saveFeed(configdir)
199         #print entry
200         
201     def updateFeed(self, configdir, expiryTime=24):
202         index = 0
203         for entry in self.getEntries():
204             if not entry["downloaded"]:
205                 try:
206                     f = urllib2.urlopen(entry["link"])
207                     entry["summary"] = f.read()
208                     f.close()
209                     if len(entry["summary"]) > 0:
210                         entry["downloaded"] = True
211                         entry["time"] = time.time()
212                         self.setEntryUnread(index)
213                 except:
214                     pass
215             currentTime = time.time()
216             expiry = float(expiryTime) * 3600
217             if currentTime - entry["time"] > expiry:
218                 self.entries.remove(entry)
219             index += 1
220         self.updateTime = time.asctime()
221         self.saveFeed(configdir)
222
223     def getArticle(self, index):
224         self.setEntryRead(index)
225         content = self.getContent(index)
226         return content
227
228
229 class Listing:
230     # Lists all the feeds in a dictionary, and expose the data
231     def __init__(self, configdir):
232         self.configdir = configdir
233         self.feeds = {}
234         if isfile(self.configdir+"feeds.pickle"):
235             file = open(self.configdir+"feeds.pickle")
236             self.listOfFeeds = pickle.load(file)
237             file.close()
238         else:
239             self.listOfFeeds = {getId("Slashdot"):{"title":"Slashdot", "url":"http://rss.slashdot.org/Slashdot/slashdot"}, }
240         if self.listOfFeeds.has_key("font"):
241             del self.listOfFeeds["font"]
242         if self.listOfFeeds.has_key("feedingit-order"):
243             self.sortedKeys = self.listOfFeeds["feedingit-order"]
244         else:
245             self.sortedKeys = self.listOfFeeds.keys()
246             if "font" in self.sortedKeys:
247                 self.sortedKeys.remove("font")
248             self.sortedKeys.sort(key=lambda obj: self.getFeedTitle(obj))
249         list = self.sortedKeys[:]
250         for key in list:
251             try:
252                 self.loadFeed(key)
253             except:
254                 #import traceback
255                 #if key.startswith('d8'):
256                 #traceback.print_exc()
257                 self.sortedKeys.remove(key)
258             #print key
259                 #print key in self.sortedKeys
260         #print "d8eb3f07572892a7b5ed9c81c5bb21a2" in self.sortedKeys
261         #print self.listOfFeeds["d8eb3f07572892a7b5ed9c81c5bb21a2"]
262         self.closeCurrentlyDisplayedFeed()
263         #self.saveConfig()
264
265     def addArchivedArticle(self, key, index):
266         title = self.getFeed(key).getTitle(index)
267         link = self.getFeed(key).getLink(index)
268         date = self.getFeed(key).getDate(index)
269         if not self.listOfFeeds.has_key(getId("Archived Articles")):
270             self.listOfFeeds[getId("Archived Articles")] = {"title":"Archived Articles", "url":""}
271             self.sortedKeys.append(getId("Archived Articles"))
272             self.feeds[getId("Archived Articles")] = ArchivedArticles("Archived Articles", "")
273             self.saveConfig()
274             
275         self.getFeed(getId("Archived Articles")).addArchivedArticle(title, link, date, self.configdir)
276         
277     def loadFeed(self, key):
278             if isfile(self.configdir+key):
279                 file = open(self.configdir+key)
280                 self.feeds[key] = pickle.load(file)
281                 file.close()
282             else:
283                 #print key
284                 title = self.listOfFeeds[key]["title"]
285                 url = self.listOfFeeds[key]["url"]
286                 self.feeds[key] = Feed(title, url)
287         
288     def updateFeeds(self, expiryTime=24):
289         for key in self.getListOfFeeds():
290             self.feeds[key].updateFeed(self.configdir, expiryTime)
291             
292     def updateFeed(self, key, expiryTime=24):
293         self.feeds[key].updateFeed(self.configdir, expiryTime)
294         
295     def editFeed(self, key, title, url):
296         self.listOfFeeds[key]["title"] = title
297         self.listOfFeeds[key]["url"] = url
298         self.feeds[key].editFeed(url)
299             
300     def getFeed(self, key):
301         return self.feeds[key]
302     
303     def getFeedUpdateTime(self, key):
304         #print self.listOfFeeds.has_key(key)
305         return self.feeds[key].getUpdateTime()
306     
307     def getFeedNumberOfUnreadItems(self, key):
308         return self.feeds[key].getNumberOfUnreadItems()
309    
310     def getFeedTitle(self, key):
311         return self.listOfFeeds[key]["title"]
312     
313     def getFeedUrl(self, key):
314         return self.listOfFeeds[key]["url"]
315     
316     def getListOfFeeds(self):
317         return self.sortedKeys
318     
319     def addFeed(self, title, url):
320         if not self.listOfFeeds.has_key(getId(title)):
321             self.listOfFeeds[getId(title)] = {"title":title, "url":url}
322             self.sortedKeys.append(getId(title))
323             self.saveConfig()
324             self.feeds[getId(title)] = Feed(title, url)
325             return True
326         else:
327             return False
328         
329     def removeFeed(self, key):
330         del self.listOfFeeds[key]
331         self.sortedKeys.remove(key)
332         del self.feeds[key]
333         if isfile(self.configdir+key):
334            remove(self.configdir+key)
335         self.saveConfig()
336     
337     def saveConfig(self):
338         self.listOfFeeds["feedingit-order"] = self.sortedKeys
339         file = open(self.configdir+"feeds.pickle", "w")
340         pickle.dump(self.listOfFeeds, file)
341         file.close()
342         
343     def moveUp(self, key):
344         index = self.sortedKeys.index(key)
345         self.sortedKeys[index] = self.sortedKeys[index-1]
346         self.sortedKeys[index-1] = key
347         
348     def moveDown(self, key):
349         index = self.sortedKeys.index(key)
350         index2 = (index+1)%len(self.sortedKeys)
351         self.sortedKeys[index] = self.sortedKeys[index2]
352         self.sortedKeys[index2] = key
353         
354     def setCurrentlyDisplayedFeed(self, key):
355         self.currentlyDisplayedFeed = key
356     def closeCurrentlyDisplayedFeed(self):
357         self.currentlyDisplayedFeed = False
358     def getCurrentlyDisplayedFeed(self):
359         return self.currentlyDisplayedFeed
360     
361 if __name__ == "__main__":
362     listing = Listing('/home/user/.feedingit/')
363     list = listing.getListOfFeeds()[:]
364         #list.reverse()
365     for key in list:
366         if key.startswith('d8'):
367             print listing.getFeedUpdateTime(key)