diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index a48e5eb..6e5adc3 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -2381,6 +2381,99 @@ 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; + }>); + }; +} +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; + }): 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 implementation(subject: type_subject): type_cache; +} declare namespace lib_plankton.shape { /** * @todo diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index ba20662..bbc1b13 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -7423,6 +7423,289 @@ var lib_plankton; })(storage = lib_plankton.storage || (lib_plankton.storage = {})); })(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 . + */ +/* +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_1) { + /** + */ + function get(cache, key, retrieve) { + return cache.query(key, retrieve).then(result => 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)); + } + 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 new file mode 100644 index 0000000..4aa10be --- /dev/null +++ b/source/base.ts @@ -0,0 +1,8 @@ +namespace _zeitbild +{ + + /** + */ + export var cache : lib_plankton.cache.type_cache; + +} diff --git a/source/main.ts b/source/main.ts index d20dcc9..326b563 100644 --- a/source/main.ts +++ b/source/main.ts @@ -260,6 +260,14 @@ 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(); // exec if (args["help"]) { diff --git a/source/repositories/calendar.ts b/source/repositories/calendar.ts index 30c5beb..abfea1c 100644 --- a/source/repositories/calendar.ts +++ b/source/repositories/calendar.ts @@ -251,6 +251,7 @@ namespace _zeitbild.repository.calendar {"level": access_attributed_row["level"]} ); } + await _zeitbild.cache.clear(); return Promise.resolve<_zeitbild.type_calendar_id>(calendar_id); } @@ -312,6 +313,7 @@ namespace _zeitbild.repository.calendar ); } } + await _zeitbild.cache.clear(); return Promise.resolve(undefined); } @@ -323,6 +325,7 @@ namespace _zeitbild.repository.calendar calendar_id : _zeitbild.type_calendar_id ) : Promise { + await _zeitbild.cache.clear(); const core_store = get_core_store(); const access_attributed_chest = get_access_attributed_chest(); // attributed access @@ -371,70 +374,77 @@ namespace _zeitbild.repository.calendar > > { - return ( - lib_plankton.file.read("sql/calendar_overview.sql") - .then( - (template) => _zeitbild.database.get_implementation().query_free_get( - { - "template": template, - "arguments": { - "user_id": user_id, + return lib_plankton.cache.get_complex>( + _zeitbild.cache, + "calendar_overview", + { + "user_id": user_id, + }, + () => ( + lib_plankton.file.read("sql/calendar_overview.sql") + .then( + (template) => _zeitbild.database.get_implementation().query_free_get( + { + "template": template, + "arguments": { + "user_id": user_id, + } } - } + ) ) - ) - .then( - (rows) => Promise.resolve( - lib_plankton.call.convey( - rows, - [ - (x : Array>) => x.map( - (row : Record) => ({ - "id": row["id"], - "name": row["name"], - /** - * @todo unite with _zeitbild.service.calendar.get_access_level - */ - "access_level": decode_access_level( - Math.max( - (row["access_public"] ? 1 : 0), - ( - (user_id === null) - ? - 0 - : - (row["access_level_attributed"] ?? row["access_level_default"]) + .then( + (rows) => Promise.resolve( + lib_plankton.call.convey( + rows, + [ + (x : Array>) => x.map( + (row : Record) => ({ + "id": row["id"], + "name": row["name"], + /** + * @todo unite with _zeitbild.service.calendar.get_access_level + */ + "access_level": decode_access_level( + Math.max( + (row["access_public"] ? 1 : 0), + ( + (user_id === null) + ? + 0 + : + (row["access_level_attributed"] ?? row["access_level_default"]) + ) ) + ), + }) + ), + (x : Array) => x.filter( + (row) => ( + ! _zeitbild.value_object.access_level.order( + row.access_level, + _zeitbild.enum_access_level.none ) - ), - }) - ), - (x : Array) => x.filter( - (row) => ( - ! _zeitbild.value_object.access_level.order( - row.access_level, - _zeitbild.enum_access_level.none ) - ) - ), - (x : Array) => lib_plankton.list.sorted( - x, - { - "compare_element": lib_plankton.order.order_lexicographic_pair_wrapped( - row => row.access_level, - row => row.id, - { - "order_first": _zeitbild.value_object.access_level.order, - "order_second": (a, b) => (a <= b) - } - ), - } - ), - ] + ), + (x : Array) => lib_plankton.list.sorted( + x, + { + "compare_element": lib_plankton.order.order_lexicographic_pair_wrapped( + row => row.access_level, + row => row.id, + { + "order_first": (a, b) => _zeitbild.value_object.access_level.order(b, a), + "order_second": (a, b) => (a <= b) + } + ), + } + ), + ] + ) ) ) ) - ) + ); } } diff --git a/source/repositories/resource.ts b/source/repositories/resource.ts index ad68e7f..7b7ffe8 100644 --- a/source/repositories/resource.ts +++ b/source/repositories/resource.ts @@ -398,6 +398,7 @@ namespace _zeitbild.repository.resource "sub_id": local_resource_id, } ); + await _zeitbild.cache.clear(); return Promise.resolve<_zeitbild.type_resource_id>(resource_id); break; } @@ -414,6 +415,7 @@ namespace _zeitbild.repository.resource "sub_id": caldav_resource_id, } ); + await _zeitbild.cache.clear(); return Promise.resolve<_zeitbild.type_resource_id>(resource_id); break; } @@ -493,6 +495,7 @@ namespace _zeitbild.repository.resource "read_only": resource_object.data.read_only, } ); + await _zeitbild.cache.clear(); break; } default: { @@ -546,7 +549,7 @@ namespace _zeitbild.repository.resource throw (new Error("not a local resource")); } else { - return get_local_resource_event_store().create( + const local_resource_event_id: _zeitbild.type_local_resource_event_id = await get_local_resource_event_store().create( encode_local_resource_event( { "local_resource_id": dataset_core["sub_id"], @@ -554,6 +557,8 @@ namespace _zeitbild.repository.resource } ) ); + await _zeitbild.cache.clear(); + return Promise.resolve(local_resource_event_id); } } @@ -571,7 +576,7 @@ namespace _zeitbild.repository.resource throw (new Error("not a local resource")); } else { - return get_local_resource_event_store().update( + await get_local_resource_event_store().update( event_id, encode_local_resource_event( { @@ -580,6 +585,8 @@ namespace _zeitbild.repository.resource } ) ); + await _zeitbild.cache.clear(); + return Promise.resolve(undefined); } } @@ -591,6 +598,7 @@ namespace _zeitbild.repository.resource local_resource_event_id : _zeitbild.type_local_resource_event_id ) : Promise { + await _zeitbild.cache.clear(); 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 377c3a8..b292755 100644 --- a/source/services/calendar.ts +++ b/source/services/calendar.ts @@ -425,6 +425,19 @@ namespace _zeitbild.service.calendar } + /** + */ + type type_gather_events_result = Array< + { + calendar_id : _zeitbild.type_calendar_id; + calendar_name : string; + access_level : _zeitbild.enum_access_level; + event_id : (null | _zeitbild.type_local_resource_event_id); + event_object : _zeitbild.type_event_object; + } + >; + + /** */ export async function gather_events( @@ -432,17 +445,7 @@ namespace _zeitbild.service.calendar from_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit, user_id : (null | _zeitbild.type_user_id) - ) : Promise< - Array< - { - calendar_id : _zeitbild.type_calendar_id; - calendar_name : string; - access_level : _zeitbild.enum_access_level; - event_id : (null | _zeitbild.type_local_resource_event_id); - event_object : _zeitbild.type_event_object; - } - > - > + ) : Promise { const calendar_ids_allowed : Array<_zeitbild.type_calendar_id> = ( (await overview(user_id)) @@ -464,48 +467,58 @@ namespace _zeitbild.service.calendar ) ); calendar_ids.sort(); - return ( - Promise.all( - calendar_ids - .map( - async (calendar_id) => { - const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read( - calendar_id - ); - const access_level : _zeitbild.enum_access_level = get_access_level( - calendar_object, - user_id - ); - const events : Array< - { - id : (null | _zeitbild.type_local_resource_event_id); - object : _zeitbild.type_event_object; - } - > = await get_events( - calendar_id, - from_pit, - to_pit, - user_id - ); - return Promise.resolve( - events - .map( - (event_entry) => ({ - "calendar_id": calendar_id, - "calendar_name": calendar_object.name, - "access_level": access_level, - "event_id": event_entry.id, - "event_object": event_entry.object, - }) - ) - ); - } + return lib_plankton.cache.get_complex( + _zeitbild.cache, + "gather_events", + { + "user_id": user_id, + "from_pit": from_pit, + "to_pit": to_pit, + "calendar_ids": calendar_ids, + }, + () => ( + Promise.all( + calendar_ids + .map( + async (calendar_id) => { + const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read( + calendar_id + ); + const access_level : _zeitbild.enum_access_level = get_access_level( + calendar_object, + user_id + ); + const events : Array< + { + id : (null | _zeitbild.type_local_resource_event_id); + object : _zeitbild.type_event_object; + } + > = await get_events( + calendar_id, + from_pit, + to_pit, + user_id + ); + return Promise.resolve( + events + .map( + (event_entry) => ({ + "calendar_id": calendar_id, + "calendar_name": calendar_object.name, + "access_level": access_level, + "event_id": event_entry.id, + "event_object": event_entry.object, + }) + ) + ); + } + ) ) - ) - .then( - (sub_results) => sub_results.reduce( - (x, y) => x.concat(y), - [] + .then( + (sub_results) => sub_results.reduce( + (x, y) => x.concat(y), + [] + ) ) ) ); diff --git a/tools/makefile b/tools/makefile index 5112546..1ce8c12 100644 --- a/tools/makefile +++ b/tools/makefile @@ -35,6 +35,7 @@ ${dir_temp}/conf.ts: \ ${dir_temp}/zeitbild-unlinked.js: \ ${dir_lib}/plankton/plankton.d.ts \ + ${dir_source}/base.ts \ ${dir_source}/helpers.ts \ ${dir_source}/conf.ts \ ${dir_source}/database.ts \ diff --git a/tools/update-plankton b/tools/update-plankton index 5f646fd..beecb08 100755 --- a/tools/update-plankton +++ b/tools/update-plankton @@ -10,6 +10,7 @@ modules="${modules} call" modules="${modules} log" modules="${modules} conf" modules="${modules} storage" +modules="${modules} cache" modules="${modules} database" modules="${modules} session" modules="${modules} file"