Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 77x 77x 77x 16x 16x 16x 16x 16x 20x 13x 12x 12x 13x 10x 9x 9x 9x 9x 9x 5x 5x 5x 5x 5x 5x 22x 4x 4x 2x 10x 10x 8x 8x 8x 8x 7x 7x 7x 7x 8x 8x 8x 8x 10x 6x 8x 11x 11x 8x | import { HttpEventType, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { reaction } from 'mobx';
import { concat, defer, filter, finalize, map, Observable, of, shareReplay, switchMap, tap, toArray } from 'rxjs';
import { Ref } from '../model/ref';
import { Store } from '../store/store';
import { cacheMimeType, cacheUrlId, refCacheIds, rewriteCacheIds } from '../util/cache';
import { ProxyService } from './api/proxy.service';
import { RefService } from './api/ref.service';
/**
* Restores cache files from uploaded zips on a Ref by Ref basis.
* Since cache IDs cannot be chosen when uploading, each cache file is
* uploaded as a new cache entry and the Ref is rewritten to use the new
* cache ID. The old to new cache ID mapping is remembered so that each
* cache file is only uploaded once, even when Refs are uploaded one at a
* time. Mappings are dropped once no Ref in the upload list refers to them.
*/
@Injectable({
providedIn: 'root',
})
export class UploadCacheService {
/**
* Map of origin and old cache ID to new cache ID.
*/
private ids = new Map<string, string>();
/**
* In flight uploads, keyed by origin and old cache ID.
*/
private pending = new Map<string, Observable<string | undefined>>();
constructor(
private store: Store,
private proxy: ProxyService,
private refs: RefService,
) {
reaction(() => this.store.submit.refs, refs => this.prune(refs));
}
/**
* Upload any cache files referenced by this Ref and rewrite the Ref to use
* the new cache IDs.
*/
restore$(ref: Ref, origin: string): Observable<Ref> {
const ids = refCacheIds(ref).filter(id => {
const key = this.key(id, origin);
return this.ids.has(key) || this.pending.has(key) || this.store.submit.cacheFiles.has(id);
});
if (!ids.length) return of(ref);
return concat(...ids.map(id => defer(() => this.upload$(id, ref, origin)).pipe(
map(newId => [id, newId] as [string, string | undefined]),
))).pipe(
toArray(),
map(entries => new Map(entries.filter(([, newId]) => !!newId) as [string, string][])),
switchMap(mapping => {
const result = rewriteCacheIds(ref, mapping);
const oldId = cacheUrlId(ref.url);
if (!oldId || !mapping.has(oldId)) return of(result);
// The Ref URL was rewritten to the newly created cache Ref
return this.refs.get(result.url, origin).pipe(
map(existing => {
result.exists = true;
result.modifiedString = existing.modifiedString;
result.modified = existing.modified;
Iif (existing.plugins?.['_plugin/cache']) {
result.plugins = { ...result.plugins, '_plugin/cache': existing.plugins['_plugin/cache'] };
}
return result;
}),
);
}),
);
}
private key(id: string, origin: string) {
return origin + ' ' + id;
}
private prune(refs: Ref[]) {
const keep = new Set(refs.flatMap(refCacheIds));
for (const key of [...this.ids.keys()]) {
if (!keep.has(key.substring(key.lastIndexOf(' ') + 1))) this.ids.delete(key);
}
}
private upload$(id: string, ref: Ref, origin: string): Observable<string | undefined> {
const key = this.key(id, origin);
if (this.ids.has(key)) return of(this.ids.get(key));
Iif (this.pending.has(key)) return this.pending.get(key)!;
const zipped = this.store.submit.cacheFiles.get(id)!;
const upload$ = defer(() => zipped.async('blob')).pipe(
switchMap(blob => this.proxy.save(new File([blob], this.fileName(id, ref), { type: this.mimeType(id, ref) }), origin)),
filter(event => event.type === HttpEventType.Response),
map(event => {
const saved = (event as HttpResponse<Ref>).body!;
return cacheUrlId(saved.url) || saved.plugins?.['_plugin/cache']?.id as string | undefined;
}),
tap(newId => {
Eif (newId) this.ids.set(key, newId);
}),
finalize(() => this.pending.delete(key)),
shareReplay(1),
);
this.pending.set(key, upload$);
return upload$;
}
private fileName(id: string, ref: Ref): string {
for (const r of [ref, ...this.store.submit.refs]) {
if (cacheUrlId(r.url) === id && r.title) return r.title;
}
return id;
}
private mimeType(id: string, ref: Ref): string {
for (const r of [ref, ...this.store.submit.refs]) {
const cache = r.plugins?.['_plugin/cache'];
Iif (cache?.id === id && cache.mimeType) return cache.mimeType;
}
return cacheMimeType(id);
}
}
|