0.9beta
[feedingit] / src / rss_sqlite.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.5.4
23 # Description : Simple RSS Reader
24 # ============================================================================
25
26 import sqlite3
27 from os.path import isfile, isdir
28 from shutil import rmtree
29 from os import mkdir, remove, utime
30 import md5
31 import feedparser
32 import time
33 import urllib2
34 from BeautifulSoup import BeautifulSoup
35 from urlparse import urljoin
36 from calendar import timegm
37
38 def getId(string):
39     return md5.new(string).hexdigest()
40
41 class Feed:
42     def __init__(self, configdir, key):
43         self.key = key
44         self.configdir = configdir
45         self.dir = "%s/%s.d" %(self.configdir, self.key)
46         if not isdir(self.dir):
47             mkdir(self.dir)
48         if not isfile("%s/%s.db" %(self.dir, self.key)):
49             self.db = sqlite3.connect("%s/%s.db" %(self.dir, self.key) )
50             self.db.execute("CREATE TABLE feed (id text, title text, contentLink text, date float, updated float, link text, read int);")
51             self.db.execute("CREATE TABLE images (id text, imagePath text);")
52             self.db.commit()
53         else:
54             self.db = sqlite3.connect("%s/%s.db" %(self.dir, self.key) )
55
56     def addImage(self, configdir, key, baseurl, url):
57         filename = configdir+key+".d/"+getId(url)
58         if not isfile(filename):
59             try:
60                 f = urllib2.urlopen(urljoin(baseurl,url))
61                 outf = open(filename, "w")
62                 outf.write(f.read())
63                 f.close()
64                 outf.close()
65             except:
66                 print "Could not download " + url
67         else:
68             #open(filename,"a").close()  # "Touch" the file
69             file = open(filename,"a")
70             utime(filename, None)
71             file.close()
72         return filename
73
74     def updateFeed(self, configdir, url, etag, modified, expiryTime=24, proxy=None, imageCache=False):
75         # Expiry time is in hours
76         if proxy == None:
77             tmp=feedparser.parse(url, etag = etag, modified = modified)
78         else:
79             tmp=feedparser.parse(url, etag = etag, modified = modified, handlers = [proxy])
80         expiry = float(expiryTime) * 3600.
81
82         currentTime = 0
83         # Check if the parse was succesful (number of entries > 0, else do nothing)
84         if len(tmp["entries"])>0:
85            currentTime = time.time()
86            # The etag and modified value should only be updated if the content was not null
87            try:
88                etag = tmp["etag"]
89            except KeyError:
90                etag = None
91            try:
92                modified = tmp["modified"]
93            except KeyError:
94                modified = None
95            try:
96                f = urllib2.urlopen(urljoin(tmp["feed"]["link"],"/favicon.ico"))
97                data = f.read()
98                f.close()
99                outf = open(self.dir+"/favicon.ico", "w")
100                outf.write(data)
101                outf.close()
102                del data
103            except:
104                #import traceback
105                #traceback.print_exc()
106                 pass
107
108
109            #reversedEntries = self.getEntries()
110            #reversedEntries.reverse()
111
112            ids = self.getIds()
113
114            tmp["entries"].reverse()
115            for entry in tmp["entries"]:
116                date = self.extractDate(entry)
117                try:
118                    entry["title"]
119                except:
120                    entry["title"] = "No Title"
121                try:
122                    entry["link"]
123                except:
124                    entry["link"] = ""
125                try:
126                    entry["author"]
127                except:
128                    entry["author"] = None
129                if(not(entry.has_key("id"))):
130                    entry["id"] = None 
131                tmpEntry = {"title":entry["title"], "content":self.extractContent(entry),
132                             "date":date, "link":entry["link"], "author":entry["author"], "id":entry["id"]}
133                id = self.generateUniqueId(tmpEntry)
134                
135                #articleTime = time.mktime(self.entries[id]["dateTuple"])
136                if not id in ids:
137                    soup = BeautifulSoup(self.getArticle(tmpEntry)) #tmpEntry["content"])
138                    images = soup('img')
139                    baseurl = tmpEntry["link"]
140                    if imageCache:
141                       for img in images:
142                           try:
143                             filename = self.addImage(configdir, self.key, baseurl, img['src'])
144                             img['src']=filename
145                             self.db.execute("INSERT INTO images (id, imagePath) VALUES (?, ?);", (id, filename) )
146                           except:
147                               import traceback
148                               traceback.print_exc()
149                               print "Error downloading image %s" % img
150                    tmpEntry["contentLink"] = configdir+self.key+".d/"+id+".html"
151                    file = open(tmpEntry["contentLink"], "w")
152                    file.write(soup.prettify())
153                    file.close()
154                    values = (id, tmpEntry["title"], tmpEntry["contentLink"], tmpEntry["date"], currentTime, tmpEntry["link"], 0)
155                    self.db.execute("INSERT INTO feed (id, title, contentLink, date, updated, link, read) VALUES (?, ?, ?, ?, ?, ?, ?);", values)
156                else:
157                    try:
158                        self.db.execute("UPDATE feed SET updated=? WHERE id=?;", (currentTime, id) )
159                        self.db.commit()
160                        filename = configdir+self.key+".d/"+id+".html"
161                        file = open(filename,"a")
162                        utime(filename, None)
163                        file.close()
164                        images = self.db.execute("SELECT imagePath FROM images where id=?;", (id, )).fetchall()
165                        for image in images:
166                             file = open(image[0],"a")
167                             utime(image[0], None)
168                             file.close()
169                    except:
170                        pass
171            self.db.commit()
172             
173            
174         rows = self.db.execute("SELECT id FROM feed WHERE (read=0 AND updated<?) OR (read=1 AND updated<?);", (currentTime-2*expiry, currentTime-expiry))
175         for row in rows:
176            self.removeEntry(row[0])
177         
178         from glob import glob
179         from os import stat
180         for file in glob(configdir+self.key+".d/*"):
181             #
182             stats = stat(file)
183             #
184             # put the two dates into matching format
185             #
186             lastmodDate = stats[8]
187             #
188             expDate = time.time()-expiry*3
189             # check if image-last-modified-date is outdated
190             #
191             if expDate > lastmodDate:
192                 #
193                 try:
194                     #
195                     #print 'Removing', file
196                     #
197                     remove(file) # commented out for testing
198                     #
199                 except OSError:
200                     #
201                     print 'Could not remove', file
202         updateTime = 0
203         rows = self.db.execute("SELECT MAX(date) FROM feed;")
204         for row in rows:
205             updateTime=row[0]
206         return (updateTime, etag, modified)
207     
208     def setEntryRead(self, id):
209         self.db.execute("UPDATE feed SET read=1 WHERE id=?;", (id,) )
210         self.db.commit()
211         
212     def setEntryUnread(self, id):
213         self.db.execute("UPDATE feed SET read=0 WHERE id=?;", (id,) )
214         self.db.commit()     
215         
216     def markAllAsRead(self):
217         self.db.execute("UPDATE feed SET read=1 WHERE read=0;")
218         self.db.commit()
219
220     def isEntryRead(self, id):
221         read_status = self.db.execute("SELECT read FROM feed WHERE id=?;", (id,) ).fetchone()[0]
222         return read_status==1  # Returns True if read==1, and False if read==0
223     
224     def getTitle(self, id):
225         return self.db.execute("SELECT title FROM feed WHERE id=?;", (id,) ).fetchone()[0]
226     
227     def getContentLink(self, id):
228         return self.db.execute("SELECT contentLink FROM feed WHERE id=?;", (id,) ).fetchone()[0]
229     
230     def getExternalLink(self, id):
231         return self.db.execute("SELECT link FROM feed WHERE id=?;", (id,) ).fetchone()[0]
232     
233     def getDate(self, id):
234         dateStamp = self.db.execute("SELECT date FROM feed WHERE id=?;", (id,) ).fetchone()[0]
235         return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(dateStamp))
236
237     def getDateTuple(self, id):
238         dateStamp = self.db.execute("SELECT date FROM feed WHERE id=?;", (id,) ).fetchone()[0]
239         return time.localtime(dateStamp)
240     
241     def getDateStamp(self, id):
242         return self.db.execute("SELECT date FROM feed WHERE id=?;", (id,) ).fetchone()[0]
243     
244     def generateUniqueId(self, entry):
245         if(entry["id"] != None):
246             return getId(str(entry["id"]))
247         else:
248             return getId(str(entry["date"]) + str(entry["title"]))
249     
250     def getIds(self, onlyUnread=False):
251         if onlyUnread:
252             rows = self.db.execute("SELECT id FROM feed where read=0 ORDER BY date DESC;").fetchall()
253         else:
254             rows = self.db.execute("SELECT id FROM feed ORDER BY date DESC;").fetchall()
255         ids = []
256         for row in rows:
257             ids.append(row[0])
258         #ids.reverse()
259         return ids
260     
261     def getNextId(self, id):
262         ids = self.getIds()
263         index = ids.index(id)
264         return ids[(index+1)%len(ids)]
265         
266     def getPreviousId(self, id):
267         ids = self.getIds()
268         index = ids.index(id)
269         return ids[(index-1)%len(ids)]
270     
271     def getNumberOfUnreadItems(self):
272         return self.db.execute("SELECT count(*) FROM feed WHERE read=0;").fetchone()[0]
273     
274     def getNumberOfEntries(self):
275         return self.db.execute("SELECT count(*) FROM feed;").fetchone()[0]
276
277     def getArticle(self, entry):
278         #self.setEntryRead(id)
279         #entry = self.entries[id]
280         title = entry['title']
281         #content = entry.get('content', entry.get('summary_detail', {}))
282         content = entry["content"]
283
284         link = entry['link']
285         author = entry['author']
286         date = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(entry["date"]) )
287
288         #text = '''<div style="color: black; background-color: white;">'''
289         text = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
290         text += "<html><head><title>" + title + "</title>"
291         text += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>\n'
292         #text += '<style> body {-webkit-user-select: none;} </style>'
293         text += '</head><body background=\"white\"><div><a href=\"' + link + '\">' + title + "</a>"
294         if author != None:
295             text += "<BR /><small><i>Author: " + author + "</i></small>"
296         text += "<BR /><small><i>Date: " + date + "</i></small></div>"
297         text += "<BR /><BR />"
298         text += content
299         text += "</body></html>"
300         return text
301    
302     def getContent(self, id):
303         contentLink = self.db.execute("SELECT contentLink FROM feed WHERE id=?;", (id,)).fetchone()[0]
304         try:
305             file = open(self.entries[id]["contentLink"])
306             content = file.read()
307             file.close()
308         except:
309             content = "Content unavailable"
310         return content
311     
312     def extractDate(self, entry):
313         if entry.has_key("updated_parsed"):
314             return timegm(entry["updated_parsed"])
315         elif entry.has_key("published_parsed"):
316             return timegm(entry["published_parsed"])
317         else:
318             return time.time()
319         
320     def extractContent(self, entry):
321         content = ""
322         if entry.has_key('summary'):
323             content = entry.get('summary', '')
324         if entry.has_key('content'):
325             if len(entry.content[0].value) > len(content):
326                 content = entry.content[0].value
327         if content == "":
328             content = entry.get('description', '')
329         return content
330     
331     def removeEntry(self, id):
332         contentLink = self.db.execute("SELECT contentLink FROM feed WHERE id=?;", (id,)).fetchone()[0]
333         if contentLink:
334             try:
335                 os.remove(contentLink)
336             except:
337                 print "File not found for deletion: %s" % contentLink
338         self.db.execute("DELETE FROM feed WHERE id=?;", (id,) )
339         self.db.execute("DELETE FROM images WHERE id=?;", (id,) )
340         self.db.commit()
341  
342 class ArchivedArticles(Feed):    
343     def addArchivedArticle(self, title, link, date, configdir):
344         id = self.generateUniqueId({"date":date, "title":title})
345         values = (id, title, link, date, 0, link, 0)
346         self.db.execute("INSERT INTO feed (id, title, contentLink, date, updated, link, read) VALUES (?, ?, ?, ?, ?, ?, ?);", values)
347         self.db.commit()
348
349     def updateFeed(self, configdir, url, etag, modified, expiryTime=24, proxy=None, imageCache=False):
350         currentTime = 0
351         rows = self.db.execute("SELECT id, link FROM feed WHERE updated=0;")
352         for row in rows:
353             currentTime = time.time()
354             id = row[0]
355             link = row[1]
356             f = urllib2.urlopen(link)
357             #entry["content"] = f.read()
358             html = f.read()
359             f.close()
360             soup = BeautifulSoup(html)
361             images = soup('img')
362             baseurl = link
363             for img in images:
364                 filename = self.addImage(configdir, self.key, baseurl, img['src'])
365                 img['src']=filename
366                 self.db.execute("INSERT INTO images (id, imagePath) VALUES (?, ?);", (id, filename) )
367             contentLink = configdir+self.key+".d/"+id+".html"
368             file = open(contentLink, "w")
369             file.write(soup.prettify())
370             file.close()
371             
372             self.db.execute("UPDATE feed SET read=0, contentLink=?, updated=? WHERE id=?;", (contentLink, time.time(), id) )
373             self.db.commit()
374         return (currentTime, None, None)
375     
376     def purgeReadArticles(self):
377         rows = self.db.execute("SELECT id FROM feed WHERE read=1;")
378         #ids = self.getIds()
379         for row in rows:
380             self.removeArticle(row[0])
381
382     def removeArticle(self, id):
383         rows = self.db.execute("SELECT imagePath FROM images WHERE id=?;", (id,) )
384         for row in rows:
385             try:
386                 count = self.db.execute("SELECT count(*) FROM images WHERE id!=? and imagePath=?;", (id,row[0]) ).fetchone()[0]
387                 if count == 0:
388                     os.remove(row[0])
389             except:
390                 pass
391         self.removeEntry(id)
392
393 class Listing:
394     # Lists all the feeds in a dictionary, and expose the data
395     def __init__(self, configdir):
396         self.configdir = configdir
397         
398         self.db = sqlite3.connect("%s/feeds.db" % self.configdir)
399         
400         try:
401             table = self.db.execute("SELECT sql FROM sqlite_master").fetchone()
402             if table == None:
403                 self.db.execute("CREATE TABLE feeds(id text, url text, title text, unread int, updateTime float, rank int, etag text, modified text, widget int, category int);")
404                 self.db.execute("CREATE TABLE categories(id text, title text, unread int, rank int);")
405                 self.addCategory("Default Category")
406                 if isfile(self.configdir+"feeds.pickle"):
407                     self.importOldFormatFeeds()
408                 else:
409                     self.addFeed("Maemo News", "http://maemo.org/news/items.xml")    
410             else:
411                 from string import find, upper
412                 if find(upper(table[0]), "WIDGET")<0:
413                     self.db.execute("ALTER TABLE feeds ADD COLUMN widget int;")
414                     self.db.execute("UPDATE feeds SET widget=1;")
415                     self.db.commit()
416                 if find(upper(table[0]), "CATEGORY")<0:
417                     self.db.execute("CREATE TABLE categories(id text, title text, unread int, rank int);")
418                     self.addCategory("Default Category")
419                     self.db.execute("ALTER TABLE feeds ADD COLUMN category int;")
420                     self.db.execute("UPDATE feeds SET category=1;")
421                     self.db.commit()
422         except:
423             pass
424
425     def importOldFormatFeeds(self):
426         """This function loads feeds that are saved in an outdated format, and converts them to sqlite"""
427         import rss
428         listing = rss.Listing(self.configdir)
429         rank = 0
430         for id in listing.getListOfFeeds():
431             try:
432                 rank += 1
433                 values = (id, listing.getFeedTitle(id) , listing.getFeedUrl(id), 0, time.time(), rank, None, "None", 1)
434                 self.db.execute("INSERT INTO feeds (id, title, url, unread, updateTime, rank, etag, modified, widget, category) VALUES (?, ?, ? ,? ,? ,?, ?, ?, ?, 1);", values)
435                 self.db.commit()
436                 
437                 feed = listing.getFeed(id)
438                 new_feed = self.getFeed(id)
439                 
440                 items = feed.getIds()[:]
441                 items.reverse()
442                 for item in items:
443                         if feed.isEntryRead(item):
444                             read_status = 1
445                         else:
446                             read_status = 0 
447                         date = timegm(feed.getDateTuple(item))
448                         title = feed.getTitle(item)
449                         newId = new_feed.generateUniqueId({"date":date, "title":title})
450                         values = (newId, title , feed.getContentLink(item), date, time.time(), feed.getExternalLink(item), read_status)
451                         new_feed.db.execute("INSERT INTO feed (id, title, contentLink, date, updated, link, read) VALUES (?, ?, ?, ?, ?, ?, ?);", values)
452                         new_feed.db.commit()
453                         try:
454                             images = feed.getImages(item)
455                             for image in images:
456                                 new_feed.db.execute("INSERT INTO images (id, imagePath) VALUES (?, ?);", (item, image) )
457                                 new_feed.db.commit()
458                         except:
459                             pass
460                 self.updateUnread(id)
461             except:
462                 import traceback
463                 traceback.print_exc()
464         remove(self.configdir+"feeds.pickle")
465                 
466         
467     def addArchivedArticle(self, key, index):
468         feed = self.getFeed(key)
469         title = feed.getTitle(index)
470         link = feed.getExternalLink(index)
471         date = feed.getDate(index)
472         count = self.db.execute("SELECT count(*) FROM feeds where id=?;", ("ArchivedArticles",) ).fetchone()[0]
473         if count == 0:
474             self.addFeed("Archived Articles", "", id="ArchivedArticles")
475
476         archFeed = self.getFeed("ArchivedArticles")
477         archFeed.addArchivedArticle(title, link, date, self.configdir)
478         self.updateUnread("ArchivedArticles")
479         
480     def updateFeed(self, key, expiryTime=24, proxy=None, imageCache=False):
481         feed = self.getFeed(key)
482         db = sqlite3.connect("%s/feeds.db" % self.configdir)
483         (url, etag, modified) = db.execute("SELECT url, etag, modified FROM feeds WHERE id=?;", (key,) ).fetchone()
484         (updateTime, etag, modified) = feed.updateFeed(self.configdir, url, etag, eval(modified), expiryTime, proxy, imageCache)
485         if updateTime > 0:
486             db.execute("UPDATE feeds SET updateTime=?, etag=?, modified=? WHERE id=?;", (updateTime, etag, str(modified), key) )
487         else:
488             db.execute("UPDATE feeds SET etag=?, modified=? WHERE id=?;", (etag, str(modified), key) )
489         db.commit()
490         self.updateUnread(key, db=db)
491         
492     def getFeed(self, key):
493         if key == "ArchivedArticles":
494             return ArchivedArticles(self.configdir, key)
495         return Feed(self.configdir, key)
496         
497     def editFeed(self, key, title, url, category=None):
498         if category:
499             self.db.execute("UPDATE feeds SET title=?, url=?, category=? WHERE id=?;", (title, url, category, key))
500         else:
501             self.db.execute("UPDATE feeds SET title=?, url=? WHERE id=?;", (title, url, key))
502         self.db.commit()
503         
504     def getFeedUpdateTime(self, key):
505         return time.ctime(self.db.execute("SELECT updateTime FROM feeds WHERE id=?;", (key,)).fetchone()[0])
506         
507     def getFeedNumberOfUnreadItems(self, key):
508         return self.db.execute("SELECT unread FROM feeds WHERE id=?;", (key,)).fetchone()[0]
509         
510     def getFeedTitle(self, key):
511         return self.db.execute("SELECT title FROM feeds WHERE id=?;", (key,)).fetchone()[0]
512         
513     def getFeedUrl(self, key):
514         return self.db.execute("SELECT url FROM feeds WHERE id=?;", (key,)).fetchone()[0]
515         
516     def getListOfFeeds(self, category=None):
517         if category:
518             rows = self.db.execute("SELECT id FROM feeds WHERE category=? ORDER BY rank;", (category, ) )
519         else:
520             rows = self.db.execute("SELECT id FROM feeds ORDER BY rank;" )
521         keys = []
522         for row in rows:
523             if row[0]:
524                 keys.append(row[0])
525         return keys
526     
527     def getListOfCategories(self):
528         rows = self.db.execute("SELECT id FROM categories ORDER BY rank;" )
529         keys = []
530         for row in rows:
531             if row[0]:
532                 keys.append(row[0])
533         return keys
534     
535     def getCategoryTitle(self, id):
536         row = self.db.execute("SELECT title FROM categories WHERE id=?;", (id, )).fetchone()
537         return row[0]
538     
539     def getSortedListOfKeys(self, order, onlyUnread=False, category=1):
540         if   order == "Most unread":
541             tmp = "ORDER BY unread DESC"
542             #keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][1], reverse=True)
543         elif order == "Least unread":
544             tmp = "ORDER BY unread"
545             #keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][1])
546         elif order == "Most recent":
547             tmp = "ORDER BY updateTime DESC"
548             #keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][2], reverse=True)
549         elif order == "Least recent":
550             tmp = "ORDER BY updateTime"
551             #keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][2])
552         else: # order == "Manual" or invalid value...
553             tmp = "ORDER BY rank"
554             #keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][0])
555         if onlyUnread:
556             sql = "SELECT id FROM feeds WHERE unread>0 WHERE category=%s" %category + tmp 
557         else:
558             sql = "SELECT id FROM feeds WHERE category=%s " %category + tmp
559         rows = self.db.execute(sql)
560         keys = []
561         for row in rows:
562             if row[0]:
563                 keys.append(row[0])
564         return keys
565     
566     def getFavicon(self, key):
567         filename = "%s%s.d/favicon.ico" % (self.configdir, key)
568         if isfile(filename):
569             return filename
570         else:
571             return False
572         
573     def updateUnread(self, key, db=None):
574         if db == None:
575             db = self.db
576         feed = self.getFeed(key)
577         db.execute("UPDATE feeds SET unread=? WHERE id=?;", (feed.getNumberOfUnreadItems(), key))
578         db.commit()
579
580     def addFeed(self, title, url, id=None, category=1):
581         if not id:
582             id = getId(title)
583         count = self.db.execute("SELECT count(*) FROM feeds WHERE id=?;", (id,) ).fetchone()[0]
584         if count == 0:
585             max_rank = self.db.execute("SELECT MAX(rank) FROM feeds;").fetchone()[0]
586             if max_rank == None:
587                 max_rank = 0
588             values = (id, title, url, 0, 0, max_rank+1, None, "None", 1, category)
589             self.db.execute("INSERT INTO feeds (id, title, url, unread, updateTime, rank, etag, modified, widget, category) VALUES (?, ?, ? ,? ,? ,?, ?, ?, ?,?);", values)
590             self.db.commit()
591             # Ask for the feed object, it will create the necessary tables
592             self.getFeed(id)
593             return True
594         else:
595             return False
596         
597     def addCategory(self, title):
598         rank = self.db.execute("SELECT MAX(rank)+1 FROM categories;").fetchone()[0]
599         if rank==None:
600             rank=1
601         id = self.db.execute("SELECT MAX(id)+1 FROM categories;").fetchone()[0]
602         if id==None:
603             id=1
604         self.db.execute("INSERT INTO categories (id, title, unread, rank) VALUES (?, ?, 0, ?)", (id, title, rank))
605         self.db.commit()
606     
607     def removeFeed(self, key):
608         rank = self.db.execute("SELECT rank FROM feeds WHERE id=?;", (key,) ).fetchone()[0]
609         self.db.execute("DELETE FROM feeds WHERE id=?;", (key, ))
610         self.db.execute("UPDATE feeds SET rank=rank-1 WHERE rank>?;", (rank,) )
611         self.db.commit()
612
613         if isdir(self.configdir+key+".d/"):
614            rmtree(self.configdir+key+".d/")
615            
616     def removeCategory(self, key):
617         if self.db.execute("SELECT count(*) FROM categories;").fetchone()[0] > 1:
618             rank = self.db.execute("SELECT rank FROM categories WHERE id=?;", (key,) ).fetchone()[0]
619             self.db.execute("DELETE FROM categories WHERE id=?;", (key, ))
620             self.db.execute("UPDATE categories SET rank=rank-1 WHERE rank>?;", (rank,) )
621             self.db.execute("UPDATE feeds SET category=1 WHERE category=?;", (key,) )
622             self.db.commit()
623         
624     #def saveConfig(self):
625     #    self.listOfFeeds["feedingit-order"] = self.sortedKeys
626     #    file = open(self.configdir+"feeds.pickle", "w")
627     #    pickle.dump(self.listOfFeeds, file)
628     #    file.close()
629         
630     def moveUp(self, key):
631         rank = self.db.execute("SELECT rank FROM feeds WHERE id=?;", (key,)).fetchone()[0]
632         if rank>0:
633             self.db.execute("UPDATE feeds SET rank=? WHERE rank=?;", (rank, rank-1) )
634             self.db.execute("UPDATE feeds SET rank=? WHERE id=?;", (rank-1, key) )
635             self.db.commit()
636             
637     def moveCategoryUp(self, key):
638         rank = self.db.execute("SELECT rank FROM categories WHERE id=?;", (key,)).fetchone()[0]
639         if rank>0:
640             self.db.execute("UPDATE categories SET rank=? WHERE rank=?;", (rank, rank-1) )
641             self.db.execute("UPDATE categories SET rank=? WHERE id=?;", (rank-1, key) )
642             self.db.commit()
643         
644     def moveDown(self, key):
645         rank = self.db.execute("SELECT rank FROM feeds WHERE id=?;", (key,)).fetchone()[0]
646         max_rank = self.db.execute("SELECT MAX(rank) FROM feeds;").fetchone()[0]
647         if rank<max_rank:
648             self.db.execute("UPDATE feeds SET rank=? WHERE rank=?;", (rank, rank+1) )
649             self.db.execute("UPDATE feeds SET rank=? WHERE id=?;", (rank+1, key) )
650             self.db.commit()
651             
652     def moveCategoryDown(self, key):
653         rank = self.db.execute("SELECT rank FROM categories WHERE id=?;", (key,)).fetchone()[0]
654         max_rank = self.db.execute("SELECT MAX(rank) FROM categories;").fetchone()[0]
655         if rank<max_rank:
656             self.db.execute("UPDATE categories SET rank=? WHERE rank=?;", (rank, rank+1) )
657             self.db.execute("UPDATE categories SET rank=? WHERE id=?;", (rank+1, key) )
658             self.db.commit()
659             
660