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'
')
- buffer.write(f"
")
if len(f.entries) > 0:
+ buffer.write(f"
")
for entry in f.entries:
title = html.escape(entry.title)
buffer.write(
- f'• {title} ({entry.time_ago()}) '
+ f'- {title} ({entry.time_ago()})
'
)
+ 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