diff options
Diffstat (limited to 'FrontEnd/src/app/services/DataHub2.ts')
-rw-r--r-- | FrontEnd/src/app/services/DataHub2.ts | 191 |
1 files changed, 0 insertions, 191 deletions
diff --git a/FrontEnd/src/app/services/DataHub2.ts b/FrontEnd/src/app/services/DataHub2.ts deleted file mode 100644 index f0fb724b..00000000 --- a/FrontEnd/src/app/services/DataHub2.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { Observable } from "rxjs"; - -export type DataStatus = "syncing" | "synced" | "offline"; - -export function mergeDataStatus(statusList: DataStatus[]): DataStatus { - if (statusList.includes("offline")) { - return "offline"; - } else if (statusList.includes("syncing")) { - return "syncing"; - } else { - return "synced"; - } -} - -export type Subscriber<TData> = (data: TData) => void; - -export interface DataAndStatus<TData> { - data: TData | null; - status: DataStatus; -} - -export class DataLine2<TData> { - constructor( - private config: { - saveData: (data: TData) => Promise<void>; - getSavedData: () => Promise<TData | null>; - // return null for offline - fetchData: (savedData: TData | null) => Promise<TData | null>; - } - ) {} - - private _current: DataAndStatus<TData> | null = null; - private _observers: Subscriber<DataAndStatus<TData>>[] = []; - - private _syncPromise: Promise<void> | null = null; - - get currentData(): DataAndStatus<TData> | null { - return this._current; - } - - get isDestroyable(): boolean { - const { _observers, currentData } = this; - return ( - _observers.length === 0 && - (currentData == null || currentData.status !== "syncing") - ); - } - - private next(data: DataAndStatus<TData>): void { - this._current = data; - this._observers.forEach((o) => o(data)); - } - - subscribe(subsriber: Subscriber<DataAndStatus<TData>>): void { - void this.sync(); // TODO: Should I sync at this point or let the user sync explicitly. - this._observers.push(subsriber); - const { currentData } = this; - if (currentData != null) { - subsriber(currentData); - } - } - - unsubscribe(subsriber: Subscriber<DataAndStatus<TData>>): void { - const index = this._observers.indexOf(subsriber); - if (index > -1) this._observers.splice(index, 1); - } - - getObservalble(): Observable<DataAndStatus<TData>> { - return new Observable<DataAndStatus<TData>>((observer) => { - const f = (data: DataAndStatus<TData>): void => { - observer.next(data); - }; - this.subscribe(f); - - return () => { - this.unsubscribe(f); - }; - }); - } - - private syncWithAction(action: () => Promise<void>): Promise<void> { - if (this._syncPromise != null) return this._syncPromise; - this._syncPromise = action().then(() => { - this._syncPromise = null; - }); - return this._syncPromise; - } - - sync(): Promise<void> { - return this.syncWithAction(this.doSync.bind(this)); - } - - private async doSync(): Promise<void> { - const { currentData } = this; - this.next({ data: currentData?.data ?? null, status: "syncing" }); - const savedData = await this.config.getSavedData(); - if (currentData == null && savedData != null) { - this.next({ data: savedData, status: "syncing" }); - } - const data = await this.config.fetchData(savedData); - if (data == null) { - this.next({ - data: savedData, - status: "offline", - }); - } else { - await this.config.saveData(data); - this.next({ data: data, status: "synced" }); - } - } - - save(data: TData): Promise<void> { - return this.syncWithAction(this.doSave.bind(this, data)); - } - - private async doSave(data: TData): Promise<void> { - await this.config.saveData(data); - this.next({ data: data, status: "synced" }); - } - - getSavedData(): Promise<TData | null> { - return this.config.getSavedData(); - } -} - -export class DataHub2<TKey, TData> { - private readonly subscriptionLineMap = new Map<string, DataLine2<TData>>(); - - private keyToString: (key: TKey) => string; - - private cleanTimerId = 0; - - // setup is called after creating line and if it returns a function as destroyer, then when the line is destroyed the destroyer will be called. - constructor( - private config: { - saveData: (key: TKey, data: TData) => Promise<void>; - getSavedData: (key: TKey) => Promise<TData | null>; - fetchData: (key: TKey, savedData: TData | null) => Promise<TData | null>; - keyToString?: (key: TKey) => string; - } - ) { - this.keyToString = - config.keyToString ?? - ((value): string => { - if (typeof value === "string") return value; - else - throw new Error( - "Default keyToString function only pass string value." - ); - }); - } - - private cleanLines(): void { - const toDelete: string[] = []; - for (const [key, line] of this.subscriptionLineMap.entries()) { - if (line.isDestroyable) { - toDelete.push(key); - } - } - - if (toDelete.length === 0) return; - - for (const key of toDelete) { - this.subscriptionLineMap.delete(key); - } - - if (this.subscriptionLineMap.size === 0) { - window.clearInterval(this.cleanTimerId); - this.cleanTimerId = 0; - } - } - - private createLine(key: TKey): DataLine2<TData> { - const keyString = this.keyToString(key); - const newLine: DataLine2<TData> = new DataLine2<TData>({ - saveData: (data) => this.config.saveData(key, data), - getSavedData: () => this.config.getSavedData(key), - fetchData: (savedData) => this.config.fetchData(key, savedData), - }); - this.subscriptionLineMap.set(keyString, newLine); - if (this.subscriptionLineMap.size === 1) { - this.cleanTimerId = window.setInterval(this.cleanLines.bind(this), 20000); - } - return newLine; - } - - getLine(key: TKey): DataLine2<TData> { - const keyString = this.keyToString(key); - return this.subscriptionLineMap.get(keyString) ?? this.createLine(key); - } -} |