use sqlite as database backend

This commit is contained in:
josch 2012-07-01 11:39:25 +02:00
parent 776c30770f
commit b55097e8c3
2 changed files with 152 additions and 54 deletions

View file

@ -29,7 +29,7 @@ from urlparse import urlparse, urlunparse, urljoin
import feedparser
from lxml import etree
from cStringIO import StringIO
import shelve
import sqlite_db
import time
import datetime
import os, re
@ -97,7 +97,7 @@ def markup_escape_text(text):
"""
if not text:
return ""
return GLib.markup_escape_text(text.encode('utf-8'))
return GLib.markup_escape_text(text)
class TabLabel(Gtk.HBox):
"""A class for Tab labels"""
@ -489,16 +489,13 @@ class EntryTree(Gtk.TreeView):
_, it = selection.get_selected()
if not it: return
item = self.get_model().get_value(it, 0)
entry = self.feeddb[self.feedurl]
if entry['items'][item]['unread']:
title = markup_escape_text(entry['items'][item]['title'])
date = get_time_pretty(entry['items'][item]['date'])
entry = self.feeddb.get_entry(self.feedurl, item)
if entry['unread']:
title = markup_escape_text(entry['title'])
date = get_time_pretty(entry['date'])
self.get_model().set_value(it, 1, title)
self.get_model().set_value(it, 2, date)
entry['items'][item]['unread'] = False
entry['unread'] -= 1
self.feeddb[self.feedurl] = entry
#self.feeddb.sync() # dont sync on every unread item - makes stuff extremely slow over time
self.feeddb.mark_read(self.feedurl, item)
self.emit("item-selected", self.feedurl, item)
self.connect("cursor-changed", on_cursor_changed_cb)
@ -517,11 +514,11 @@ class EntryTree(Gtk.TreeView):
self.feedurl = None
for feedurl in config:
if self.feeddb.get(feedurl):
if self.feeddb.feed_exists(feedurl):
self.update(feedurl)
def display(self, feedurl):
if not feedurl or feedurl not in self.feeddb:
if not feedurl or not self.feeddb.feed_exists(feedurl):
self.set_model(self.empty_model)
self.feedurl = None
else:
@ -532,14 +529,13 @@ class EntryTree(Gtk.TreeView):
model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
# using model.set_sort_column_id is horribly slow, so append them
# sorted instead
items = sorted(self.feeddb[feedurl]['items'].iteritems(), key=lambda x: x[1]['date'], reverse=True)
for guid, value in items:
for value in self.feeddb.get_entries_all(feedurl):
title = markup_escape_text(value.get('title', ""))
date = get_time_pretty(value['date'])
if value['unread']:
title = "<b>"+title+"</b>"
date = "<b>"+date+"</b>"
model.append([guid, title, date])
model.append([value['entry'], title, date])
def compare_date(model, a, b, data):
item1 = model.get_value(a, 0)
item2 = model.get_value(b, 0)
@ -609,15 +605,16 @@ class FeedTree(Gtk.TreeView):
for category, feeds in categories.items():
it = self.model.append(None, [None, category, folder_icon, None])
for feedurl in feeds:
if self.feeddb.get(feedurl):
feed_icon = self.feeddb[feedurl].get('favicon')
if self.feeddb.feed_exists(feedurl):
feed = self.feeddb.get_feed(feedurl)
feed_icon = feed.get('favicon')
if feed_icon:
feed_icon = pixbuf_new_from_file_in_memory(feed_icon, (16, 16))
else:
feed_icon = self.render_icon(Gtk.STOCK_FILE, Gtk.IconSize.MENU, None)
label = markup_escape_text(self.feeddb[feedurl].get('title', feedurl))
unread = self.feeddb[feedurl].get('unread')
label = markup_escape_text(feed.get('title', feedurl))
unread = feed.get('unread')
if unread > 0:
label = "<b>"+label+" (%d)"%unread+"</b>"
else:
@ -656,13 +653,9 @@ class FeedTree(Gtk.TreeView):
def mark_read(self, it, sync=True):
feedurl = self.model.get_value(it, 0)
entry = self.feeddb.get(feedurl)
if not entry: return
entry['unread'] = 0
for item in entry['items'].values():
item['unread'] = False
self.model.set_value(it, 1, markup_escape_text(entry['title']))
self.feeddb[feedurl] = entry
feed = self.feeddb.get_feed(feedurl)
self.feeddb.mark_read_feed(feedurl)
self.model.set_value(it, 1, markup_escape_text(feed['title']))
self.emit("update-feed", feedurl)
if sync:
self.feeddb.sync()
@ -709,8 +702,9 @@ class FeedTree(Gtk.TreeView):
itc = self.model.iter_children(it)
while (itc):
feedurl = self.model.get_value(itc, 0)
title = markup_escape_text(self.feeddb[feedurl]['title'])
unread = self.feeddb[feedurl]['unread']
feed = self.feeddb.get_feed(feedurl)
title = markup_escape_text(feed['title'])
unread = feed['unread']
if unread > 0:
title = "<b>"+title+" (%d)"%unread+"</b>"
self.model.set_value(itc, 1, title)
@ -758,10 +752,11 @@ class FeedTree(Gtk.TreeView):
def update_feed(self, it):
feedurl = self.model.get_value(it, 0)
msg = Soup.Message.new("GET", feedurl)
if self.feeddb.get(feedurl) and self.feeddb[feedurl].get('etag'):
msg.request_headers.append('If-None-Match', self.feeddb[feedurl]['etag'])
if self.feeddb.get(feedurl) and self.feeddb[feedurl].get('lastmodified'):
msg.request_headers.append('If-Modified-Since', self.feeddb[feedurl]['lastmodified'])
feed = self.feeddb.get_feed(feedurl)
if feed.get('etag'):
msg.request_headers.append('If-None-Match', feed['etag'])
if feed.get('lastmodified'):
msg.request_headers.append('If-Modified-Since', feed['lastmodified'])
def complete_cb(session, msg, it):
if msg.status_code not in [200, 304]:
@ -771,7 +766,7 @@ class FeedTree(Gtk.TreeView):
return
# get existing feedentry or create new one
entry = self.feeddb.get(feedurl, dict())
entry = self.feeddb.get_feed(feedurl)
if entry.get('favicon'):
icon = pixbuf_new_from_file_in_memory(entry['favicon'], (16, 16))
@ -796,7 +791,7 @@ class FeedTree(Gtk.TreeView):
entry['lastmodified'] = msg.response_headers.get_one('Last-Modified')
try:
feed = feedparser.parse(msg.response_body.flatten().get_data())
feedparse = feedparser.parse(msg.response_body.flatten().get_data())
except:
print "error parsing feed:"
print msg.response_body.flatten().get_data()
@ -805,14 +800,14 @@ class FeedTree(Gtk.TreeView):
self.update_feed_done(feedurl)
return
if feed.bozo != 0:
if feedparse.bozo != 0:
# retrieved data was no valid feed
error_icon = self.render_icon(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.MENU, None)
self.model.set_value(it, 2, error_icon)
self.update_feed_done(feedurl)
return
entry['title'] = feed.feed.get('title')
entry['title'] = feedparse.feed.get('title')
self.model.set_value(it, 1, markup_escape_text(entry['title']))
# assumption: favicon never changes
@ -820,14 +815,14 @@ class FeedTree(Gtk.TreeView):
self.updating.add(feedurl+"_icon")
self.update_icon(it, feedurl)
for item in feed.entries:
for item in feedparse.entries:
# use guid with fallback to link as identifier
itemid = item.get("id", item.get("link"))
if not itemid:
# TODO: display error "cannot identify feeditems"
break
if entry['items'].has_key(itemid):
if self.feeddb.entry_exists(feedurl, itemid):
# already exists
continue
@ -836,7 +831,7 @@ class FeedTree(Gtk.TreeView):
'title': item.get('title'),
'date': item.get('published_parsed'),
'content': item.get('content'),
'categories': [cat for _, cat in item.get('categories', [])] or None,
'categories': ','.join([cat for _, cat in item.get('categories', [])]) or "",
'unread': True
}
@ -858,14 +853,14 @@ class FeedTree(Gtk.TreeView):
else:
new_item['content'] = ""
entry['items'][itemid] = new_item
self.feeddb.add_entry(feedurl, itemid, new_item)
entry['unread'] += 1
if entry['unread'] > 0:
self.model.set_value(it, 1, '<b>'+markup_escape_text(entry['title'])+" (%d)"%entry['unread']+'</b>')
self.feeddb[feedurl] = entry
self.feeddb.update_feed(feedurl, entry)
self.emit("update-feed", feedurl)
@ -894,9 +889,7 @@ class FeedTree(Gtk.TreeView):
data = msg.response_body.flatten().get_data()
if len(data):
icon = pixbuf_new_from_file_in_memory(data, (16, 16))
entry = self.feeddb[feedurl]
entry['favicon'] = data
self.feeddb[feedurl] = entry
self.feeddb.set_favicon(feedurl, data)
self.model.set_value(it, 2, icon)
self.update_feed_done(feedurl)
else:
@ -920,9 +913,7 @@ class FeedTree(Gtk.TreeView):
icon = self.render_icon(Gtk.STOCK_FILE, Gtk.IconSize.MENU, None)
else:
icon = self.render_icon(Gtk.STOCK_FILE, Gtk.IconSize.MENU, None)
entry = self.feeddb[feedurl]
entry['favicon'] = data
self.feeddb[feedurl] = entry
self.feeddb.set_favicon(feedurl, data)
self.model.set_value(it, 2, icon)
self.update_feed_done(feedurl+"_icon")
self.session.queue_message(msg, complete_cb, it)
@ -932,23 +923,23 @@ class FeedReaderWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
# try the following paths for pyferea.db in this order
# try the following paths for pyferea.sqlite in this order
xdg_data_home = os.environ.get('XDG_DATA_HOME') or os.path.join(os.path.expanduser('~'), '.local', 'share')
feeddb_paths = [
"./pyferea.db",
os.path.join(xdg_data_home, "pyferea", "pyferea.db"),
"./pyferea.sqlite",
os.path.join(xdg_data_home, "pyferea", "pyferea.sqlite"),
]
feeddb = None
for path in feeddb_paths:
if os.path.exists(path):
feeddb = shelve.open(path)
feeddb = sqlite_db.SQLStorage(path)
break
if not feeddb:
print "cannot find pyferea.db in any of the following locations:"
print "cannot find pyferea.sqlite in any of the following locations:"
for path in feeddb_paths:
print path
print "creating new db at %s"%feeddb_paths[0]
feeddb = shelve.open(feeddb_paths[0])
feeddb = sqlite_db.SQLStorage(feeddb_paths[0])
# try the following paths for feeds.yaml in this order
xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or os.path.join(os.path.expanduser('~'), '.config')
@ -1030,7 +1021,7 @@ class FeedReaderWindow(Gtk.Window):
entries = EntryTree(config, feeddb)
def item_selected_cb(entry, feedurl, item):
item = feeddb[feedurl]['items'][item]
item = feeddb.get_entry(feedurl, item)
if config[feedurl]['loadlink']:
content_pane.load_uri(item['link'])
else:

107
sqlite_db.py Normal file
View file

@ -0,0 +1,107 @@
import sqlite3
def dbcreate(conn):
conn.execute("""
CREATE TABLE IF NOT EXISTS feeds (
feed TEXT NOT NULL,
title TEXT,
favicon BLOB,
etag TEXT,
lastmodified TEXT,
unread INTEGER
)
""")
conn.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS feedidx ON feeds (feed)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS entries (
feed TEXT NOT NULL,
entry TEXT NOT NULL,
title TEXT,
content TEXT,
link TEXT,
date INTEGER,
unread INTEGER,
categories TEXT
)""")
conn.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS entridx ON entries (feed,entry)
""")
conn.commit()
def convert(filename):
conn = sqlite3.connect(filename)
conn.text_factory = str
dbcreate(conn)
import shelve
feeddb = shelve.open("pyferea.db")
for feed, fvalues in feeddb.items():
conn.execute("""REPLACE INTO feeds (feed, title, favicon, etag, lastmodified, unread) VALUES (?,?,?,?,?,?)""",
(feed, fvalues.get('title'), fvalues.get('favicon'), fvalues.get('etag'), fvalues.get('lastmodified'), fvalues.get('unread')))
for entry, evalues in fvalues['items'].items():
conn.execute("""REPLACE INTO entries (feed, entry, title, content, link, date, unread, categories) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
(feed, entry, evalues.get('title'), evalues.get('content'), evalues.get('link'), evalues.get('date'), evalues.get('unread'), ', '.join(evalues.get('categories') or [])))
conn.commit()
conn.close()
feeddb.close()
class SQLStorage():
def __init__(self, filename=':memory:'):
self.conn = sqlite3.connect(filename)
self.conn.text_factory = str
dbcreate(self.conn)
def get_feed(self, feed):
return dict(zip(('title', 'favicon', 'etag', 'lastmodified', 'unread'),
self.conn.execute("""SELECT title, favicon, etag, lastmodified, unread FROM feeds WHERE feed=?""", (feed,)).fetchone()))
def get_entry(self, feed, entry):
return dict(zip(('title', 'content', 'link', 'date', 'unread', 'categories'),
self.conn.execute("""SELECT title, content, link, date, unread, categories FROM entries WHERE feed=? AND entry=?""", (feed, entry)).fetchone()))
def get_entries_all(self, feed):
return [dict(zip(('entry', 'title', 'date', 'unread'), content))
for content in self.conn.execute("""
SELECT entry, title, date, unread FROM entries WHERE feed=? ORDER BY date DESC
""", (feed,)).fetchall()]
def add_entry(self, feed, entry, values):
self.conn.execute("""REPLACE INTO entries (feed, entry, title, content, link, date, unread, categories) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
(feed, entry, values['title'], values['content'], values['link'], values['date'], values['unread'], values['categories']))
self.conn.commit()
def update_feed(self, feed, values):
self.conn.execute("""UPDATE feeds SET title=?, favicon=?, etag=?, lastmodified=?, unread=? WHERE feed=?""",
(values['title'], values['favicon'], values['etag'], values['lastmodified'], values['unread'], feed))
self.conn.commit()
def set_favicon(self, feed, favicon):
self.conn.execute("""UPDATE feeds SET favicon=? WHERE feed=?""", (favicon, feed))
self.conn.commit()
def mark_read(self, feed, entry):
self.conn.execute("""UPDATE entries SET unread=0 WHERE feed=? AND entry=?""", (feed, entry))
self.conn.execute("""UPDATE feeds SET unread=unread-1 WHERE feed=?""", (feed,))
self.conn.commit()
def mark_read_feed(self, feed):
self.conn.execute("""UPDATE entries SET unread=0 WHERE unread=1""")
self.conn.execute("""UPDATE feeds set unread=0 WHERE feed=?""", (feed,))
self.conn.commit()
def feed_exists(self, feed):
return self.conn.execute("""SELECT feed FROM feeds WHERE feed=?""", (feed,)).fetchone() is not None
def entry_exists(self, feed, entry):
return self.conn.execute("""SELECT feed FROM entries WHERE feed=? AND entry=?""", (feed,entry)).fetchone() is not None
def close(self):
self.conn.close()
def sync(self):
pass
if __name__ == "__main__":
convert("pyferea.sqlite")