Unsubscribe
This commit is contained in:
parent
c06f3ef114
commit
02232c9c3e
3 changed files with 112 additions and 35 deletions
42
cry/cli.py
42
cry/cli.py
|
|
@ -13,7 +13,12 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
@click.version_option()
|
@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):
|
def cli(verbose):
|
||||||
"Command line feed reader"
|
"Command line feed reader"
|
||||||
if verbose > 1:
|
if verbose > 1:
|
||||||
|
|
@ -153,3 +158,38 @@ def show(pattern, count):
|
||||||
else:
|
else:
|
||||||
click.echo(f" <No Entries>")
|
click.echo(f" <No Entries>")
|
||||||
click.echo()
|
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
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import random
|
||||||
import socket
|
import socket
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import string
|
import string
|
||||||
|
import time
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
import platformdirs
|
import platformdirs
|
||||||
|
|
@ -33,7 +34,17 @@ SCHEMA_STATEMENTS = [
|
||||||
ON UPDATE CASCADE
|
ON UPDATE CASCADE
|
||||||
ON DELETE 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,
|
title,
|
||||||
link
|
link
|
||||||
FROM feeds
|
FROM feeds
|
||||||
WHERE title LIKE :sql_pattern ESCAPE '\\'
|
WHERE (title LIKE :sql_pattern ESCAPE '\\'
|
||||||
OR link LIKE :sql_pattern ESCAPE '\\'
|
OR link LIKE :sql_pattern ESCAPE '\\')
|
||||||
|
AND status != 2 -- UNSUBSCRIBED
|
||||||
""",
|
""",
|
||||||
{"sql_pattern": sql_pattern},
|
{"sql_pattern": sql_pattern},
|
||||||
)
|
)
|
||||||
|
|
@ -200,21 +212,25 @@ class Database:
|
||||||
|
|
||||||
feeds = []
|
feeds = []
|
||||||
for meta, title, link in almost_feeds:
|
for meta, title, link in almost_feeds:
|
||||||
cursor = self.db.execute(
|
if feed_limit > 0:
|
||||||
"""
|
cursor = self.db.execute(
|
||||||
SELECT
|
"""
|
||||||
id,
|
SELECT
|
||||||
inserted_at,
|
id,
|
||||||
title,
|
inserted_at,
|
||||||
link
|
title,
|
||||||
FROM entries
|
link
|
||||||
WHERE feed_url=?
|
FROM entries
|
||||||
ORDER BY inserted_at DESC
|
WHERE feed_url=?
|
||||||
LIMIT ?
|
ORDER BY inserted_at DESC
|
||||||
""",
|
LIMIT ?
|
||||||
[meta.url, feed_limit],
|
""",
|
||||||
)
|
[meta.url, feed_limit],
|
||||||
rows = cursor.fetchall()
|
)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
else:
|
||||||
|
rows = []
|
||||||
|
|
||||||
entries = [
|
entries = [
|
||||||
feed.Entry(id=id, inserted_at=inserted_at, title=title, link=link)
|
feed.Entry(id=id, inserted_at=inserted_at, title=title, link=link)
|
||||||
for id, inserted_at, title, link in rows
|
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)
|
return feed.Feed(meta=meta, title=title, link=link, entries=entries)
|
||||||
|
|
||||||
def update_meta(self, f: feed.FeedMeta):
|
def update_meta(self, f: feed.FeedMeta):
|
||||||
self.db.execute(
|
with self.db:
|
||||||
"""
|
self.db.execute(
|
||||||
UPDATE feeds SET
|
"""
|
||||||
last_fetched_ts=?,
|
UPDATE feeds SET
|
||||||
retry_after_ts=?,
|
last_fetched_ts=?,
|
||||||
status=?,
|
retry_after_ts=?,
|
||||||
etag=?,
|
status=?,
|
||||||
modified=?
|
etag=?,
|
||||||
WHERE url=?
|
modified=?
|
||||||
""",
|
WHERE url=?
|
||||||
[f.last_fetched_ts, f.retry_after_ts, f.status, f.etag, f.modified, f.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:
|
def store_feed(self, f: feed.Feed) -> int:
|
||||||
"""Store the given feed in the database.
|
"""Store the given feed in the database.
|
||||||
|
|
@ -375,3 +399,16 @@ class Database:
|
||||||
)
|
)
|
||||||
end_count = cursor.fetchone()[0]
|
end_count = cursor.fetchone()[0]
|
||||||
return end_count - start_count
|
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
|
||||||
|
|
|
||||||
10
cry/feed.py
10
cry/feed.py
|
|
@ -18,9 +18,9 @@ import requests.structures
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
FEED_STATUS_DEAD = 0
|
FEED_STATUS_ALIVE = 0
|
||||||
FEED_STATUS_ALIVE = 1
|
FEED_STATUS_DEAD = 1
|
||||||
FEED_STATUS_MISSING = 2
|
FEED_STATUS_UNSUBSCRIBED = 2
|
||||||
|
|
||||||
# TODO: Consider configuration here.
|
# TODO: Consider configuration here.
|
||||||
http = requests.Session()
|
http = requests.Session()
|
||||||
|
|
@ -143,8 +143,8 @@ async def fetch_feed(
|
||||||
|
|
||||||
Regardless, the new FeedMeta has the latest state of the feed.
|
Regardless, the new FeedMeta has the latest state of the feed.
|
||||||
"""
|
"""
|
||||||
if feed.status == FEED_STATUS_DEAD:
|
if feed.status != FEED_STATUS_ALIVE:
|
||||||
LOG.info(f"{feed.url} is dead")
|
LOG.info(f"{feed.url} is dead or unsubscribed")
|
||||||
return (None, feed)
|
return (None, feed)
|
||||||
|
|
||||||
if time.time() < feed.retry_after_ts:
|
if time.time() < feed.retry_after_ts:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue