diff --git a/README.md b/README.md index e50a5c0..55a3f8d 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,12 @@ If the latest announcement has been cleared or there are no announcements yet, a There's a hook in the configuration that lets you add a custom message above all the annoucements. A good use for this message would be to include a link to a more general system status or message of the day (MOTD) page. +## Announcement Lifetime + +Announcements are retained in the queue for up to some configurable lifetime in days. +After that they are purged automatically. +By default announcements stay in the queue for a week. + ## Persisted Announcements By default the service does nothing to persist announcements. @@ -106,7 +112,3 @@ On update, the file is over-written to reflect the current state of the queue. This way if the service is restarted, those old announcements aren't lost. The persistence file is just JSON. **BE CERTAIN** access to this file is protected! - -## Things That Could Use Work - -We should put a cap on the size of the announcement queue, or expire them after some time period. diff --git a/announcement_config.py b/announcement_config.py index c8e8389..06f1dc5 100644 --- a/announcement_config.py +++ b/announcement_config.py @@ -49,6 +49,12 @@ # AnnouncementQueue(LoggingConfigurable) configuration #------------------------------------------------------------------------------ +## Number of days to retain announcements. +# +# Announcements that have been in the queue for this many days are purged from +# the queue. +#c.AnnouncementQueue.lifetime_days = 7.0 + ## File path where announcements persist as JSON. # # For a persistent announcement queue, this parameter must be set to a non-empty diff --git a/jupyterhub_announcement/announcement.py b/jupyterhub_announcement/announcement.py index 3e2d140..bc2e9a9 100644 --- a/jupyterhub_announcement/announcement.py +++ b/jupyterhub_announcement/announcement.py @@ -12,7 +12,7 @@ from tornado import escape, gen, ioloop, web from traitlets.config import Application, LoggingConfigurable -from traitlets import Bool, Dict, Integer, List, Unicode, default +from traitlets import Bool, Dict, Float, Integer, List, Unicode, default class _JSONEncoder(json.JSONEncoder): @@ -56,6 +56,13 @@ class AnnouncementQueue(LoggingConfigurable): announcements will not be persisted on updates to the queue.""" ).tag(config=True) + lifetime_days = Float(7.0, + help="""Number of days to retain announcements. + + Announcements that have been in the queue for this many days are + purged from the queue.""" + ).tag(config=True) + def __init__(self, **kwargs): super().__init__(**kwargs) @@ -96,6 +103,16 @@ def _persist(self): with open(self.persist_path, "w") as stream: json.dump(self.announcements, stream, cls=_JSONEncoder, indent=2) + def purge(self): + max_age = datetime.timedelta(days=self.lifetime_days) + now = datetime.datetime.now() + old_count = len(self.announcements) + self.announcements = [a for a in self.announcements + if now - a["timestamp"] < max_age] + if self.persist_path and len(self.announcements) < old_count: + self.log.info(f"persisting queue to {self.persist_path}") + self._handle_persist() + class AnnouncementHandler(HubAuthenticated, web.RequestHandler): @@ -261,6 +278,10 @@ def init_queue(self): def start(self): self.app.listen(self.port) + def purge_callback(): + self.queue.purge() + c = ioloop.PeriodicCallback(purge_callback, 300000) + c.start() ioloop.IOLoop.current().start() diff --git a/setup.py b/setup.py index a383aca..328b67d 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='jupyterhub-announcement', - version='0.2.0', + version='0.3.0', description='JupyterHub Announcement Service', author='R. C. Thomas', author_email='rcthomas@lbl.gov',