/* 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 { /** * @todo Tests schreiben */ function shall_remind( event : _munin.type_event, reminder : type_reminder, { "pit": pit = lib_plankton.pit.now(), "interval": interval = 1, } : { pit ?: lib_plankton.pit.type_pit; interval ?: int; } = { } ) : boolean { const window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( pit, 0 ); const window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( pit, interval ); const event_begin : lib_plankton.pit.type_pit = lib_plankton.pit.from_datetime( event.begin ); const reminder_time : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour( event_begin, (-reminder) ); return lib_plankton.pit.is_between( reminder_time, window_from, window_to ); } /** */ 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_new, { "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} ); // 0 ≤ ((p - a(p)) - o) < i const x : float = ( ( ( lib_plankton.pit.to_unix_timestamp(pit) - lib_plankton.pit.to_unix_timestamp(anchor) ) / (60 * 60) ) - reminder.offset ); return ((0 <= x) && (x < interval)); } /** */ export function reminder_event_in_window( reminder : type_reminder_new, event : _munin.type_event, { "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.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 ); return lib_plankton.pit.is_between( event_begin, window_from, window_to ); } /** */ function shall_remind_new( reminder : type_reminder_new, event : _munin.type_event, { "pit": pit = lib_plankton.pit.now(), "interval": interval = 1, } : { pit ?: lib_plankton.pit.type_pit; interval ?: int; } = { } ) : boolean { return ( reminder_due( reminder, {"pit": pit, "interval": interval} ) && reminder_event_in_window( reminder, event, {"pit": pit, "interval": interval} ) ); } /** */ 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_hours of target.reminders) { for (const event of events) { const remind : boolean = shall_remind( event, reminder_hours, { "pit": now, "interval": conf.settings.interval, } ); if (! remind) { // do nothing } else { lib_plankton.log._info( "munin.remind.do", { "details": { "event": event, "target": target.show(), } } ); if (dry_run) { // do nothing } else { 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); } } } }