Unsubscribe

This commit is contained in:
John Doty 2024-07-11 07:35:32 +09:00
parent c06f3ef114
commit 02232c9c3e
3 changed files with 112 additions and 35 deletions

View file

@ -13,7 +13,12 @@ LOG = logging.getLogger(__name__)
@click.group()
@click.version_option()
@click.option("-v", "--verbose", count=True)
@click.option(
"-v",
"--verbose",
count=True,
help="Increase the verbosity of the output. This option can be specified multiple times.",
)
def cli(verbose):
"Command line feed reader"
if verbose > 1:
@ -153,3 +158,38 @@ def show(pattern, count):
else:
click.echo(f" <No Entries>")
click.echo()
@cli.command("list")
@click.argument("pattern", required=False, default="")
def list_feeds(pattern):
"""List subscribed feeds.
If a pattern is supplied, then filter the feeds to urls or titles that
match the pattern. Otherwise, just show everything.
"""
db = database.Database.local()
feeds = db.load_all(feed_limit=0, pattern=pattern)
max_title = max(len(f.title) for f in feeds)
max_url = max(len(f.meta.url) for f in feeds)
feeds.sort(key=lambda f: f.title)
for f in feeds:
click.echo(f"{f.title:{max_title}} {f.meta.url:{max_url}}")
@cli.command("unsubscribe")
@click.argument("url")
def unsubscribe(url):
"""Unsubscribe from the specified feed.
(If you need to find the URL for the feed to unsubscribe from, use the
`list` command.)
"""
db = database.Database.local()
count = db.set_feed_status(url, feed.FEED_STATUS_UNSUBSCRIBED)
if count == 0:
click.echo(f"Not subscribed to feed {url}")
return 1

View file

@ -3,6 +3,7 @@ import random
import socket
import sqlite3
import string
import time
import typing
import platformdirs
@ -33,7 +34,17 @@ SCHEMA_STATEMENTS = [
ON UPDATE CASCADE
ON DELETE CASCADE
);
""",
# I went and changed the status enum to make ALIVE == 0 when I added the
# "unsubscribed" status. I should probably make these strings huh.
"""
UPDATE feeds
SET status=CASE
WHEN status = 0 THEN 1
WHEN status = 1 THEN 0
ELSE status
END
""",
]
@ -168,8 +179,9 @@ class Database:
title,
link
FROM feeds
WHERE title LIKE :sql_pattern ESCAPE '\\'
OR link LIKE :sql_pattern ESCAPE '\\'
WHERE (title LIKE :sql_pattern ESCAPE '\\'
OR link LIKE :sql_pattern ESCAPE '\\')
AND status != 2 -- UNSUBSCRIBED
""",
{"sql_pattern": sql_pattern},
)
@ -200,21 +212,25 @@ class Database:
feeds = []
for meta, title, link in almost_feeds:
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()
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
@ -278,18 +294,26 @@ class Database:
return feed.Feed(meta=meta, title=title, link=link, entries=entries)
def update_meta(self, f: feed.FeedMeta):
self.db.execute(
"""
UPDATE feeds SET
last_fetched_ts=?,
retry_after_ts=?,
status=?,
etag=?,
modified=?
WHERE url=?
""",
[f.last_fetched_ts, f.retry_after_ts, f.status, f.etag, f.modified, f.url],
)
with self.db:
self.db.execute(
"""
UPDATE feeds SET
last_fetched_ts=?,
retry_after_ts=?,
status=?,
etag=?,
modified=?
WHERE url=?
""",
[
f.last_fetched_ts,
f.retry_after_ts,
f.status,
f.etag,
f.modified,
f.url,
],
)
def store_feed(self, f: feed.Feed) -> int:
"""Store the given feed in the database.
@ -375,3 +399,16 @@ class Database:
)
end_count = cursor.fetchone()[0]
return end_count - start_count
def set_feed_status(self, url: str, 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

View file

@ -18,9 +18,9 @@ import requests.structures
LOG = logging.getLogger(__name__)
FEED_STATUS_DEAD = 0
FEED_STATUS_ALIVE = 1
FEED_STATUS_MISSING = 2
FEED_STATUS_ALIVE = 0
FEED_STATUS_DEAD = 1
FEED_STATUS_UNSUBSCRIBED = 2
# TODO: Consider configuration here.
http = requests.Session()
@ -143,8 +143,8 @@ async def fetch_feed(
Regardless, the new FeedMeta has the latest state of the feed.
"""
if feed.status == FEED_STATUS_DEAD:
LOG.info(f"{feed.url} is dead")
if feed.status != FEED_STATUS_ALIVE:
LOG.info(f"{feed.url} is dead or unsubscribed")
return (None, feed)
if time.time() < feed.retry_after_ts: