munin/source/logic.ts

266 lines
5.6 KiB
TypeScript
Raw Normal View History

/*
This file is part of »munin«.
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
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<void>
{
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
{
2025-07-01 19:04:41 +02:00
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<void>
{
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);
}
}
}
}