Mercurial > code > home > repos > gcalendarwatch
diff gcalendarwatch.py @ 85:f75b3a109b66
rewrite gcalendarwatch to read from calsync's mongo data only, not google services
author | drewp@bigasterisk.com |
---|---|
date | Sat, 07 Sep 2024 16:14:45 -0700 |
parents | 5f7ae444ecae |
children |
line wrap: on
line diff
--- a/gcalendarwatch.py Sat Sep 07 16:13:55 2024 -0700 +++ b/gcalendarwatch.py Sat Sep 07 16:14:45 2024 -0700 @@ -1,40 +1,33 @@ """ -sync google calendar into mongodb, return a few preset queries as RDF graphs +read calendar from mongodb, return a few preset queries as RDF graphs gcalendarwatch.conf looks like this: { - "minutes_between_polls" : 60 - "mongo" : {"host" : "h1", "port" : 27017, "database" : "dbname", "collection" : "pim"}, + "mongo" : {"host" : "h1", "port" : 27017, "database" : "dbname"}, } - -This should be updated to use -http://googledevelopers.blogspot.com/2013/07/google-calendar-api-push-notifications.html -and update faster with less polling """ import datetime -import functools import json import logging -import time -import traceback -from typing import Iterable, cast +import re +from typing import Sequence, cast import background_loop import pymongo.collection +import pymongo.database from dateutil.tz import tzlocal from patchablegraph import PatchableGraph from patchablegraph.handler import GraphEvents, StaticGraph from pymongo import MongoClient -from rdflib import Namespace, URIRef +from rdflib import Namespace from starlette.applications import Starlette from starlette.requests import Request -from starlette.responses import HTMLResponse, PlainTextResponse +from starlette.responses import HTMLResponse from starlette.routing import Route from starlette_exporter import PrometheusMiddleware, handle_metrics -from datetimemath import dayRange +from datetimemath import dayRange, limitDays from graphconvert import asGraph -from ingest import SyncToMongo from localtypes import Conf, Record logging.basicConfig(level=logging.INFO) @@ -63,87 +56,67 @@ }""" -class ReadMongoEvents(object): - """read events from mongodb""" - - def __init__(self, collection: pymongo.collection.Collection): - self.collection = collection - - def getEvents(self, t1: datetime.datetime, t2: datetime.datetime) -> Iterable[Record]: - if t1.tzinfo is None or t2.tzinfo is None: - raise TypeError("tz-naive datetimes") - for doc in self.collection.find({"startTime": {"$gte": t1, "$lt": t2}}).sort([("startTime", 1)]): - doc['uri'] = doc.pop('_id') - if 'feedId' in doc: - doc['feed'] = URIRef('old_event') - yield doc +def filterStarred(recs: Sequence[Record], maxCount=15) -> list[Record]: + recs = sorted(recs, key=lambda r: r['start']) + out = [] + for rec in recs: + if m := re.search(r'(.*)\*\s*$', rec['title']): + rec = rec.copy() + rec['title'] = m.group(1) + out.append(rec) + if len(out) >= maxCount: + break + return out -def update( - # this is incompletely type-checked: - sync: SyncToMongo, # curried by main - first_run: bool, # passed by background_loop - cal=None # sometimes passed by us -) -> int: - log.info(f"updating {cal or 'all'}") - try: - n = sync.update(cal=cal, days=120) - except Exception: - traceback.print_exc() - log.error("update failed") - n = 0 - return n +class SyncGraphsToMongo(object): + """reads mongodb (that calsync wrote); edits graphs""" + calendarsCollection: pymongo.collection.Collection + eventsCollection: pymongo.collection.Collection + def __init__(self, conf: Conf, db: pymongo.database.Database, agendaGraph: PatchableGraph, countdownGraph: PatchableGraph, + currentEventsGraph: PatchableGraph): + self.conf = conf + self.eventsCollection = db.get_collection('test_gcalendar') + self.calendarsCollection = db.get_collection('test_gcalendar_cals') + + self.agendaGraph = agendaGraph + self.countdownGraph = countdownGraph + self.currentEventsGraph = currentEventsGraph -def statusMsg(conf, loop: background_loop.Loop): - period = conf['minutes_between_polls'] * 60 - ago = time.time() - loop.lastSuccessRun - if not loop.everSucceeded: - msg = "no completed updates %d sec after startup" % ago - if ago > period * 1.1: - raise ValueError(msg) - else: - msg = "last update was %d sec ago" % ago - if ago > period * 1.1: - raise ValueError(msg) - return msg - + def _getEvents(self, t1: datetime.datetime, t2: datetime.datetime) -> list[Record]: + if t1.tzinfo is None or t2.tzinfo is None: + raise TypeError("tz-naive datetimes") + return list(self.eventsCollection.find({"startTime": {"$gte": t1, "$lt": t2}}).sort([("startTime", 1)])) -async def PollNow(loop: background_loop.Loop, req: Request) -> PlainTextResponse: - body = await req.body() - cals = json.loads(body).get('cals', None) if body else [None] - n = 0 - for cal in cals: - n += await loop.runNow(cal=cal) - msg = f"found {n} new records" - log.info(msg) - return PlainTextResponse(msg) + def updateGraphs(self, first_run): + s, e = dayRange(120) + currentRecords = self._getEvents(s, e) + cals = list(self.calendarsCollection.find()) + self.agendaGraph.setToGraph(asGraph(self.conf, cals, limitDays(currentRecords, days=2))) + self.countdownGraph.setToGraph(asGraph(self.conf, cals, filterStarred(currentRecords, maxCount=15), extraClasses=[EV['CountdownEvent']])) - -def updateCurrentEvents(conf: Conf, currentEventsGraph: PatchableGraph, collection: pymongo.collection.Collection, first_run: bool): - now = datetime.datetime.now(tzlocal()) - events = list(collection.find({"startTime": {"$lte": now}, "endTime": {"$gte": now}})) - currentEventsGraph.setToGraph(asGraph(conf, cals=[], events=events, extraClasses=[EV['CurrentEvent']])) + now = datetime.datetime.now(tzlocal()) + events = list(self.eventsCollection.find({"startTime": {"$lte": now}, "endTime": {"$gte": now}})) + self.currentEventsGraph.setToGraph(asGraph(self.conf, cals=[], events=events, extraClasses=[EV['CurrentEvent']])) def main(): - agendaGraph = PatchableGraph() # next few days - countdownGraph = PatchableGraph() # next n of starred events - currentEventsGraph = PatchableGraph() # events happening now + agendaGraph = PatchableGraph(label='agenda') # next few days + countdownGraph = PatchableGraph(label='countdown') # next n of starred events + currentEventsGraph = PatchableGraph(label='currentEvents') # events happening now + conf = cast(Conf, json.load(open("gcalendarwatch.conf"))) m = conf['mongo'] - mongoOut = MongoClient(m['host'], m['port'], tz_aware=True)[m['database']][m['collection']] - sync = SyncToMongo(conf, mongoOut, agendaGraph, countdownGraph) - read = ReadMongoEvents(mongoOut) + db = MongoClient(m['host'], m['port'], tz_aware=True)[m['database']] + sync = SyncGraphsToMongo(conf, db, agendaGraph, countdownGraph, currentEventsGraph) - s, e = dayRange(120) - sync.updateGraphs(read.getEvents(s, e)) - - loop = background_loop.loop_forever(functools.partial(update, sync=sync), conf['minutes_between_polls'] * 60) - background_loop.loop_forever(functools.partial(updateCurrentEvents, conf, currentEventsGraph, mongoOut), 5, metric_prefix="current_events") + # todo: this should watch for mongodb edits, or get a signal from calsync + background_loop.loop_forever(sync.updateGraphs, 5, metric_prefix="update_graphs") def getRoot(request: Request) -> HTMLResponse: - return HTMLResponse(content=open("index.html").read().replace("MSG", statusMsg(conf, loop))) + return HTMLResponse(content=open("index.html").read()) + moreNs = { "": "http://bigasterisk.com/event#", "cal": "http://bigasterisk.com/calendar/", @@ -159,7 +132,6 @@ Route('/graph/calendar/countdown/events', GraphEvents(countdownGraph)), Route('/graph/currentEvents', StaticGraph(currentEventsGraph, moreNs)), Route('/graph/currentEvents/events', GraphEvents(currentEventsGraph)), - Route('/pollNow', functools.partial(PollNow, loop), methods=['POST']) ]) app.add_middleware(PrometheusMiddleware, group_paths=True, filter_unhandled_paths=True, app_name='gcalendarwatch')