From bfcd27afb82d44505a2b7b0bfd1335ea2d8205b7 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 8 Aug 2020 21:04:00 +0800 Subject: ... --- Timeline/ClientApp/src/app/data/timeline.ts | 107 ++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) (limited to 'Timeline/ClientApp/src') diff --git a/Timeline/ClientApp/src/app/data/timeline.ts b/Timeline/ClientApp/src/app/data/timeline.ts index 477d410b..7ef7a8bb 100644 --- a/Timeline/ClientApp/src/app/data/timeline.ts +++ b/Timeline/ClientApp/src/app/data/timeline.ts @@ -1,7 +1,7 @@ import React from 'react'; import XRegExp from 'xregexp'; import { Observable, from, combineLatest } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; +import { map, switchMap, filter } from 'rxjs/operators'; import { convertError } from '../utilities/rxjs'; @@ -111,6 +111,10 @@ type TimelineData = Omit & { members: string[]; }; +type TimelinePostData = Omit & { + author: string; +}; + export class TimelineService { private getCachedTimeline( timelineName: string @@ -261,9 +265,7 @@ export class TimelineService { getHttpTimelineClient() .memberPut(timelineName, username, user.token) .then(() => { - userInfoService.getUserInfo(username).subscribe(() => { - void this.syncTimeline(timelineName); - }); + void this.syncTimeline(timelineName); }) ); } @@ -501,6 +503,103 @@ export class TimelineService { return this._postsSubscriptionHub; } + private getCachedPostData( + timelineName: string, + postId: number + ): Promise { + return dataStorage + .getItem( + `timeline.${timelineName}.post.${postId}.data` + ) + .then((data) => data?.data ?? null); + } + + private async syncPostData( + timelineName: string, + postId: number + ): Promise { + const syncStatusKey = `user.timeline.${timelineName}.post.data.${postId}`; + if (syncStatusHub.get(syncStatusKey)) return; + syncStatusHub.begin(syncStatusKey); + + const dataKey = `timeline.${timelineName}.post.${postId}.data`; + + const cache = await dataStorage.getItem(dataKey); + if (cache == null) { + try { + const data = await getHttpTimelineClient().getPostData( + timelineName, + postId + ); + await dataStorage.setItem(dataKey, data); + syncStatusHub.end(syncStatusKey); + this._postDataHub + .getLine({ timelineName, postId }) + ?.next({ data: data.data, type: 'synced' }); + } catch (e) { + syncStatusHub.end(syncStatusKey); + this._postDataHub + .getLine({ timelineName, postId }) + ?.next({ type: 'offline' }); + if (!(e instanceof HttpNetworkError)) { + throw e; + } + } + } else { + try { + const res = await getHttpTimelineClient().getPostData( + timelineName, + postId, + cache.etag + ); + if (res instanceof NotModified) { + syncStatusHub.end(syncStatusKey); + this._postDataHub + .getLine({ timelineName, postId }) + ?.next({ data: cache.data, type: 'synced' }); + } else { + const avatar = res; + await dataStorage.setItem(dataKey, avatar); + syncStatusHub.end(syncStatusKey); + this._postDataHub + .getLine({ timelineName, postId }) + ?.next({ data: avatar.data, type: 'synced' }); + } + } catch (e) { + syncStatusHub.end(syncStatusKey); + this._postDataHub + .getLine({ timelineName, postId }) + ?.next({ data: cache.data, type: 'offline' }); + if (!(e instanceof HttpNetworkError)) { + throw e; + } + } + } + } + + private _postDataHub = new SubscriptionHub< + { timelineName: string; postId: number }, + | { data: Blob; type: 'cache' | 'synced' | 'offline' } + | { data?: undefined; type: 'notexist' | 'offline' } + >({ + keyToString: (key) => `${key.timelineName}.${key.postId}`, + setup: (key, line) => { + void this.getCachedPostData(key.timelineName, key.postId).then((data) => { + if (data != null) { + line.next({ data: data, type: 'cache' }); + } + return this.syncPostData(key.timelineName, key.postId); + }); + }, + }); + + getPostData$(timelineName: string, postId: number): Observable { + return this._postDataHub.getObservable({ timelineName, postId }).pipe( + map((state) => state.data), + filter((blob): blob is Blob => blob != null) + ); + } + createPost( timelineName: string, request: TimelineCreatePostRequest -- cgit v1.2.3