From bdd257545b04478a81a3c0b3165d5eb1384a3138 Mon Sep 17 00:00:00 2001 From: Martin Karkowski Date: Sun, 6 Nov 2022 11:44:49 +0100 Subject: [PATCH] adding new files in 1.4.6 --- lib/decorators/functionDecorators.ts | 7 +- lib/helpers/fileMethods.ts | 21 ++- lib/helpers/hash.ts | 28 +++ lib/helpers/index.browser.ts | 10 ++ lib/helpers/lists.ts | 259 +++++++++++++++++++++++++++ lib/helpers/taskQueue.spec.ts | 132 ++++++++++++++ lib/helpers/taskQueue.ts | 155 ++++++++++++++++ lib/types/ui/helpers.interface.ts | 14 +- lib/ui/helpers.nodejs.ts | 24 +-- 9 files changed, 621 insertions(+), 29 deletions(-) create mode 100644 lib/helpers/hash.ts create mode 100644 lib/helpers/lists.ts create mode 100644 lib/helpers/taskQueue.spec.ts create mode 100644 lib/helpers/taskQueue.ts diff --git a/lib/decorators/functionDecorators.ts b/lib/decorators/functionDecorators.ts index b2cbe20..9b99ad5 100644 --- a/lib/decorators/functionDecorators.ts +++ b/lib/decorators/functionDecorators.ts @@ -26,7 +26,7 @@ export type callable = { export function exportAsNopeService( func: T, options: IexportAsNopeServiceParameters -) { +): T & { options: IexportAsNopeServiceParameters } { // Only add the element if it doesnt exists. if (!CONTAINER.services.has(options.id)) { CONTAINER.services.set(options.id, { @@ -37,5 +37,8 @@ export function exportAsNopeService( uri: options.id || (func as any).name, }); } - return func; + + (func as any).options = options; + + return func as any; } diff --git a/lib/helpers/fileMethods.ts b/lib/helpers/fileMethods.ts index 58eb438..7f5b370 100644 --- a/lib/helpers/fileMethods.ts +++ b/lib/helpers/fileMethods.ts @@ -73,11 +73,24 @@ export async function createPath(path: string) { return path; } -export async function deletePath(_dir_path: string): Promise { - if (!(await exists(_dir_path))) { +/** + * Deletes the complete Path recursevly. + * > *WARNING: * Deletes Everything in the Folder. + * + * Example: + * `deletePath('C:\\Test');` + * + * This deletes all Files and Subfolders in the given Path. + * Furthermore the Folder Test itself is removed. + * + * @export + * @param {string} dir_path + */ +export async function deletePath(dir_path: string): Promise { + if (!(await exists(dir_path))) { throw new URIError("path doesnt exits."); } - const _totalPath = _dir_path; + const _totalPath = dir_path; /** Sort the Pathes according to their length. For instance: * _pathes = ['C:\\Test\\Sub', 'C:\\Test\\Sub\\SubSub'] * @@ -104,7 +117,7 @@ export async function deletePath(_dir_path: string): Promise { await rmdir(_path); } - await rmdir(_dir_path); + await rmdir(dir_path); } /** diff --git a/lib/helpers/hash.ts b/lib/helpers/hash.ts new file mode 100644 index 0000000..6fe4486 --- /dev/null +++ b/lib/helpers/hash.ts @@ -0,0 +1,28 @@ +/** + * @author M.Karkowski + * @email M.Karkowski@zema.de + */ + +import { stringifyWithFunctions } from "./jsonMethods"; + +/** + * Function to generate a Hash + * @param obj the Object, that should be hashed + */ +export function generateHash(obj: any) { + // Convert the object to String + const str = typeof obj === "string" ? obj : stringifyWithFunctions(obj); + + // Define Vars. + let hash = 0, + i, + chr; + if (str.length === 0) return hash.toString(); + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; // Convert to 32bit integer + } + + return hash.toString(); +} diff --git a/lib/helpers/index.browser.ts b/lib/helpers/index.browser.ts index 7a17766..cacc31e 100644 --- a/lib/helpers/index.browser.ts +++ b/lib/helpers/index.browser.ts @@ -8,34 +8,41 @@ import * as async from "./async"; import * as descriptors from "./descriptors"; import * as functions from "./functionMethods"; import * as subject from "./getSubject"; +import * as hashs from "./hash"; import * as ids from "./idMethods"; import * as json from "./jsonMethods"; import * as schema from "./jsonSchemaMethods"; import * as lazy from "./lazyMethods"; import * as limit from "./limit"; +import * as lists from "./lists"; import * as objects from "./objectMethods"; import * as pathes from "./pathMatchingMethods"; import * as runtime from "./runtimeMethods"; import * as sets from "./setMethods"; import * as singletons from "./singletonMethod"; import * as strings from "./stringMethods"; +import * as taskQueues from "./taskQueue"; export * from "./arrayMethods"; export * from "./async"; export * from "./descriptors"; export * from "./functionMethods"; export * from "./getSubject"; +export * from "./hash"; export * from "./idMethods"; export * from "./jsonMethods"; export * from "./jsonSchemaMethods"; export * from "./lazyMethods"; export * from "./limit"; +export * from "./lists"; export * from "./objectMethods"; export * from "./pathMatchingMethods"; export * from "./runtimeMethods"; export * from "./setMethods"; export * from "./singletonMethod"; export * from "./stringMethods"; +export * from "./taskQueue"; + export { async, arrays, @@ -53,4 +60,7 @@ export { descriptors, functions, limit, + hashs, + taskQueues, + lists, }; diff --git a/lib/helpers/lists.ts b/lib/helpers/lists.ts new file mode 100644 index 0000000..6eb922f --- /dev/null +++ b/lib/helpers/lists.ts @@ -0,0 +1,259 @@ +import { dynamicSort, extractListElement } from "./arrayMethods"; + +/** + * A Priority List. All Items are sorted by a Priority Number. + * + * @export + * @class PriorityList + */ +export class PriorityList { + private _priority_list = new Array<{ priority: number; data: T }>(); + private _list = new Array(); + private _updated = false; + + /** + * Function to returns a sorted List containing only the Value + * + * @returns {Array} Sorted List containing the Values. + * @memberof PriorityList + */ + public list(): Array { + return extractListElement(this._priority_list, "data"); + } + + protected _sort(): void { + // Sort the List based on the element priority + this._priority_list.sort(dynamicSort("priority", true)); + + // Adapt the _list element : + this._list = extractListElement(this._priority_list, "data"); + this._updated = true; + } + + /** + * Adds Data to the Priority List + * @param _priority lower => lower priority + * @param _data data which are stored + */ + public push(_priority: number, _data: T): void { + // Add the Element with the given priority to the list + this._updated = false; + this._priority_list.push({ priority: _priority, data: _data }); + } + + /** + * Returns the Element with the lowest priority + * + * @param {boolean} [remove=true] Flag to remove the item. Defaults to true. Otherwise it remains in the list. + * @return {(T | null)} + * @memberof PriorityList + */ + public highest(remove = true): T | null { + if (!this._updated) { + this._sort(); + } + const _ret = this._priority_list[remove ? "splice" : "slice"](0, 1)[0]; + return _ret ? _ret.data : null; + } + + /** + * Returns the Element with the highest priority + * @param {boolean} [remove=true] Flag to remove the item. Defaults to true. Otherwise it remains in the list. + * @return {(T | null)} + * @memberof PriorityList + */ + public lowest(remove = true): T | null { + if (!this._updated) { + this._sort(); + } + let _ret: { priority: number; data: T } | undefined = undefined; + if (remove) { + _ret = this._priority_list.pop(); + } else { + _ret = this._priority_list[this._list.length - 1]; + } + + return _ret ? _ret.data : null; + } + + /** + * Returns the Length of the Priority list + * + * @readonly + * @type {number} + * @memberof PriorityList + */ + public get length(): number { + return this._priority_list.length; + } +} + +/** + * Limited List. This list at max contains a specific amount of elements. + * After the max number of elements has been added, the first element added + * will be removed. + */ +export class LimitedList { + /** + * Element containing the list + * + * @private + * @type {Array} + * @memberof LimitedList + */ + private _list: Array; + /** + * Internal Pointer, showing the actual item. + * + * @private + * @type {number} + * @memberof LimitedList + */ + private _pointer: number; + + constructor(public maxLength: number) { + this._pointer = -1; + this._list = new Array(); + } + + /** + * Adds Data to the Stack. The Pointer is getting adapted. + * + * @param {T} data + * @returns + * @memberof LimitedList + */ + push(data: T) { + // Check if the Maximum length is achieved + if (this._list.length >= this.maxLength) { + // Remove the First Element + this._list = this._list.slice(1, this._pointer + 1); + } + + // Store the Content + const ret = this._list.push(data); + + // Adapt the Pointer + this._pointer = this._list.length - 1; + + return ret; + } + + /** + * Contains the Length of the list. + * + * @readonly + * @memberof LimitedList + */ + public get length() { + return this._list.length; + } + + /** + * Gets the current pointer. + * + * @readonly + * @memberof LimitedList + */ + public get currentPointer() { + return this._pointer; + } + + last(): T | null { + if (this._list.length > 0) { + this._pointer = this._list.length - 1; + return this._list[this._pointer]; + } + + // No data available. + return null; + } + + /** + * Returns the Pointer to the first item. + * @returns + */ + first(): T | null { + this._pointer = this._list.length - 1; + + if (this._pointer >= 0 && this._pointer < this._list.length) { + return this._list[this._pointer]; + } + + // No data available. + return null; + } + + /** + * Returns the last item. Adapts the pointer and the + * current item is the last item. + * example: + * l = limited.last() + * c = limited.current() + * + * l == c -> True + * @returns The last element. + */ + previous(): T | null { + // Check if the Pointer is in the defined Range + if (this._pointer - 1 >= 0 && this._pointer - 1 < this._list.length) { + return this._list[--this._pointer]; + } + + // No data available. + return null; + } + + /** + * Returns the current item, the pointer is showing at. + * @returns + */ + current(): T | null { + // Check if the Pointer is in the defined Range + if (this._pointer >= 0 && this._pointer < this._list.length) { + return this._list[this._pointer]; + } + + /** No data available any more */ + return null; + } + + next(): T | null { + /** Check if the Pointer is in the defined Range */ + if (this._pointer + 1 >= 0 && this._pointer + 1 < this._list.length) { + return this._list[++this._pointer]; + } + + /** No data available any more */ + return null; + } + + /** + * Pops the last element. If there is no element undefined is returned. + * @returns The last element. + */ + pop(current = false): T { + if (current) { + const ret = this._list.splice(this._pointer, 1)[0]; + return ret; + } + + const ret = this._list.pop(); + // Adapt the Pointer + this._pointer = this._list.length - 1; + + return ret; + } + + /** + * Helper to iterate over all items. + * @param callbackFn + * @param thisArg + */ + public forEach( + callbackFn: (item: T, index: number, array: Array) => void, + thisArg?: any + ) { + this._list.forEach(callbackFn, thisArg); + } +} diff --git a/lib/helpers/taskQueue.spec.ts b/lib/helpers/taskQueue.spec.ts new file mode 100644 index 0000000..fb44ea3 --- /dev/null +++ b/lib/helpers/taskQueue.spec.ts @@ -0,0 +1,132 @@ +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @desc [description] + */ + +import { sleep } from "./async"; +import { assert, expect } from "chai"; +import { describe, it } from "mocha"; +import { PriorityTaskQueue } from "./taskQueue"; + +describe("PriorityTaskQueue", function () { + // Describe the required Test: + + describe("Async Functions", function () { + it("no parallel execution - no priority", async function () { + let called: string[] = []; + + async function delayed(ret: string) { + await sleep(25); + called.push(ret); + return ret; + } + + const queue = new PriorityTaskQueue(); + queue.maxParallel = 1; + queue.usePriority = false; + + const promises = [ + queue.execute(delayed, ["first"], 5), + queue.execute(delayed, ["second"], 10), + ]; + + const start = Date.now(); + + await Promise.all(promises); + + const diff = Date.now() - start; + + assert(diff > 40, "Functions should be called after each other"); + assert(called[0] == "first", "First should be the first entry"); + assert(called[1] == "second", "First should be the first entry"); + }); + it("parallel execution - no priority", async function () { + let called: string[] = []; + + async function delayed(ret: string) { + await sleep(25); + called.push(ret); + return ret; + } + + const queue = new PriorityTaskQueue(); + queue.maxParallel = 10; + queue.usePriority = false; + + const promises = [ + queue.execute(delayed, ["first"], 5), + queue.execute(delayed, ["second"], 10), + ]; + + const start = Date.now(); + + await Promise.all(promises); + + const diff = Date.now() - start; + + assert(diff < 40, "Functions should be called parallel"); + assert(called[0] == "first", "First should be the first entry"); + assert(called[1] == "second", "First should be the first entry"); + }); + it("no parallel execution - with priority", async function () { + let called: string[] = []; + + async function delayed(ret: string) { + await sleep(25); + called.push(ret); + return ret; + } + + const queue = new PriorityTaskQueue(); + queue.maxParallel = 1; + queue.usePriority = true; + + const promises = [ + queue.execute(delayed, ["first"], 5), + queue.execute(delayed, ["second"], 10), + queue.execute(delayed, ["third"], 15), + ]; + + const start = Date.now(); + + await Promise.all(promises); + + const diff = Date.now() - start; + + assert(diff > 40, "Functions should be called after each other"); + assert(called[1] == "third", "second should be the third entry"); + assert(called[2] == "second", "third should be the second entry"); + }); + it("parallel execution - with priority", async function () { + let called: string[] = []; + + async function delayed(ret: string) { + await sleep(25); + called.push(ret); + return ret; + } + + const queue = new PriorityTaskQueue(); + queue.maxParallel = 10; + queue.usePriority = true; + + const promises = [ + queue.execute(delayed, ["first"], 5), + queue.execute(delayed, ["second"], 10), + queue.execute(delayed, ["third"], 15), + ]; + + const start = Date.now(); + + await Promise.all(promises); + + const diff = Date.now() - start; + + assert(diff < 40, "Functions should be called parallel"); + assert(called[0] == "first", "First should be the first entry"); + assert(called[1] == "second", "second should be the second entry"); + assert(called[2] == "third", "third should be the third entry"); + }); + }); +}); diff --git a/lib/helpers/taskQueue.ts b/lib/helpers/taskQueue.ts new file mode 100644 index 0000000..e9c4391 --- /dev/null +++ b/lib/helpers/taskQueue.ts @@ -0,0 +1,155 @@ +import { NopePromise } from "../promise"; +import { isAsyncFunction } from "./async"; +import { PriorityList } from "./lists"; + +/** + * A Task-Queue. This could be used to make parallel + * Request run sequentially. For Instance during + * Saving and Reading Vars to achive a consistent set + * of Data. + * + * Usage: + * // Create a Queue + * const _queue = new PriorityTaskQueue(); + * // Create a Function + * const _func = (_input: string, _cb) => { + * console.log("Hallo ", _input) + * _cb(null, null); + * }; + * + * await _queue.execute(_func, 'Welt'); + * + * @export + * @class TaskQeue + */ +export class PriorityTaskQueue { + protected _queue = new PriorityList<{ + func: (...args) => void; + cancel: () => void; + args: any; + resolve: (data) => void; + reject: (err) => void; + }>(); + + protected _runningTasks = 0; + protected _counter = 0; + + public maxParallel = 1; + public usePriority = true; + + /** + * Executes the given Task. If now Task is running it is executed immediatelly, + * otherwise it is pushed in the queue and call if the other tasks are call. + * + * @param {any} _func The Function which should be called. + * @param {any} _param The Data which should be used for the call. + * @param {any} _callback The Callback, which should be called after + * @memberof TaskQeue + */ + public execute( + func: (...args) => T | Promise, + args: any[], + priority: number = 0, + cancel: () => void = () => {} + ): NopePromise { + let resolve, reject; + + const promise = new NopePromise((res, rej) => { + resolve = res; + reject = rej; + }); + + promise.cancel = cancel; + + // Check whether the Execution is activ: + if ( + this._runningTasks < this.maxParallel && + this._queue.length < this.maxParallel + ) { + this._runningTasks++; + this._execute({ + func, + args, + cancel, + resolve, + reject, + }); + } else { + // Extend the Queue. + this._queue.push(this.usePriority ? priority : this._counter++, { + func, + args, + cancel, + resolve, + reject, + }); + } + + return promise; + } + + protected _execute(data: { + func: (...args) => any | Promise; + args: any[]; + cancel: () => void; + resolve: (data) => void; + reject: (err) => void; + }) { + // Verify whether there is an CancelHandler, if yes. + // Register at the Cancel-Handler. Thereby the next + // function is call if the currently running Task is + // aborted. + if (data.cancel) { + data.args.push(() => { + data.cancel(); + data.reject(Error("Canceled")); + this._finish(); + }); + } + if (isAsyncFunction(data.func)) { + (data.func as (...args: any[]) => Promise)(...data.args) + .then((res) => { + data.resolve(res); + this._finish(); + }) + .catch((err) => { + data.reject(err); + this._finish(); + }); + } else { + try { + const res = (data.func as (...args: any[]) => any)(...data.args); + data.resolve(res); + } catch (err) { + data.reject(err); + } + this._finish(); + } + } + + /** + * Internal Function to Finish all Tasks. + * + * @protected + * @memberof PriorityTaskQueue + */ + protected _finish() { + // Remove one Element. + this._runningTasks--; + + if (this._runningTasks < 0) { + this._runningTasks = 0; + } + // Remove the First Task + const task = this._queue.highest(); + + // Call the Function with the adapted Callback, if there is a Task Left open. + if (task) { + this._execute(task); + } + } + + public get length(): number { + return this._queue.length; + } +} diff --git a/lib/types/ui/helpers.interface.ts b/lib/types/ui/helpers.interface.ts index 4af401c..22d130b 100644 --- a/lib/types/ui/helpers.interface.ts +++ b/lib/types/ui/helpers.interface.ts @@ -183,19 +183,7 @@ export interface IUiDefinition { */ ui?: IClassDescription["ui"]; /** - * Name of the Package - */ - package: string; - /** - * Path of the defintio file. - */ - path?: string; - /** - * Class identifier - */ - class: string; - /** - * The Methods of the class + * Definition of the UI. */ methods: { [index: string]: IServiceOptions["ui"]; diff --git a/lib/ui/helpers.nodejs.ts b/lib/ui/helpers.nodejs.ts index 5706e8e..3d2ca1a 100644 --- a/lib/ui/helpers.nodejs.ts +++ b/lib/ui/helpers.nodejs.ts @@ -46,13 +46,6 @@ export async function writeUiFile( // Iterate over the classes. for (const cls of item.package.providedClasses) { const itemToAdd: IUiDefinition["classes"][0] = { - // The Class Name - class: cls.description.name, - // The Package Name - package: item.package.nameOfPackage, - // The Path of he File. - path: item.path, - // The defined UI defintions. ui: cls.ui, // Define the Methods elements methods: {}, @@ -74,7 +67,12 @@ export async function writeUiFile( ) { // If an ui definition exists, we want // to export it and store it in our file. - uiFile.classes[itemToAdd.class] = itemToAdd; + const ui = itemToAdd.ui || {}; + + uiFile.classes[cls.description.name] = { + ...ui, + methods: itemToAdd.methods, + }; } } @@ -302,7 +300,10 @@ export async function uploadUi(args: Partial) { return result; } - async function getContentOfNewestFile() { + async function getContentOfNewestFile(): Promise<{ + functions: any; + classes: any; + }> { // Get all Possible Files const _files = await getFiles((item, scope) => { return item.identifier === "ui-definition"; @@ -330,7 +331,10 @@ export async function uploadUi(args: Partial) { logger.error(e); } } - return {}; + return { + functions: {}, + classes: {}, + }; } logger.info(