0.4.2-5 Edit feed option, loading screen
[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 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         entry = self.entries[index]
147         if entry.has_key('content'):
148             content = entry.content[0].value
149         elif entry.has_key('summary'):
150             content = entry.get('summary', '')
151         else:
152             content = entry.get('description', '')
153         return content
154     
155     def getArticle(self, index):
156         self.setEntryRead(index)
157         entry = self.entries[index]
158         title = entry.get('title', 'No title')
159         #content = entry.get('content', entry.get('summary_detail', {}))
160         content = self.getContent(index)
161
162         link = entry.get('link', 'NoLink')
163         if entry.has_key("updated_parsed"):
164             date = time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"])
165         elif entry.has_key("published_parsed"):
166             date = time.strftime("%a, %d %b %Y %H:%M:%S", entry["published_parsed"])
167         else:
168             date = ""
169         #text = '''<div style="color: black; background-color: white;">'''
170         text = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>'
171         text += '<div><a href=\"' + link + '\">' + title + "</a>"
172         text += "<BR /><small><i>Date: " + date + "</i></small></div>"
173         text += "<BR /><BR />"
174         text += content
175         return text    
176
177 class ArchivedArticles(Feed):
178     def addArchivedArticle(self, title, link, updated_parsed, configdir):
179         entry = {}
180         entry["title"] = title
181         entry["link"] = link
182         entry["downloaded"] = False
183         entry["summary"] = '<a href=\"' + link + '\">' + title + "</a>"
184         entry["updated_parsed"] = updated_parsed
185         entry["time"] = time.time()
186         self.entries.append(entry)
187         self.readItems[self.getUniqueId(len(self.entries)-1)] = False
188         self.countUnread = self.countUnread + 1
189         self.saveFeed(configdir)
190         #print entry
191         
192     def updateFeed(self, configdir, expiryTime=24):
193         for entry in self.getEntries():
194             if not entry["downloaded"]:
195                 try:
196                     f = urllib2.urlopen(entry["link"])
197                     entry["summary"] = f.read()
198                     f.close()
199                     if len(entry["summary"]) > 0:
200                         entry["downloaded"] = True
201                         entry["time"] = time.time()
202                 except:
203                     pass
204             currentTime = time.time()
205             expiry = float(expiryTime) * 3600
206             if currentTime - entry["time"] > expiry:
207                 self.entries.remove(entry)
208         self.updateTime = time.asctime()
209         self.saveFeed(configdir)
210
211     def getArticle(self, index):
212         self.setEntryRead(index)
213         content = self.getContent(index)
214         return content
215
216
217 class Listing:
218     # Lists all the feeds in a dictionary, and expose the data
219     def __init__(self, configdir):
220         self.configdir = configdir
221         self.feeds = {}
222         if isfile(self.configdir+"feeds.pickle"):
223             file = open(self.configdir+"feeds.pickle")
224             self.listOfFeeds = pickle.load(file)
225             file.close()
226         else:
227             self.listOfFeeds = {getId("Slashdot"):{"title":"Slashdot", "url":"http://rss.slashdot.org/Slashdot/slashdot"}, }
228         if self.listOfFeeds.has_key("font"):
229             del self.listOfFeeds["font"]
230         if self.listOfFeeds.has_key("feedingit-order"):
231             self.sortedKeys = self.listOfFeeds["feedingit-order"]
232         else:
233             self.sortedKeys = self.listOfFeeds.keys()
234             if "font" in self.sortedKeys:
235                 self.sortedKeys.remove("font")
236             self.sortedKeys.sort(key=lambda obj: self.getFeedTitle(obj))
237         for key in self.sortedKeys:
238             try:
239                 self.loadFeed(key)
240             except:
241                 self.sortedKeys.remove(key)
242         self.closeCurrentlyDisplayedFeed()
243         #self.saveConfig()
244
245     def addArchivedArticle(self, key, index):
246         title = self.getFeed(key).getTitle(index)
247         link = self.getFeed(key).getLink(index)
248         date = self.getFeed(key).getDate(index)
249         if not self.listOfFeeds.has_key(getId("Archived Articles")):
250             self.listOfFeeds[getId("Archived Articles")] = {"title":"Archived Articles", "url":""}
251             self.sortedKeys.append(getId("Archived Articles"))
252             self.feeds[getId("Archived Articles")] = ArchivedArticles("Archived Articles", "")
253             self.saveConfig()
254             
255         self.getFeed(getId("Archived Articles")).addArchivedArticle(title, link, date, self.configdir)
256         
257     def loadFeed(self, key):
258             if isfile(self.configdir+key):
259                 file = open(self.configdir+key)
260                 self.feeds[key] = pickle.load(file)
261                 file.close()
262             else:
263                 title = self.listOfFeeds[key]["title"]
264                 url = self.listOfFeeds[key]["url"]
265                 self.feeds[key] = Feed(title, url)
266         
267     def updateFeeds(self, expiryTime=24):
268         for key in self.getListOfFeeds():
269             self.feeds[key].updateFeed(self.configdir, expiryTime)
270             
271     def updateFeed(self, key, expiryTime=24):
272         self.feeds[key].updateFeed(self.configdir, expiryTime)
273         
274     def editFeed(self, key, url, title):
275         self.listOfFeeds[key]["title"] = title
276         self.listOfFeeds[key]["url"] = url
277         self.feeds[key].editFeed(url)
278             
279     def getFeed(self, key):
280         return self.feeds[key]
281     
282     def getFeedUpdateTime(self, key):
283         return self.feeds[key].getUpdateTime()
284     
285     def getFeedNumberOfUnreadItems(self, key):
286         return self.feeds[key].getNumberOfUnreadItems()
287    
288     def getFeedTitle(self, key):
289         return self.listOfFeeds[key]["title"]
290     
291     def getFeedUrl(self, key):
292         return self.listOfFeeds[key]["url"]
293     
294     def getListOfFeeds(self):
295         return self.sortedKeys
296     
297     def addFeed(self, title, url):
298         if not self.listOfFeeds.has_key(getId(title)):
299             self.listOfFeeds[getId(title)] = {"title":title, "url":url}
300             self.sortedKeys.append(getId(title))
301             self.saveConfig()
302             self.feeds[getId(title)] = Feed(title, url)
303             return True
304         else:
305             return False
306         
307     def removeFeed(self, key):
308         del self.listOfFeeds[key]
309         self.sortedKeys.remove(key)
310         del self.feeds[key]
311         if isfile(self.configdir+key):
312            remove(self.configdir+key)
313     
314     def saveConfig(self):
315         self.listOfFeeds["feedingit-order"] = self.sortedKeys
316         file = open(self.configdir+"feeds.pickle", "w")
317         pickle.dump(self.listOfFeeds, file)
318         file.close()
319         
320     def moveUp(self, key):
321         index = self.sortedKeys.index(key)
322         self.sortedKeys[index] = self.sortedKeys[index-1]
323         self.sortedKeys[index-1] = key
324         
325     def moveDown(self, key):
326         index = self.sortedKeys.index(key)
327         index2 = (index+1)%len(self.sortedKeys)
328         self.sortedKeys[index] = self.sortedKeys[index2]
329         self.sortedKeys[index2] = key
330         
331     def setCurrentlyDisplayedFeed(self, key):
332         self.currentlyDisplayedFeed = key
333     def closeCurrentlyDisplayedFeed(self):
334         self.currentlyDisplayedFeed = False
335     def getCurrentlyDisplayedFeed(self):
336         return self.currentlyDisplayedFeed