/* 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_check( reminder : type_reminder, events_all : Array<_munin.type_event>, { "pit": pit = lib_plankton.pit.now(), "interval": interval = 1, } : { pit ?: lib_plankton.pit.type_pit; interval ?: int; } = { } ) : (null | Array<_munin.type_event>) { const anchor : lib_plankton.pit.type_pit = frequency_anchor( reminder.frequency, {"pit": pit} ); const dueness_window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( anchor, (reminder.offset + 0) ); const dueness_window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( anchor, (reminder.offset + interval) ); const due : boolean = lib_plankton.pit.is_between( pit, dueness_window_from, dueness_window_to ); if (! due) { return null; } else { const events_window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( anchor, reminder.from ); const events_window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( anchor, reminder.to ); const events : Array<_munin.type_event> = ( events_all .filter( (event) => lib_plankton.pit.is_between( lib_plankton.pit.from_datetime(event.begin), events_window_from, events_window_to ) ) ); return events; } } /** */ 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_all : 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 events : Array<_munin.type_event> = reminder_check( reminder, events_all, { "pit": now, "interval": conf.settings.interval, } ); if (events === null) { lib_plankton.log._info( "munin.run_iteration.reminder_not_due", { "details": { "reminder": reminder, } } ); } else { if (events.length <= 0) { lib_plankton.log._info( "munin.run_iteration.no_matching_events", { "details": { "reminder": reminder, } } ); } else { lib_plankton.log._info( "munin.run_iteration.remind", { "details": { "reminder": reminder, "events": events, "target": target.show(), } } ); if (dry_run) { // do nothing } else { try { await target.send(events); } 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); } } } }