/* This file is part of »munin«. Copyright 2025 'Fenris Wolf' »munin« is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. »munin« is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with »munin«. If not, see . */ namespace _munin.logic { /** */ export function frequency_decode( frequency_encoded : string ) : _munin.enum_frequency { switch (frequency_encoded) { case "hourly": return _munin.enum_frequency.hourly; case "daily": return _munin.enum_frequency.daily; case "weekly": return _munin.enum_frequency.weekly; case "monthly": return _munin.enum_frequency.monthly; default: {throw new Error("unhandled");} } } /** */ function frequency_anchor( frequency : _munin.enum_frequency, { "pit": pit = lib_plankton.pit.now(), } : { pit ?: lib_plankton.pit.type_pit, } = { } ) : lib_plankton.pit.type_pit { switch (frequency) { case _munin.enum_frequency.hourly: return lib_plankton.pit.trunc_hour(pit); case _munin.enum_frequency.daily: return lib_plankton.pit.trunc_day(pit); case _munin.enum_frequency.weekly: return lib_plankton.pit.trunc_week(pit); case _munin.enum_frequency.monthly: return lib_plankton.pit.trunc_month(pit); default: { throw (new Error("unhandled frequency: " + frequency)); break; } } } /** */ export function reminder_due( reminder : type_reminder, { "pit": pit = lib_plankton.pit.now(), "interval": interval = 1, } : { pit ?: lib_plankton.pit.type_pit; interval ?: int; } = { } ) : boolean { const anchor : lib_plankton.pit.type_pit = frequency_anchor( reminder.frequency, {"pit": pit} ); const window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( anchor, (reminder.offset + 0) ); const window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( anchor, (reminder.offset + interval) ); const result : boolean = lib_plankton.pit.is_between( pit, window_from, window_to ); lib_plankton.log._info( "munin.logic.reminder_due", { "details": { "reminder": reminder, "datetime": lib_plankton.pit.to_datetime(pit), "interval": interval, "anchor": lib_plankton.pit.to_datetime(anchor), "window_from": lib_plankton.pit.to_datetime(window_from), "window_to": lib_plankton.pit.to_datetime(window_to), "result": result, } } ); return result; } /** */ export function reminder_covers_event( reminder : type_reminder, event : _munin.type_event, { "pit": pit = lib_plankton.pit.now(), } : { pit ?: lib_plankton.pit.type_pit; } = { } ) : boolean { const anchor : lib_plankton.pit.type_pit = frequency_anchor( reminder.frequency, {"pit": pit} ); const window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( anchor, reminder.from ); const window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( anchor, reminder.to ); const event_begin : lib_plankton.pit.type_pit = lib_plankton.pit.from_datetime( event.begin ); const result : boolean = lib_plankton.pit.is_between( event_begin, window_from, window_to ); lib_plankton.log._info( "munin.logic.reminder_covers_event", { "details": { "reminder": reminder, "event": event, "datetime": lib_plankton.pit.to_datetime(pit), "anchor": lib_plankton.pit.to_datetime(anchor), "window_from": lib_plankton.pit.to_datetime(window_from), "window_to": lib_plankton.pit.to_datetime(window_to), "result": result, } } ); return result; } /** */ async function run_iteration( conf : _munin.conf.type_conf, sources : Array<_munin.type_source>, targets : Array<_munin.type_target>, { "dry_run": dry_run = false, } : { dry_run ?: boolean; } = { } ) : Promise { const now : lib_plankton.pit.type_pit = lib_plankton.pit.now(); const events : Array<_munin.type_event> = ( (await Promise.all(sources.map(source => source.fetch()))) .reduce((x, y) => x.concat(y), []) ); for (const target of targets) { for (const reminder of target.reminders) { const due : boolean = reminder_due( reminder, { "pit": now, "interval": conf.settings.interval, } ); if (! due) { // do nothing } else { const events_matching : Array<_munin.type_event> = events.filter( event => reminder_covers_event( reminder, event, { "pit": now, } ) ); if (events_matching.length <= 0) { // do nothing } else { lib_plankton.log._info( "munin.remind.do", { "details": { "events": events, "target": target.show(), } } ); if (dry_run) { // do nothing } else { /** * @todo bundle? */ for (const event of events_matching) { try { await target.send(conf.labels, event); } catch (error) { lib_plankton.log.error( "munin.remind.error", { "details": { "message": String(error), } } ); } } } } } } } } /** */ export async function run( conf : _munin.conf.type_conf, { "single_run": single_run = false, "dry_run": dry_run = false, } : { single_run ?: boolean; dry_run ?: boolean; } = { } ) : Promise { const sources : Array<_munin.type_source> = conf.sources.map( source_raw => _munin.sources.factory(source_raw) ); const targets : Array<_munin.type_target> = conf.targets.map( target_raw => _munin.targets.factory(target_raw) ); lib_plankton.log._info( "munin.run.start", { "details": { } } ); if (single_run) { await run_iteration(conf, sources, targets, {"dry_run": dry_run}); } else { while (true) { await run_iteration(conf, sources, targets, {"dry_run": dry_run}); await lib_plankton.call.sleep(conf.settings.interval * 60 * 60); } } } }