diff --git a/cry/cli.py b/cry/cli.py index 86f0d69..beaed84 100644 --- a/cry/cli.py +++ b/cry/cli.py @@ -239,11 +239,13 @@ def unsubscribe(url): `list` command.) """ db = database.Database.local() - count = db.set_feed_status(url, feed.FEED_STATUS_UNSUBSCRIBED) - if count == 0: + meta = db.load_meta(url) + if meta is None: click.echo(f"Not subscribed to feed {url}") return 1 + db.update_feed_status(meta, feed.FEED_STATUS_UNSUBSCRIBED) + @cli.command("serve") def serve(): @@ -262,6 +264,11 @@ def serve(): Subscribed Feeds +

Feeds

""" @@ -272,17 +279,19 @@ def serve(): ago = f" ({f.entries[0].time_ago()})" else: ago = "" + buffer.write(f"
") buffer.write(f'

{feed_title}{ago}

') - buffer.write(f"
") if len(f.entries) > 0: + buffer.write(f"") else: buffer.write("No entries...") - buffer.write(f"
") + buffer.write(f"
") # feed buffer.flush() text = buffer.getvalue() response = text.encode("utf-8") diff --git a/cry/database.py b/cry/database.py index 9f5b7bd..a73e971 100644 --- a/cry/database.py +++ b/cry/database.py @@ -304,55 +304,12 @@ class Database: Returns the number of new entries inserted. """ with self.db: - self.db.execute( - """ - INSERT INTO feeds ( - url, - last_fetched_ts, - retry_after_ts, - status, - etag, - modified, - title, - link - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT DO UPDATE - SET - last_fetched_ts=excluded.last_fetched_ts, - retry_after_ts=excluded.retry_after_ts, - status=excluded.status, - etag=excluded.etag, - modified=excluded.modified, - title=excluded.title, - link=excluded.link - """, - [ - f.meta.url, - f.meta.last_fetched_ts, - f.meta.retry_after_ts, - f.meta.status, - f.meta.etag, - f.meta.modified, - f.title, - f.link, - ], - ) + self._insert_feed(f.meta, f.title, f.link) + return self._insert_entries(f.meta.url, f.entries) - change_count = self._insert_entries(f.meta.url, f.entries) - return change_count - - def set_feed_status(self, url: str, status: int) -> int: + def update_feed_status(self, meta: feed.FeedMeta, status: int) -> int: with self.db: - cursor = self.db.execute( - """ - UPDATE feeds - SET status = ?, - last_fetched_ts = ? - WHERE url = ? - """, - [status, int(time.time()), url], - ) - return cursor.rowcount + return self._update_feed_status(meta, status) def redirect_feed(self, old_url: str, new_url: str): with self.db: @@ -380,6 +337,7 @@ class Database: # bother. # Mark the old feed unsubscribed. + # TODO: Rebuild with helpers self.db.execute( """ UPDATE feeds @@ -406,6 +364,59 @@ class Database: (prop, value), ) + def _insert_feed(self, meta: feed.FeedMeta, title: str, link: str): + """Insert into the feeds table, handling collisions with UPSERT.""" + self.db.execute( + """ + INSERT INTO feeds ( + url, + last_fetched_ts, + retry_after_ts, + status, + etag, + modified, + title, + link + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT DO UPDATE + SET + last_fetched_ts=MAX(last_fetched_ts, excluded.last_fetched_ts), + retry_after_ts=MAX(retry_after_ts, excluded.retry_after_ts), + -- For all other fields, take the value that was computed by the + -- most recent fetch. + status=CASE + WHEN last_fetched_ts > excluded.last_fetched_ts THEN status + ELSE excluded.status + END, + etag=CASE + WHEN last_fetched_ts > excluded.last_fetched_ts THEN etag + ELSE excluded.etag + END, + modified=CASE + WHEN last_fetched_ts > excluded.last_fetched_ts THEN modified + ELSE excluded.modified + END, + title=CASE + WHEN last_fetched_ts > excluded.last_fetched_ts THEN title + ELSE excluded.title + END, + link=CASE + WHEN last_fetched_ts > excluded.last_fetched_ts THEN link + ELSE excluded.link + END + """, + [ + meta.url, + meta.last_fetched_ts, + meta.retry_after_ts, + meta.status, + meta.etag, + meta.modified, + title, + link, + ], + ) + def _insert_entries(self, feed_url: str, entries: list[feed.Entry]) -> int: cursor = self.db.execute( "SELECT COUNT (*) FROM entries WHERE feed_url=?", [feed_url] @@ -451,3 +462,16 @@ class Database: ) end_count = cursor.fetchone()[0] return end_count - start_count + + def _update_feed_status(self, meta: feed.FeedMeta, status: int) -> int: + new_ts = max(int(time.time()), meta.last_fetched_ts + 1) + cursor = self.db.execute( + """ + UPDATE feeds + SET status = ?, + last_fetched_ts = ? + WHERE url = ? + """, + [status, new_ts, meta.url], + ) + return cursor.rowcount diff --git a/tests/test_database.py b/tests/test_database.py index daa3b7b..511b760 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -252,16 +252,18 @@ def test_database_store_update_meta(): assert db.load_all_meta()[0] == new_meta -def test_database_set_feed_status(): +def test_database_update_feed_status(): db = database.Database(":memory:", random_slug()) db.ensure_database_schema() db.store_feed(FEED) assert db.load_all_meta()[0].status != feed.FEED_STATUS_UNSUBSCRIBED - db.set_feed_status(FEED.meta.url, feed.FEED_STATUS_UNSUBSCRIBED) + db.update_feed_status( + FEED.meta, + feed.FEED_STATUS_UNSUBSCRIBED, + ) - # TODO: Ensure that the updated time is touched too. assert db.load_all_meta()[0].status == feed.FEED_STATUS_UNSUBSCRIBED