From 6b02fb66bce475f104584473c9e27b29b9a43ebc Mon Sep 17 00:00:00 2001 From: John Doty Date: Thu, 11 Jul 2024 10:32:48 +0900 Subject: [PATCH] Deal with locked databases, tweaks --- cry/cli.py | 24 +++- cry/database.py | 299 +++++++++++++++++++++++++----------------------- 2 files changed, 172 insertions(+), 151 deletions(-) diff --git a/cry/cli.py b/cry/cli.py index 5c2f8f5..d08e001 100644 --- a/cry/cli.py +++ b/cry/cli.py @@ -199,22 +199,34 @@ def serve(): def do_GET(self): db = database.Database.local() feeds = db.load_all(feed_limit=10) + del db + feeds.sort(key=feed.sort_key, reverse=True) buffer = io.StringIO() - buffer.write("") - buffer.write('Subscribed Feeds') - buffer.write("") - buffer.write("

Feeds

") + buffer.write( + """ + + + + Subscribed Feeds + +

Feeds

+ """ + ) for f in feeds: feed_title = html.escape(f.title) - buffer.write(f'

{feed_title}

') + if len(f.entries) > 0: + ago = f" ({f.entries[0].time_ago()})" + else: + ago = "" + buffer.write(f'

{feed_title}{ago}

') buffer.write(f"
") if len(f.entries) > 0: for entry in f.entries: title = html.escape(entry.title) buffer.write( - f'{title} ({entry.time_ago()}) ' + f'{title} ({entry.time_ago()}) ' ) else: buffer.write("No entries...") diff --git a/cry/database.py b/cry/database.py index 4bd37f6..c63b8f4 100644 --- a/cry/database.py +++ b/cry/database.py @@ -102,20 +102,24 @@ class Database: return db def get_property(self, prop: str, default=None) -> typing.Any: - cursor = self.db.execute("SELECT value FROM properties WHERE name=?", (prop,)) - result = cursor.fetchone() - if result is None: - return default - return result[0] + with self.db: + cursor = self.db.execute( + "SELECT value FROM properties WHERE name=?", (prop,) + ) + result = cursor.fetchone() + if result is None: + return default + return result[0] def set_property(self, prop: str, value): - self.db.execute( - """ - INSERT INTO properties (name, value) VALUES (?, ?) - ON CONFLICT DO UPDATE SET value=excluded.value - """, - (prop, value), - ) + with self.db: + self.db.execute( + """ + INSERT INTO properties (name, value) VALUES (?, ?) + ON CONFLICT DO UPDATE SET value=excluded.value + """, + (prop, value), + ) def ensure_database_schema(self): with self.db: @@ -138,67 +142,135 @@ class Database: self.set_property("origin", self.origin) def load_all_meta(self) -> list[feed.FeedMeta]: - cursor = self.db.execute( - """ - SELECT - url, - last_fetched_ts, - retry_after_ts, - status, - etag, - modified - FROM feeds - """ - ) - rows = cursor.fetchall() - return [ - feed.FeedMeta( - url=url, - last_fetched_ts=int(last_fetched_ts), - retry_after_ts=int(retry_after_ts), - status=int(status), - etag=etag, - modified=modified, - origin=self.origin, + with self.db: + cursor = self.db.execute( + """ + SELECT + url, + last_fetched_ts, + retry_after_ts, + status, + etag, + modified + FROM feeds + """ ) - for url, last_fetched_ts, retry_after_ts, status, etag, modified in rows - ] + rows = cursor.fetchall() + return [ + feed.FeedMeta( + url=url, + last_fetched_ts=int(last_fetched_ts), + retry_after_ts=int(retry_after_ts), + status=int(status), + etag=etag, + modified=modified, + origin=self.origin, + ) + for url, last_fetched_ts, retry_after_ts, status, etag, modified in rows + ] def load_all(self, feed_limit: int = 20, pattern: str = "") -> list[feed.Feed]: - pattern = pattern.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") - sql_pattern = f"%{pattern}%" - cursor = self.db.execute( - """ - SELECT - url, - last_fetched_ts, - retry_after_ts, - status, - etag, - modified, - title, - link - FROM feeds - WHERE (title LIKE :sql_pattern ESCAPE '\\' - OR link LIKE :sql_pattern ESCAPE '\\') - AND status != 2 -- UNSUBSCRIBED - """, - {"sql_pattern": sql_pattern}, - ) - rows = cursor.fetchall() + with self.db: + pattern = ( + pattern.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") + ) + sql_pattern = f"%{pattern}%" + cursor = self.db.execute( + """ + SELECT + url, + last_fetched_ts, + retry_after_ts, + status, + etag, + modified, + title, + link + FROM feeds + WHERE (title LIKE :sql_pattern ESCAPE '\\' + OR link LIKE :sql_pattern ESCAPE '\\') + AND status != 2 -- UNSUBSCRIBED + """, + {"sql_pattern": sql_pattern}, + ) + rows = cursor.fetchall() - almost_feeds = [] - for row in rows: - ( - url, - last_fetched_ts, - retry_after_ts, - status, - etag, - modified, - title, - link, - ) = row + almost_feeds = [] + for row in rows: + ( + url, + last_fetched_ts, + retry_after_ts, + status, + etag, + modified, + title, + link, + ) = row + meta = feed.FeedMeta( + url=url, + last_fetched_ts=last_fetched_ts, + retry_after_ts=retry_after_ts, + status=status, + etag=etag, + modified=modified, + origin=self.origin, + ) + almost_feeds.append((meta, title, link)) + + feeds = [] + for meta, title, link in almost_feeds: + if feed_limit > 0: + cursor = self.db.execute( + """ + SELECT + id, + inserted_at, + title, + link + FROM entries + WHERE feed_url=? + ORDER BY inserted_at DESC + LIMIT ? + """, + [meta.url, feed_limit], + ) + rows = cursor.fetchall() + else: + rows = [] + + entries = [ + feed.Entry(id=id, inserted_at=inserted_at, title=title, link=link) + for id, inserted_at, title, link in rows + ] + f = feed.Feed(meta=meta, title=title, link=link, entries=entries) + feeds.append(f) + + return feeds + + def load_feed(self, url: str) -> feed.Feed | None: + with self.db: + cursor = self.db.execute( + """ + SELECT + last_fetched_ts, + retry_after_ts, + status, + etag, + modified, + title, + link + FROM feeds + WHERE url=? + """, + [url], + ) + + row = cursor.fetchone() + if row is None: + return None + + last_fetched_ts, retry_after_ts, status, etag, modified, title, link = row meta = feed.FeedMeta( url=url, last_fetched_ts=last_fetched_ts, @@ -208,88 +280,25 @@ class Database: modified=modified, origin=self.origin, ) - almost_feeds.append((meta, title, link)) - feeds = [] - for meta, title, link in almost_feeds: - if feed_limit > 0: - cursor = self.db.execute( - """ - SELECT - id, - inserted_at, - title, - link - FROM entries - WHERE feed_url=? - ORDER BY inserted_at DESC - LIMIT ? - """, - [meta.url, feed_limit], - ) - rows = cursor.fetchall() - else: - rows = [] + cursor = self.db.execute( + """ + SELECT + id, + inserted_at, + title, + link + FROM entries + WHERE feed_url=? + """, + [url], + ) + rows = cursor.fetchall() entries = [ feed.Entry(id=id, inserted_at=inserted_at, title=title, link=link) for id, inserted_at, title, link in rows ] - f = feed.Feed(meta=meta, title=title, link=link, entries=entries) - feeds.append(f) - - return feeds - - def load_feed(self, url: str) -> feed.Feed | None: - cursor = self.db.execute( - """ - SELECT - last_fetched_ts, - retry_after_ts, - status, - etag, - modified, - title, - link - FROM feeds - WHERE url=? - """, - [url], - ) - - row = cursor.fetchone() - if row is None: - return None - - last_fetched_ts, retry_after_ts, status, etag, modified, title, link = row - meta = feed.FeedMeta( - url=url, - last_fetched_ts=last_fetched_ts, - retry_after_ts=retry_after_ts, - status=status, - etag=etag, - modified=modified, - origin=self.origin, - ) - - cursor = self.db.execute( - """ - SELECT - id, - inserted_at, - title, - link - FROM entries - WHERE feed_url=? - """, - [url], - ) - - rows = cursor.fetchall() - entries = [ - feed.Entry(id=id, inserted_at=inserted_at, title=title, link=link) - for id, inserted_at, title, link in rows - ] return feed.Feed(meta=meta, title=title, link=link, entries=entries)