announcements: add support for dynamic feed registration

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2022-03-25 08:02:36 -04:00
parent 88b0282900
commit cd03b91f87
No known key found for this signature in database
GPG Key ID: 7027245FBBDDF59A
1 changed files with 82 additions and 2 deletions

View File

@ -6,7 +6,6 @@
from __future__ import annotations from __future__ import annotations
import datetime import datetime
from importlib.resources import path
import pathlib import pathlib
import asyncio import asyncio
import logging import logging
@ -35,6 +34,7 @@ etree.register_namespace("moonlight", MOONLIGHT_URL)
class Announcements: class Announcements:
def __init__(self, config: ConfigHelper) -> None: def __init__(self, config: ConfigHelper) -> None:
self.server = config.get_server() self.server = config.get_server()
self.config = config
self.entry_mgr = EntryManager(config) self.entry_mgr = EntryManager(config)
self.eventloop = self.server.get_event_loop() self.eventloop = self.server.get_event_loop()
self.update_timer = self.eventloop.register_timer( self.update_timer = self.eventloop.register_timer(
@ -45,11 +45,14 @@ class Announcements:
"moonraker": RssFeed(config, "moonraker", self.entry_mgr), "moonraker": RssFeed(config, "moonraker", self.entry_mgr),
"klipper": RssFeed(config, "klipper", self.entry_mgr) "klipper": RssFeed(config, "klipper", self.entry_mgr)
} }
self.stored_feeds: List[str] = []
sub_list: List[str] = config.getlist("subscriptions", []) sub_list: List[str] = config.getlist("subscriptions", [])
self.configured_feeds: List[str] = ["moonraker", "klipper"]
for sub in sub_list: for sub in sub_list:
sub = sub.lower() sub = sub.lower()
if sub in self.subscriptions: if sub in self.subscriptions:
continue continue
self.configured_feeds.append(sub)
self.subscriptions[sub] = RssFeed(config, sub, self.entry_mgr) self.subscriptions[sub] = RssFeed(config, sub, self.entry_mgr)
self.server.register_endpoint( self.server.register_endpoint(
@ -64,6 +67,10 @@ class Announcements:
"/server/announcements/update", ["POST"], "/server/announcements/update", ["POST"],
self._handle_update_request self._handle_update_request
) )
self.server.register_endpoint(
"/server/announcements/feed", ["POST", "DELETE"],
self._handle_feed_request
)
self.server.register_notification( self.server.register_notification(
"announcements:dismissed", "announcement_dismissed" "announcements:dismissed", "announcement_dismissed"
) )
@ -72,6 +79,16 @@ class Announcements:
) )
async def component_init(self) -> None: async def component_init(self) -> None:
db: MoonrakerDatabase = self.server.lookup_component("database")
stored_feeds: List[str] = await db.get_item(
"moonraker", "announcements.stored_feeds", []
)
self.stored_feeds = stored_feeds
for name in stored_feeds:
if name in self.subscriptions:
continue
feed = RssFeed(self.config, name, self.entry_mgr)
self.subscriptions[name] = feed
async with self.request_lock: async with self.request_lock:
await self.entry_mgr.initialize() await self.entry_mgr.initialize()
for sub in self.subscriptions.values(): for sub in self.subscriptions.values():
@ -109,7 +126,8 @@ class Announcements:
incl_dsm = web_request.get_boolean("include_dismissed", True) incl_dsm = web_request.get_boolean("include_dismissed", True)
entries = await self.entry_mgr.list_entries(incl_dsm) entries = await self.entry_mgr.list_entries(incl_dsm)
return { return {
"entries": entries "entries": entries,
"feeds": list(self.subscriptions.keys())
} }
async def _handle_update_request( async def _handle_update_request(
@ -140,6 +158,55 @@ class Announcements:
"modified": changed "modified": changed
} }
async def _handle_feed_request(
self, web_request: WebRequest
) -> Dict[str, Any]:
action = web_request.get_action()
name: str = web_request.get("name")
name = name.lower()
changed: bool = False
db: MoonrakerDatabase = self.server.lookup_component("database")
result = "skipped"
if action == "POST":
if name not in self.subscriptions:
feed = RssFeed(self.config, name, self.entry_mgr)
self.subscriptions[name] = feed
await feed.initialize()
changed = await feed.update_entries()
self.stored_feeds.append(name)
db.insert_item(
"moonraker", "announcements.stored_feeds", self.stored_feeds
)
result = "added"
elif action == "DELETE":
if name not in self.stored_feeds:
raise self.server.error(f"Feed '{name}' not stored")
if name in self.configured_feeds:
raise self.server.error(
f"Feed '{name}' exists in the configuration, cannot remove"
)
self.stored_feeds.remove(name)
db.insert_item(
"moonraker", "announcements.stored_feeds", self.stored_feeds
)
if name in self.subscriptions:
del self.subscriptions[name]
changed = await self.entry_mgr.prune_by_feed(name)
logging.info(f"Removed Announcement Feed: {name}")
result = "removed"
else:
raise self.server.error(f"Feed does not exist: {name}")
if changed:
entries = await self.entry_mgr.list_entries()
self.eventloop.delay_callback(
.05, self.server.send_event, "announcements:entries_updated",
{"entries": entries}
)
return {
"feed": name,
"action": result
}
def add_internal_announcement( def add_internal_announcement(
self, title: str, desc: str, url: str, priority: str, feed: str self, title: str, desc: str, url: str, priority: str, feed: str
) -> Dict[str, Any]: ) -> Dict[str, Any]:
@ -239,6 +306,19 @@ class EntryManager:
return True return True
return False return False
async def prune_by_feed(self, feed: str) -> bool:
entries = await self.list_entries()
del_keys: List[str] = []
for entry in entries:
if entry["feed"] == feed:
key = self.entry_id_map.pop(entry["entry_id"], None)
if key is not None:
del_keys.append(key)
if del_keys:
self.announce_db.delete_batch(del_keys)
return True
return False
class RssFeed: class RssFeed:
def __init__( def __init__(
self, config: ConfigHelper, name: str, entry_mgr: EntryManager self, config: ConfigHelper, name: str, entry_mgr: EntryManager