From 8608e79859df12cac480dcc005f901c6046aaa5a Mon Sep 17 00:00:00 2001 From: Amaury Date: Sat, 3 Jul 2021 12:47:06 +0200 Subject: [PATCH] issue 4 and 5 --- src/index.ts | 2 +- src/listener-rss-aggregator.ts | 98 ++++++++++++++++++++++++++++++++++ src/manage-listener.ts | 64 ---------------------- src/sqlite-tools.ts | 15 ++++-- tests/index-spec.ts | 10 ++-- tests/sqlite-tools-spec.ts | 6 +-- tsconfig.base.json | 16 +++--- tsconfig.json | 24 +++++---- 8 files changed, 141 insertions(+), 94 deletions(-) create mode 100644 src/listener-rss-aggregator.ts delete mode 100644 src/manage-listener.ts diff --git a/src/index.ts b/src/index.ts index 1b3697c..7d9f0d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -export { ManageListener } from "./manage-listener"; +export { ListenerRssAggregator } from "./listener-rss-aggregator"; diff --git a/src/listener-rss-aggregator.ts b/src/listener-rss-aggregator.ts new file mode 100644 index 0000000..42b4d1c --- /dev/null +++ b/src/listener-rss-aggregator.ts @@ -0,0 +1,98 @@ +import EventEmitter from "events"; + +import { ListenerRss, ListenerRSSInfos } from "listener-rss"; +import { SqliteTools } from "./sqlite-tools"; + +/** + * Permit to manage a ListenerRSS array, data storage and event aggregation + */ +export class ListenerRssAggregator extends EventEmitter { + private listenerArray: Map = new Map(); // renomer listenerMap + private looprunning: boolean = false; + + private sqliteDb: SqliteTools; + + constructor(path: string) { + super(); + this.sqliteDb = new SqliteTools(path); + this.load(); + } + + async getAllConfigs(): Promise { + return await this.sqliteDb.fetchAll(); + } + + private async load() { + const configs = await this.getAllConfigs(); + + configs.forEach((config) => { + this.addNewListener(config); + }); + } + + private addNewListener(info: ListenerRSSInfos): ListenerRss { + const newListener = new ListenerRss(info); + this.listenerArray.set(newListener.address, newListener); + + newListener.on("update", (obj) => this.emit("update", obj)); + newListener.on("newEntries", (obj) => this.emit("newEntries", obj)); + newListener.on("error", (err) => this.emit("error", err)); + + return newListener; + } + + private removeOldListener(adr: string): void { + const oldListener = this.listenerArray.get(adr); + + if (!oldListener) return; + + oldListener.stop(); + oldListener + .removeAllListeners("update") + .removeAllListeners("newEntries") + .removeAllListeners("error"); + this.listenerArray.delete(adr); + } + + async registerListener(info: ListenerRSSInfos) { + if (this.listenerArray.has(info.address)) return; + + const listener = this.addNewListener(info); + await this.sqliteDb.insertListener(listener); + } + + async unregisterListener(adr: string): Promise { + if (!this.listenerArray.has(adr)) return; + + this.removeOldListener(adr); + await this.sqliteDb.deleteListener(adr); + } + + async saveOverride(expectedConfig: ListenerRSSInfos[]) { + const actualConfig = await this.getAllConfigs(); + + for (const newItem of expectedConfig.filter( + (item) => !actualConfig.includes(item) + )) + this.registerListener(newItem); + + for (const oldItem of actualConfig.filter( + (item) => !expectedConfig.includes(item) + )) + this.unregisterListener(oldItem.address); + } + + startAll(): void { + if (this.looprunning) return; + + this.looprunning = true; + for (const item of this.listenerArray.values()) item.start(); + } + + stopAll(): void { + if (!this.looprunning) return; + + this.looprunning = false; + for (const item of this.listenerArray.values()) item.stop(); + } +} diff --git a/src/manage-listener.ts b/src/manage-listener.ts deleted file mode 100644 index 4bbc1c4..0000000 --- a/src/manage-listener.ts +++ /dev/null @@ -1,64 +0,0 @@ -import EventEmitter from "events"; - -import { ListenerRss, ListenerRSSInfos } from "listener-rss"; -import { SqliteTools } from "./sqlite-tools"; - -/** - * Permit to manage a ListenerRSS array, data storage and event aggregation - */ -export class ManageListener extends EventEmitter { - listenerArray: ListenerRss[] = []; - looprunning: boolean = false; - - sqliteDb: SqliteTools; - - constructor(path: string) { - super(); - this.sqliteDb = new SqliteTools(path); - } - - async load() { - await this.sqliteDb.ensureTableExists(); - const configs: ListenerRSSInfos[] = await this.sqliteDb.fetchAll(); - - configs.forEach((config) => { - this.addNewListener(config); - }); - } - - private addNewListener(info: ListenerRSSInfos) : ListenerRss { - const newListener = new ListenerRss(info); - this.listenerArray.push(newListener); - - newListener.on("update", (obj) => this.emit("update", obj)); - newListener.on("newEntries", (obj) => this.emit("newEntries", obj)); - newListener.on("error", (err) => this.emit("error", err)); - - return newListener; - } - - async registerListener(info: ListenerRSSInfos) { - const listener = this.addNewListener(info); - await this.sqliteDb.insertListener(listener); - } - - async save() { - await this.sqliteDb.updateAll( - this.listenerArray.map((listener) => listener.getProperty()) - ); - } - - startAll(): void { - if (!this.looprunning) { - this.looprunning = true; - this.listenerArray.forEach((listener) => listener.start()); - } - } - - stopAll(): void { - if (this.looprunning) { - this.looprunning = false; - this.listenerArray.forEach((listener) => listener.stop()); - } - } -} diff --git a/src/sqlite-tools.ts b/src/sqlite-tools.ts index a5c8ae9..aad5b4f 100644 --- a/src/sqlite-tools.ts +++ b/src/sqlite-tools.ts @@ -9,7 +9,9 @@ export class SqliteTools { this.path = path; } - async withDB(callback: (db: DatabaseConnection) => Promise): Promise { + async withDB( + callback: (db: DatabaseConnection) => Promise + ): Promise { const db = connect(this.path); try { return callback(db); @@ -29,13 +31,12 @@ export class SqliteTools { PRIMARY KEY (address), CHECK(timeloop >= 0) );`; - + await db.query(req); }); } async fetchAll(): Promise { - const rows = await this.withDB(async (db) => { let req = sql`SELECT * FROM listeners`; @@ -61,6 +62,14 @@ export class SqliteTools { }); } + async deleteListener(adr: string) { + await this.withDB(async (db) => { + let req = sql`DELETE FROM listeners + WHERE address = ${adr}`; + await db.query(req); + }); + } + async updateAll(listeners: ListenerRSSInfos[]) { await this.withDB(async (db) => { await db.tx(async (transaction) => { diff --git a/tests/index-spec.ts b/tests/index-spec.ts index 99f9bb4..3886dad 100644 --- a/tests/index-spec.ts +++ b/tests/index-spec.ts @@ -2,7 +2,7 @@ import { ListenerRss, ListenerRSSInfos } from "listener-rss"; // local lib -import { ManageListener } from "../src/"; +import { ListenerRssAggregator } from "../src/"; // Unit test import { join as joinPath } from "path"; @@ -29,14 +29,14 @@ describe("test class ManageListener", function () { it("should save the inserted objects", async function () { await withFile(async ({ path }) => { // given - const ml = new ManageListener(path); + const ml = new ListenerRssAggregator(path); await ml.load(); for (const item of dataWithoutHistory) await ml.registerListener(item); // when ml.save(); - const ml_bis = new ManageListener(path); + const ml_bis = new ListenerRssAggregator(path); await ml_bis.load(); // expect @@ -60,7 +60,7 @@ describe("test class ManageListener", function () { await withFile(async ({ path }) => { // given const clock = sinon.useFakeTimers(); - const ml = new ManageListener(path); + const ml = new ListenerRssAggregator(path); await ml.load(); for (const item of dataWithoutHistory) await ml.registerListener(item); @@ -150,7 +150,7 @@ describe("test class ManageListener", function () { await withFile(async ({ path }) => { // given const clock = sinon.useFakeTimers(); - const ml = new ManageListener(path); + const ml = new ListenerRssAggregator(path); await ml.load(); for (const item of dataWithHistory) await ml.registerListener(item); diff --git a/tests/sqlite-tools-spec.ts b/tests/sqlite-tools-spec.ts index d7ec564..e474a47 100644 --- a/tests/sqlite-tools-spec.ts +++ b/tests/sqlite-tools-spec.ts @@ -17,8 +17,8 @@ const dataWithoutHistory = dataWithHistory.map((data) => ({ ...data, lastEntriesLinks: [], })); -describe("test sqlite tools class", () => { - it("should create a .db file and fill him", async () => { +describe("test sqlite tools class", function () { + it("should create a .db file and fill him", async function () { await withFile(async ({ path }) => { // given const dbTools = new SqliteTools(path); @@ -37,7 +37,7 @@ describe("test sqlite tools class", () => { }); }); - it("should update the values of the databases", async () => { + it("should update the values of the databases", async function () { await withFile(async ({ path }) => { // given const dbTools = new SqliteTools(path); diff --git a/tsconfig.base.json b/tsconfig.base.json index d5c0441..1821562 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -4,8 +4,8 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ @@ -21,11 +21,11 @@ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + "downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */, // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ @@ -51,7 +51,7 @@ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ @@ -66,7 +66,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ - "skipLibCheck": true, /* Skip type checking of declaration files. */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 67a65b5..3d6fa19 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,15 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "declaration": true, /* Generates corresponding '.d.ts' file. */ - "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "outDir": "./build/", /* Redirect output structure to the directory. */ - "rootDir": "./src/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - }, - "files": ["./src/index.ts", "./src/manage-listener.ts", "./src/sqlite-tools.ts"], - "exclude": ["./tests/**/*"] -} \ No newline at end of file + "extends": "./tsconfig.base.json", + "compilerOptions": { + "declaration": true /* Generates corresponding '.d.ts' file. */, + "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, + "outDir": "./build/" /* Redirect output structure to the directory. */, + "rootDir": "./src/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + }, + "files": [ + "./src/index.ts", + "src/listener-rss-aggregator.ts", + "./src/sqlite-tools.ts" + ], + "exclude": ["./tests/**/*"] +}