diff options
| author | crupest <crupest@outlook.com> | 2020-08-08 21:04:00 +0800 | 
|---|---|---|
| committer | crupest <crupest@outlook.com> | 2020-08-08 21:04:00 +0800 | 
| commit | bfcd27afb82d44505a2b7b0bfd1335ea2d8205b7 (patch) | |
| tree | 4fffd94a3241800e88a76cb82527baaf98045f3d /Timeline/ClientApp/src/app/data | |
| parent | b4950b4b9d5fbfe0dbef2b1706bf322f737b877e (diff) | |
| download | timeline-bfcd27afb82d44505a2b7b0bfd1335ea2d8205b7.tar.gz timeline-bfcd27afb82d44505a2b7b0bfd1335ea2d8205b7.tar.bz2 timeline-bfcd27afb82d44505a2b7b0bfd1335ea2d8205b7.zip  | |
...
Diffstat (limited to 'Timeline/ClientApp/src/app/data')
| -rw-r--r-- | Timeline/ClientApp/src/app/data/timeline.ts | 107 | 
1 files changed, 103 insertions, 4 deletions
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<HttpTimelineInfo, 'owner' | 'members'> & {    members: string[];
  };
 +type TimelinePostData = Omit<HttpTimelinePostInfo, 'author'> & {
 +  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<Blob | null> {
 +    return dataStorage
 +      .getItem<BlobWithEtag | null>(
 +        `timeline.${timelineName}.post.${postId}.data`
 +      )
 +      .then((data) => data?.data ?? null);
 +  }
 +
 +  private async syncPostData(
 +    timelineName: string,
 +    postId: number
 +  ): Promise<void> {
 +    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<BlobWithEtag | null>(dataKey);
 +    if (cache == null) {
 +      try {
 +        const data = await getHttpTimelineClient().getPostData(
 +          timelineName,
 +          postId
 +        );
 +        await dataStorage.setItem<BlobWithEtag>(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<BlobWithEtag>(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<Blob> {
 +    return this._postDataHub.getObservable({ timelineName, postId }).pipe(
 +      map((state) => state.data),
 +      filter((blob): blob is Blob => blob != null)
 +    );
 +  }
 +
    createPost(
      timelineName: string,
      request: TimelineCreatePostRequest
  | 
