From 0db6959fbedd5dbb8ec41b53d3427b0e8a5973a5 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 9 Aug 2020 22:09:00 +0800 Subject: Rename SubscriptionHub to DataHub. --- Timeline/ClientApp/src/app/data/DataHub.ts | 181 ++++++++++++++++++++ Timeline/ClientApp/src/app/data/SubscriptionHub.ts | 187 --------------------- Timeline/ClientApp/src/app/data/timeline.ts | 8 +- Timeline/ClientApp/src/app/data/user.ts | 6 +- 4 files changed, 188 insertions(+), 194 deletions(-) create mode 100644 Timeline/ClientApp/src/app/data/DataHub.ts delete mode 100644 Timeline/ClientApp/src/app/data/SubscriptionHub.ts (limited to 'Timeline/ClientApp/src') diff --git a/Timeline/ClientApp/src/app/data/DataHub.ts b/Timeline/ClientApp/src/app/data/DataHub.ts new file mode 100644 index 00000000..982aacba --- /dev/null +++ b/Timeline/ClientApp/src/app/data/DataHub.ts @@ -0,0 +1,181 @@ +import { pull } from 'lodash'; +import { Observable } from 'rxjs'; + +export type Subscriber = (data: TData) => void; + +export interface IDataLine { + readonly value: undefined | TData; + next(value: TData): void; + readonly isSyncing: boolean; + beginSync(): void; + endSync(): void; + endSyncAndNext(value: TData): void; +} + +export class DataLine implements IDataLine { + private _current: TData | undefined = undefined; + + private _syncing = false; + + private _observers: Subscriber[] = []; + + constructor( + private config?: { destroyable?: (value: TData | undefined) => boolean } + ) {} + + subscribe(subscriber: Subscriber): void { + this._observers.push(subscriber); + if (this._current !== undefined) { + subscriber(this._current); + } + } + + unsubscribe(subscriber: Subscriber): void { + if (!this._observers.includes(subscriber)) return; + pull(this._observers, subscriber); + } + + get value(): TData | undefined { + return this._current; + } + + next(value: TData): void { + this._current = value; + this._observers.forEach((observer) => observer(value)); + } + + get isSyncing(): boolean { + return this._syncing; + } + + beginSync(): void { + if (!this._syncing) { + this._syncing = true; + } + } + + endSync(): void { + if (this._syncing) { + this._syncing = false; + } + } + + get destroyable(): boolean { + const customDestroyable = this.config?.destroyable; + + return ( + this._observers.length === 0 && + (customDestroyable != null ? customDestroyable(this._current) : true) + ); + } + + endSyncAndNext(value: TData): void { + this.endSync(); + this.next(value); + } +} + +export class DataHub { + private keyToString: (key: TKey) => string; + private setup?: (key: TKey, line: IDataLine) => (() => void) | void; + private destroyable?: (key: TKey, value: TData | undefined) => boolean; + + private readonly subscriptionLineMap = new Map>(); + + 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(config?: { + keyToString?: (key: TKey) => string; + setup?: (key: TKey, line: IDataLine) => void; + destroyable?: (key: TKey, value: TData | undefined) => boolean; + }) { + this.keyToString = + config?.keyToString ?? + ((value): string => { + if (typeof value === 'string') return value; + else + throw new Error( + 'Default keyToString function only pass string value.' + ); + }); + + this.setup = config?.setup; + this.destroyable = config?.destroyable; + } + + private cleanLines(): void { + const toDelete: string[] = []; + for (const [key, line] of this.subscriptionLineMap.entries()) { + if (line.destroyable) { + 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, useSetup = true): DataLine { + const keyString = this.keyToString(key); + const { setup, destroyable } = this; + const newLine = new DataLine({ + destroyable: + destroyable != null ? (value) => destroyable(key, value) : undefined, + }); + this.subscriptionLineMap.set(keyString, newLine); + if (useSetup) { + setup?.(key, newLine); + } + if (this.subscriptionLineMap.size === 1) { + this.cleanTimerId = window.setInterval(this.cleanLines.bind(this), 20000); + } + return newLine; + } + + subscribe(key: TKey, subscriber: Subscriber): void { + const keyString = this.keyToString(key); + const line = + this.subscriptionLineMap.get(keyString) ?? this.createLine(key); + return line.subscribe(subscriber); + } + + unsubscribe(key: TKey, subscriber: Subscriber): void { + const keyString = this.keyToString(key); + const line = this.subscriptionLineMap.get(keyString); + return line?.unsubscribe(subscriber); + } + + getObservable(key: TKey): Observable { + return new Observable((observer) => { + const f = (data: TData): void => { + observer.next(data); + }; + + this.subscribe(key, f); + return () => { + this.unsubscribe(key, f); + }; + }); + } + + getLine(key: TKey): IDataLine | null { + const keyString = this.keyToString(key); + return this.subscriptionLineMap.get(keyString) ?? null; + } + + getLineOrCreateWithoutSetup(key: TKey): IDataLine { + const keyString = this.keyToString(key); + return ( + this.subscriptionLineMap.get(keyString) ?? this.createLine(key, false) + ); + } +} diff --git a/Timeline/ClientApp/src/app/data/SubscriptionHub.ts b/Timeline/ClientApp/src/app/data/SubscriptionHub.ts deleted file mode 100644 index e19c547c..00000000 --- a/Timeline/ClientApp/src/app/data/SubscriptionHub.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { pull } from 'lodash'; -import { Observable } from 'rxjs'; - -export type Subscriber = (data: TData) => void; - -export interface ISubscriptionLine { - readonly value: undefined | TData; - next(value: TData): void; - readonly isSyncing: boolean; - beginSync(): void; - endSync(): void; - endSyncAndNext(value: TData): void; -} - -export class SubscriptionLine implements ISubscriptionLine { - private _current: TData | undefined = undefined; - - private _syncing = false; - - private _observers: Subscriber[] = []; - - constructor( - private config?: { destroyable?: (value: TData | undefined) => boolean } - ) {} - - subscribe(subscriber: Subscriber): void { - this._observers.push(subscriber); - if (this._current !== undefined) { - subscriber(this._current); - } - } - - unsubscribe(subscriber: Subscriber): void { - if (!this._observers.includes(subscriber)) return; - pull(this._observers, subscriber); - } - - get value(): TData | undefined { - return this._current; - } - - next(value: TData): void { - this._current = value; - this._observers.forEach((observer) => observer(value)); - } - - get isSyncing(): boolean { - return this._syncing; - } - - beginSync(): void { - if (!this._syncing) { - this._syncing = true; - } - } - - endSync(): void { - if (this._syncing) { - this._syncing = false; - } - } - - get destroyable(): boolean { - const customDestroyable = this.config?.destroyable; - - return ( - this._observers.length === 0 && - (customDestroyable != null ? customDestroyable(this._current) : true) - ); - } - - endSyncAndNext(value: TData): void { - this.endSync(); - this.next(value); - } -} - -export class SubscriptionHub { - private keyToString: (key: TKey) => string; - private setup?: ( - key: TKey, - line: ISubscriptionLine - ) => (() => void) | void; - private destroyable?: (key: TKey, value: TData | undefined) => boolean; - - private readonly subscriptionLineMap = new Map< - string, - SubscriptionLine - >(); - - 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(config?: { - keyToString?: (key: TKey) => string; - setup?: (key: TKey, line: ISubscriptionLine) => void; - destroyable?: (key: TKey, value: TData | undefined) => boolean; - }) { - this.keyToString = - config?.keyToString ?? - ((value): string => { - if (typeof value === 'string') return value; - else - throw new Error( - 'Default keyToString function only pass string value.' - ); - }); - - this.setup = config?.setup; - this.destroyable = config?.destroyable; - } - - private cleanLines(): void { - const toDelete: string[] = []; - for (const [key, line] of this.subscriptionLineMap.entries()) { - if (line.destroyable) { - 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, useSetup = true): SubscriptionLine { - const keyString = this.keyToString(key); - const { setup, destroyable } = this; - const newLine = new SubscriptionLine({ - destroyable: - destroyable != null ? (value) => destroyable(key, value) : undefined, - }); - this.subscriptionLineMap.set(keyString, newLine); - if (useSetup) { - setup?.(key, newLine); - } - if (this.subscriptionLineMap.size === 1) { - this.cleanTimerId = window.setInterval(this.cleanLines.bind(this), 20000); - } - return newLine; - } - - subscribe(key: TKey, subscriber: Subscriber): void { - const keyString = this.keyToString(key); - const line = - this.subscriptionLineMap.get(keyString) ?? this.createLine(key); - return line.subscribe(subscriber); - } - - unsubscribe(key: TKey, subscriber: Subscriber): void { - const keyString = this.keyToString(key); - const line = this.subscriptionLineMap.get(keyString); - return line?.unsubscribe(subscriber); - } - - getObservable(key: TKey): Observable { - return new Observable((observer) => { - const f = (data: TData): void => { - observer.next(data); - }; - - this.subscribe(key, f); - return () => { - this.unsubscribe(key, f); - }; - }); - } - - getLine(key: TKey): ISubscriptionLine | null { - const keyString = this.keyToString(key); - return this.subscriptionLineMap.get(keyString) ?? null; - } - - getLineOrCreateWithoutSetup(key: TKey): ISubscriptionLine { - const keyString = this.keyToString(key); - return ( - this.subscriptionLineMap.get(keyString) ?? this.createLine(key, false) - ); - } -} diff --git a/Timeline/ClientApp/src/app/data/timeline.ts b/Timeline/ClientApp/src/app/data/timeline.ts index aacb5f29..81cf20e9 100644 --- a/Timeline/ClientApp/src/app/data/timeline.ts +++ b/Timeline/ClientApp/src/app/data/timeline.ts @@ -7,7 +7,7 @@ import { uniqBy } from 'lodash'; import { convertError } from '../utilities/rxjs'; import { dataStorage, throwIfNotNetworkError } from './common'; -import { SubscriptionHub } from './SubscriptionHub'; +import { DataHub } from './DataHub'; import { UserAuthInfo, checkLogin, userService, userInfoService } from './user'; @@ -165,7 +165,7 @@ export class TimelineService { } } - private _timelineHub = new SubscriptionHub< + private _timelineHub = new DataHub< string, | { type: 'cache'; @@ -343,7 +343,7 @@ export class TimelineService { } } - private _postsHub = new SubscriptionHub< + private _postsHub = new DataHub< string, { type: 'cache' | 'offline' | 'synced' | 'forbid' | 'notexist'; @@ -476,7 +476,7 @@ export class TimelineService { } } - private _postDataHub = new SubscriptionHub< + private _postDataHub = new DataHub< { timelineName: string; postId: number }, | { data: Blob; type: 'cache' | 'synced' | 'offline' } | { data?: undefined; type: 'notexist' | 'offline' } diff --git a/Timeline/ClientApp/src/app/data/user.ts b/Timeline/ClientApp/src/app/data/user.ts index d19a6323..419cff18 100644 --- a/Timeline/ClientApp/src/app/data/user.ts +++ b/Timeline/ClientApp/src/app/data/user.ts @@ -7,7 +7,7 @@ import { convertError } from '../utilities/rxjs'; import { pushAlert } from '../common/alert-service'; import { dataStorage, throwIfNotNetworkError } from './common'; -import { SubscriptionHub } from './SubscriptionHub'; +import { DataHub } from './DataHub'; import { HttpNetworkError, BlobWithEtag, NotModified } from '../http/common'; import { @@ -271,7 +271,7 @@ export class UserInfoService { } } - private _userHub = new SubscriptionHub< + private _userHub = new DataHub< string, | { user: User; type: 'cache' | 'synced' | 'offline' } | { user?: undefined; type: 'notexist' | 'offline' } @@ -336,7 +336,7 @@ export class UserInfoService { } } - private _avatarHub = new SubscriptionHub< + private _avatarHub = new DataHub< string, | { data: Blob; type: 'cache' | 'synced' | 'offline' } | { data?: undefined; type: 'notexist' | 'offline' } -- cgit v1.2.3