From 64652af779cd09c2ad8a28dd1392b879db4516d4 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Mon, 9 Dec 2024 21:40:07 +0100 Subject: [PATCH] [res] --- lib/plankton/plankton.d.ts | 98 +++------- lib/plankton/plankton.js | 309 ++++++++------------------------ source/base.ts | 12 +- source/conf.ts | 18 +- source/helpers.ts | 40 ++--- source/main.ts | 20 ++- source/repositories/calendar.ts | 9 +- source/repositories/resource.ts | 12 +- source/services/calendar.ts | 87 +++++---- 9 files changed, 219 insertions(+), 386 deletions(-) diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index b821fba..462ad6e 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -2495,95 +2495,47 @@ declare namespace lib_plankton.storage.sql_table_common { declare namespace lib_plankton.cache { /** */ - type type_cache = { - init: (() => Promise); - clear: (() => Promise); - query: ((key: string, retrieve: (() => Promise)) => Promise<{ - retrieved: boolean; - value: type_value; - }>); + type type_result = { + retrieved: boolean; + value: type_value; }; + /** + */ + type type_entry = { + value: type_value; + expiry: (null | float); + }; + /** + */ + type type_subject = lib_plankton.storage.type_chest, void, any, any>; } declare namespace lib_plankton.cache { /** */ - function get(cache: type_cache, key: string, retrieve: (() => Promise)): Promise; - /** - */ - function get_complex(cache: type_cache, group: string, input: type_input, retrieve: ((input: type_input) => Promise), options?: { - encode_input?: ((input: type_input) => string); - }): Promise; -} -declare namespace lib_plankton.cache.never { - /** - * @author fenris - */ - type type_subject = {}; - /** - * @author fenris - */ - function make(): type_subject; - /** - */ - function implementation(subject: type_subject): type_cache; -} -declare namespace lib_plankton.cache.always { - /** - * @author fenris - */ - type type_subject = { - value: lib_plankton.pod.type_pod; - }; - /** - * @author fenris - */ - function make(value: lib_plankton.pod.type_pod): type_subject; - /** - * @author fenris - */ - function clear(subject: type_subject): Promise; - /** - * @author fenris - */ - function query(subject: type_subject, key: string, retrieve: (() => Promise)): Promise<{ - retrieved: boolean; - value: type_value; - }>; - /** - */ - function implementation(subject: type_subject): type_cache; -} -declare namespace lib_plankton.cache.chest { - /** - * @author fenris - */ - type type_subject = { - chest: lib_plankton.storage.type_chest; - }; - /** - * @author fenris - */ - function make(options?: { - chest?: lib_plankton.storage.type_chest; + function make({ "chest": chest, }?: { + chest?: lib_plankton.storage.type_chest, void, any, any>; }): type_subject; /** - * @author fenris */ function init(subject: type_subject): Promise; /** - * @author fenris */ function clear(subject: type_subject): Promise; /** - * @author fenris */ - function query(subject: type_subject, key: string, retrieve: (() => Promise)): Promise<{ - retrieved: boolean; - value: type_value; - }>; + function invalidate(subject: type_subject, key: string): Promise; /** */ - function implementation(subject: type_subject): type_cache; + function query(subject: type_subject, key: string, lifetime: (null | float), retrieve: (() => Promise)): Promise>; + /** + * syntactic sugar: if the information, whether the value was retrieved, is irrelevant + */ + function get(subject: type_subject, key: string, lifetime: (null | float), retrieve: (() => Promise)): Promise; + /** + */ + function get_complex(cache: type_subject, group: string, input: type_input, lifetime: (null | float), retrieve: ((input: type_input) => Promise), { "encode_input": encode_input, }?: { + encode_input?: ((input: type_input) => string); + }): Promise; } declare namespace lib_plankton.shape { /** diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index 312e5f4..141c529 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -7675,246 +7675,93 @@ var lib_plankton; (function (cache_1) { /** */ - function get(cache, key, retrieve) { - return cache.query(key, retrieve).then(result => result.value); + function make({ "chest": chest = lib_plankton.storage.memory.implementation_chest({}), } = {}) { + return chest; + } + cache_1.make = make; + /** + */ + function init(subject) { + return subject.setup(undefined); + } + cache_1.init = init; + /** + */ + function clear(subject) { + return subject.clear(); + } + cache_1.clear = clear; + /** + */ + function invalidate(subject, key) { + return subject.delete(key); + } + cache_1.invalidate = invalidate; + /** + */ + async function query(subject, key, lifetime, retrieve) { + const now = lib_plankton.base.get_current_timestamp(); + return ((subject.read(key) + .then((entry) => Promise.resolve({ + "found": true, + "entry": entry, + })) + .catch(() => Promise.resolve({ + "found": false, + "entry": null, + }))) + .then((item) => { + if ((!item.found) + || + ((item.entry.expiry !== null) + && + (item.entry.expiry <= now))) { + lib_plankton.log.info("plankton.cache.unknown_or_expired", { + "key": key, + }); + return (retrieve() + .then((value) => (subject.write(key, { + "value": value, + "expiry": ((lifetime === null) + ? + null + : + (now + lifetime)) + }) + .then(() => Promise.resolve({ + "retrieved": true, + "value": value + }))))); + } + else { + lib_plankton.log.info("plankton.cache.known_and_valid", { + "key": key, + }); + return Promise.resolve({ + "retrieved": false, + "value": item.entry.value + }); + } + })); + } + cache_1.query = query; + /** + * syntactic sugar: if the information, whether the value was retrieved, is irrelevant + */ + function get(subject, key, lifetime, retrieve) { + return (query(subject, key, lifetime, retrieve) + .then(result => Promise.resolve(result.value))); } cache_1.get = get; /** */ - function get_complex(cache, group, input, retrieve, options = {}) { - options = Object.assign({ - "encode_input": input => JSON.stringify(input), - }, options); - return get(cache, (group + "." + options.encode_input(input)), () => retrieve(input)); + function get_complex(cache, group, input, lifetime, retrieve, { "encode_input": encode_input = (input => JSON.stringify(input)), } = {}) { + return get(cache, (group + "." + encode_input(input)), lifetime, () => retrieve(input)); } cache_1.get_complex = get_complex; })(cache = lib_plankton.cache || (lib_plankton.cache = {})); })(lib_plankton || (lib_plankton = {})); /* -This file is part of »bacterio-plankton:cache«. - -Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' - - -»bacterio-plankton:cache« 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. - -»bacterio-plankton:cache« 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 »bacterio-plankton:cache«. If not, see . - */ -var lib_plankton; -(function (lib_plankton) { - var cache; - (function (cache) { - var never; - (function (never) { - /** - * @author fenris - */ - function make() { - return {}; - } - never.make = make; - /** - * @author fenris - */ - function clear(subject) { - return Promise.resolve(undefined); - } - /** - * @author fenris - */ - async function query(subject, key, retrieve) { - return { - "retrieved": true, - "value": (await retrieve()), - }; - } - /** - */ - function implementation(subject) { - return { - "init": () => Promise.resolve(undefined), - "clear": () => clear(subject), - "query": (key, retrieve) => query(subject, key, retrieve), - }; - } - never.implementation = implementation; - })(never = cache.never || (cache.never = {})); - })(cache = lib_plankton.cache || (lib_plankton.cache = {})); -})(lib_plankton || (lib_plankton = {})); -/* -This file is part of »bacterio-plankton:cache«. - -Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' - - -»bacterio-plankton:cache« 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. - -»bacterio-plankton:cache« 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 »bacterio-plankton:cache«. If not, see . - */ -var lib_plankton; -(function (lib_plankton) { - var cache; - (function (cache) { - var always; - (function (always) { - /** - * @author fenris - */ - function make(value) { - return { - "value": value, - }; - } - always.make = make; - /** - * @author fenris - */ - function clear(subject) { - return Promise.resolve(undefined); - } - always.clear = clear; - /** - * @author fenris - */ - function query(subject, key, retrieve) { - if (lib_plankton.pod.is_filled(subject.value)) { - return Promise.resolve({ - "retrieved": false, - "value": lib_plankton.pod.cull(subject.value), - }); - } - else { - return Promise.reject(); - } - } - always.query = query; - /** - */ - function implementation(subject) { - return { - "init": () => Promise.resolve(undefined), - "clear": () => clear(subject), - "query": (key, retrieve) => query(subject, key, retrieve), - }; - } - always.implementation = implementation; - })(always = cache.always || (cache.always = {})); - })(cache = lib_plankton.cache || (lib_plankton.cache = {})); -})(lib_plankton || (lib_plankton = {})); -/* -This file is part of »bacterio-plankton:cache«. - -Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' - - -»bacterio-plankton:cache« 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. - -»bacterio-plankton:cache« 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 »bacterio-plankton:cache«. If not, see . - */ -var lib_plankton; -(function (lib_plankton) { - var cache; - (function (cache) { - var chest; - (function (chest) { - /** - * @author fenris - */ - function make(options = {}) { - options = Object.assign({ - "chest": lib_plankton.storage.memory.implementation_chest({}), - }, options); - return { - "chest": options.chest, - }; - } - chest.make = make; - /** - * @author fenris - */ - function init(subject) { - return subject.chest.setup(undefined); - } - chest.init = init; - /** - * @author fenris - */ - function clear(subject) { - return subject.chest.clear(); - } - chest.clear = clear; - /** - * @author fenris - */ - function query(subject, key, retrieve) { - return (subject.chest.read(key) - .then(value => (lib_plankton.log.info("cache.chest_hashed.known", { - "key": key, - }) - , - Promise.resolve({ - "retrieved": false, - "value": value, - }))) - .catch(() => (lib_plankton.log.info("cache.chest_hashed.unknown", { - "key": key, - }) - , - (retrieve() - .then((value) => (subject.chest.write(key, value) - .then(() => Promise.resolve({ - "retrieved": true, - "value": value, - })))) - /* - .catch( - (reason) => Promise.reject<{retrieved : boolean; value : type_value}>(reason) - ) - */ - )))); - } - chest.query = query; - /** - */ - function implementation(subject) { - return { - "init": () => init(subject), - "clear": () => clear(subject), - "query": (key, retrieve) => query(subject, key, retrieve), - }; - } - chest.implementation = implementation; - })(chest = cache.chest || (cache.chest = {})); - })(cache = lib_plankton.cache || (lib_plankton.cache = {})); -})(lib_plankton || (lib_plankton = {})); -/* This file is part of »bacterio-plankton:shape«. Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' diff --git a/source/base.ts b/source/base.ts index 4aa10be..8a9f23c 100644 --- a/source/base.ts +++ b/source/base.ts @@ -3,6 +3,16 @@ namespace _zeitbild /** */ - export var cache : lib_plankton.cache.type_cache; + export var cache_regular : lib_plankton.cache.type_subject; + + + /** + */ + export var cache_external_resources : lib_plankton.cache.type_subject; + + + /** + */ + export var cache_templates : lib_plankton.cache.type_subject; } diff --git a/source/conf.ts b/source/conf.ts index 5ed5613..a95febb 100644 --- a/source/conf.ts +++ b/source/conf.ts @@ -283,6 +283,22 @@ namespace _zeitbild.conf } } }, + "external_resources": { + "nullable": false, + "type": "object", + "properties": { + "lifetime": { + "nullable": false, + "type": "integer", + "default": 14400 + } + }, + "additionalProperties": false, + "required": [ + ], + "default": { + } + }, "misc": { "nullable": false, "type": "object", @@ -300,7 +316,7 @@ namespace _zeitbild.conf ], "additionalProperties": false, "default": {} - } + }, }, "required": [ "version" diff --git a/source/helpers.ts b/source/helpers.ts index 94e7cb5..7cb1d69 100644 --- a/source/helpers.ts +++ b/source/helpers.ts @@ -161,37 +161,29 @@ namespace _zeitbild.helpers /** */ - var _template_cache : Record = {}; - - - /** - * @todo caching - */ export async function template_coin( name : string, data : Record ) : Promise { - let content : string; - if (! (name in _template_cache)) { - content = ( - ( - await lib_plankton.file.read( - lib_plankton.string.coin( - "templates/{{name}}.html.tpl", - { - "name": name, - } - ) + const content : string = await lib_plankton.cache.get( + _zeitbild.cache_templates, + name, + null, + () => ( + lib_plankton.file.read( + lib_plankton.string.coin( + "templates/{{name}}.html.tpl", + { + "name": name, + } ) ) - .toString() - ); - _template_cache[name] = content; - } - else { - content = _template_cache[name]; - } + .then( + x => Promise.resolve(x.toString()) + ) + ) + ); return Promise.resolve( lib_plankton.string.coin( content, diff --git a/source/main.ts b/source/main.ts index 72c91ce..8fec50a 100644 --- a/source/main.ts +++ b/source/main.ts @@ -313,14 +313,18 @@ async function main( } ) ); - _zeitbild.cache = lib_plankton.cache.chest.implementation( - lib_plankton.cache.chest.make( - { - "chest": lib_plankton.storage.memory.implementation_chest({}), - } - ) - ); - await _zeitbild.cache.init(); + { + _zeitbild.cache_regular = lib_plankton.cache.make(); + await lib_plankton.cache.init(_zeitbild.cache_regular); + } + { + _zeitbild.cache_external_resources = lib_plankton.cache.make(); + await lib_plankton.cache.init(_zeitbild.cache_external_resources); + } + { + _zeitbild.cache_templates = lib_plankton.cache.make(); + await lib_plankton.cache.init(_zeitbild.cache_templates); + } // exec if (args["help"]) { diff --git a/source/repositories/calendar.ts b/source/repositories/calendar.ts index abfea1c..0fc9362 100644 --- a/source/repositories/calendar.ts +++ b/source/repositories/calendar.ts @@ -251,7 +251,7 @@ namespace _zeitbild.repository.calendar {"level": access_attributed_row["level"]} ); } - await _zeitbild.cache.clear(); + await lib_plankton.cache.clear(_zeitbild.cache_regular); return Promise.resolve<_zeitbild.type_calendar_id>(calendar_id); } @@ -313,7 +313,7 @@ namespace _zeitbild.repository.calendar ); } } - await _zeitbild.cache.clear(); + await lib_plankton.cache.clear(_zeitbild.cache_regular); return Promise.resolve(undefined); } @@ -325,7 +325,7 @@ namespace _zeitbild.repository.calendar calendar_id : _zeitbild.type_calendar_id ) : Promise { - await _zeitbild.cache.clear(); + await lib_plankton.cache.clear(_zeitbild.cache_regular); const core_store = get_core_store(); const access_attributed_chest = get_access_attributed_chest(); // attributed access @@ -375,11 +375,12 @@ namespace _zeitbild.repository.calendar > { return lib_plankton.cache.get_complex>( - _zeitbild.cache, + _zeitbild.cache_regular, "calendar_overview", { "user_id": user_id, }, + null, () => ( lib_plankton.file.read("sql/calendar_overview.sql") .then( diff --git a/source/repositories/resource.ts b/source/repositories/resource.ts index 6bae0ca..a191bfd 100644 --- a/source/repositories/resource.ts +++ b/source/repositories/resource.ts @@ -401,7 +401,7 @@ namespace _zeitbild.repository.resource "sub_id": local_resource_id, } ); - await _zeitbild.cache.clear(); + await lib_plankton.cache.clear(_zeitbild.cache_regular); return Promise.resolve<_zeitbild.type_resource_id>(resource_id); break; } @@ -419,7 +419,7 @@ namespace _zeitbild.repository.resource "sub_id": caldav_resource_id, } ); - await _zeitbild.cache.clear(); + await lib_plankton.cache.clear(_zeitbild.cache_regular); return Promise.resolve<_zeitbild.type_resource_id>(resource_id); break; } @@ -499,7 +499,7 @@ namespace _zeitbild.repository.resource "read_only": resource_object.data.read_only, } ); - await _zeitbild.cache.clear(); + await lib_plankton.cache.clear(_zeitbild.cache_regular); break; } default: { @@ -561,7 +561,7 @@ namespace _zeitbild.repository.resource } ) ); - await _zeitbild.cache.clear(); + await lib_plankton.cache.clear(_zeitbild.cache_regular); return Promise.resolve(local_resource_event_id); } } @@ -589,7 +589,7 @@ namespace _zeitbild.repository.resource } ) ); - await _zeitbild.cache.clear(); + await lib_plankton.cache.clear(_zeitbild.cache_regular); return Promise.resolve(undefined); } } @@ -602,7 +602,7 @@ namespace _zeitbild.repository.resource local_resource_event_id : _zeitbild.type_local_resource_event_id ) : Promise { - await _zeitbild.cache.clear(); + await lib_plankton.cache.clear(_zeitbild.cache_regular); const dataset_core : Record = await get_resource_core_store().read(resource_id); if (! (dataset_core.kind === "local")) { throw (new Error("not a local resource")); diff --git a/source/services/calendar.ts b/source/services/calendar.ts index 28686f4..f2a21dc 100644 --- a/source/services/calendar.ts +++ b/source/services/calendar.ts @@ -329,45 +329,52 @@ namespace _zeitbild.service.calendar } case "caldav": { // TODO readonly - const url : lib_plankton.url.type_url = lib_plankton.url.decode( - resource_object.data.url - ); - const http_request : lib_plankton.http.type_request = { - "version": "HTTP/2", - "scheme": ((url.scheme === "https") ? "https" : "http"), - "host": url.host, - "path": (url.path ?? "/"), - "query": url.query, - "method": lib_plankton.http.enum_method.get, - "headers": {}, - "body": null, - }; - // TODO: cache? - const http_response : lib_plankton.http.type_response = await lib_plankton.http.call( - http_request, - { + const vcalendar : lib_plankton.ical.type_vcalendar = await lib_plankton.cache.get( + _zeitbild.cache_external_resources, + resource_object.data.url, + _zeitbild.conf.get().external_resources.lifetime, + async () => { + const url : lib_plankton.url.type_url = lib_plankton.url.decode( + resource_object.data.url + ); + const http_request : lib_plankton.http.type_request = { + "version": "HTTP/2", + "scheme": ((url.scheme === "https") ? "https" : "http"), + "host": url.host, + "path": (url.path ?? "/"), + "query": url.query, + "method": lib_plankton.http.enum_method.get, + "headers": {}, + "body": null, + }; + const http_response : lib_plankton.http.type_response = await lib_plankton.http.call( + http_request, + { + } + ); + const ics_raw : string = ( + (http_response.body === null) + ? + "" + : + http_response.body.toString() + ); + const vcalendar_list : Array = lib_plankton.ical.ics_decode_multi( + ics_raw, + { + "ignore_unhandled_instruction_keys": resource_object.data.from_fucked_up_wordpress, + "from_fucked_up_wordpress": resource_object.data.from_fucked_up_wordpress, + } + ); + const vcalendar : lib_plankton.ical.type_vcalendar = { + // required + "version": vcalendar_list[0].version, + "prodid": vcalendar_list[0].prodid, + "vevents": vcalendar_list.map(x => x.vevents).reduce((x, y) => x.concat(y), []), + }; + return Promise.resolve(vcalendar); } ); - const ics_raw : string = ( - (http_response.body === null) - ? - "" - : - http_response.body.toString() - ); - const vcalendar_list : Array = lib_plankton.ical.ics_decode_multi( - ics_raw, - { - "ignore_unhandled_instruction_keys": resource_object.data.from_fucked_up_wordpress, - "from_fucked_up_wordpress": resource_object.data.from_fucked_up_wordpress, - } - ); - const vcalendar : lib_plankton.ical.type_vcalendar = { - // required - "version": vcalendar_list[0].version, - "prodid": vcalendar_list[0].prodid, - "vevents": vcalendar_list.map(x => x.vevents).reduce((x, y) => x.concat(y), []), - }; return Promise.resolve( vcalendar.vevents .map( @@ -490,7 +497,7 @@ namespace _zeitbild.service.calendar ); calendar_ids.sort(); return lib_plankton.cache.get_complex( - _zeitbild.cache, + _zeitbild.cache_regular, "gather_events", { "user_id": user_id, @@ -498,6 +505,10 @@ namespace _zeitbild.service.calendar "to_pit": to_pit, "calendar_ids": calendar_ids, }, + /** + * @todo expire? + */ + null, () => ( Promise.all( calendar_ids