TeaWeb/shared/js/file/ImageCache.ts

175 lines
5.1 KiB
TypeScript
Raw Normal View History

import {tr} from "../i18n/localize";
import {LogCategory, logDebug, logWarn} from "tc-shared/log";
2020-05-03 20:59:18 +02:00
export enum ImageType {
UNKNOWN,
BITMAP,
PNG,
GIF,
SVG,
JPEG
}
2020-09-26 01:22:05 +02:00
export function imageType2MediaType(type: ImageType, file?: boolean) {
2020-05-03 20:59:18 +02:00
switch (type) {
case ImageType.BITMAP:
return "bmp";
case ImageType.GIF:
return "gif";
case ImageType.SVG:
return file ? "svg" : "svg+xml";
case ImageType.JPEG:
return "jpeg";
case ImageType.UNKNOWN:
case ImageType.PNG:
default:
return "png";
}
}
2020-09-26 01:22:05 +02:00
export function responseImageType(encoded_data: string | ArrayBuffer, base64_encoded?: boolean) {
2020-05-03 20:59:18 +02:00
const ab2str10 = () => {
const buf = new Uint8Array(encoded_data as ArrayBuffer);
if(buf.byteLength < 10)
return "";
let result = "";
for(let index = 0; index < 10; index++)
result += String.fromCharCode(buf[index]);
return result;
};
const bin = typeof(encoded_data) === "string" ? ((typeof(base64_encoded) === "undefined" || base64_encoded) ? atob(encoded_data) : encoded_data) : ab2str10();
if(bin.length < 10) return ImageType.UNKNOWN;
if(bin[0] == String.fromCharCode(66) && bin[1] == String.fromCharCode(77)) {
return ImageType.BITMAP;
} else if(bin.substr(0, 8) == "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a") {
return ImageType.PNG;
} else if(bin.substr(0, 4) == "\x47\x49\x46\x38" && (bin[4] == '\x37' || bin[4] == '\x39') && bin[5] == '\x61') {
return ImageType.GIF;
} else if(bin[0] == '\x3c') {
return ImageType.SVG;
} else if(bin[0] == '\xFF' && bin[1] == '\xd8') {
return ImageType.JPEG;
}
return ImageType.UNKNOWN;
}
2020-12-05 16:19:37 +01:00
export type ImageCacheState = {
state: "loaded",
instance: Cache
} | {
state: "errored",
reason: string
} | {
state: "unloaded"
}
2020-05-03 20:59:18 +02:00
export class ImageCache {
2020-12-05 16:19:37 +01:00
readonly cacheName: string;
2020-05-03 20:59:18 +02:00
2020-12-05 16:19:37 +01:00
private state: ImageCacheState;
2020-05-03 20:59:18 +02:00
2020-12-05 16:19:37 +01:00
private constructor(name: string) {
this.cacheName = name;
this.state = { state: "unloaded" };
2020-05-03 20:59:18 +02:00
}
2020-12-05 16:19:37 +01:00
public static async load(cacheName: string) : Promise<ImageCache> {
const cache = new ImageCache(cacheName);
2020-12-08 14:42:41 +01:00
await cache.initialize();
2020-12-05 16:19:37 +01:00
return cache;
}
private async initialize() {
if(!window.caches) {
this.state = { "state": "errored", reason: tr("Caches are not enabled by the user") };
return;
}
try {
const instance = await window.caches.open(this.cacheName);
this.state = { state: "loaded", instance: instance };
} catch (error) {
logDebug(LogCategory.GENERAL, tr("Failed to open image cache %s: %o"), this.cacheName, error);
this.state = { "state": "errored", reason: tr("Failed to open the cache") };
}
}
private getCacheInstance() : Cache | undefined {
return this.state.state === "loaded" ? this.state.instance : undefined;
}
isPersistent() {
return this.state.state === "loaded";
}
2020-05-03 20:59:18 +02:00
async reset() {
2020-12-05 16:19:37 +01:00
if(!window.caches) {
/* Caches are disabled by the user */
2020-05-03 20:59:18 +02:00
return;
2020-12-05 16:19:37 +01:00
}
2020-05-03 20:59:18 +02:00
try {
2020-12-05 16:19:37 +01:00
await caches.delete(this.cacheName);
2020-05-03 20:59:18 +02:00
} catch(error) {
throw "Failed to delete cache: " + error;
}
2020-12-05 16:19:37 +01:00
2020-05-03 20:59:18 +02:00
try {
2020-12-05 16:19:37 +01:00
await this.initialize();
2020-05-03 20:59:18 +02:00
} catch(error) {
throw "Failed to reinitialize cache!";
}
}
2020-09-26 11:17:55 +02:00
async cleanup(maxAge: number) {
2020-05-03 20:59:18 +02:00
/* FIXME: TODO */
}
2020-09-26 11:17:55 +02:00
async resolveCached(key: string, maxAge?: number) : Promise<Response | undefined> {
2020-12-05 16:19:37 +01:00
const cacheInstance = this.getCacheInstance();
if(!cacheInstance) { return undefined; }
2020-05-03 20:59:18 +02:00
2020-12-05 16:19:37 +01:00
maxAge = typeof(maxAge) === "number" ? maxAge : -1;
const cachedResponse = await cacheInstance.match("https://_local_cache/cache_request_" + key);
2020-09-26 11:17:55 +02:00
if(!cachedResponse)
2020-05-03 20:59:18 +02:00
return undefined;
/* FIXME: Max age */
2020-09-26 11:17:55 +02:00
return cachedResponse;
2020-05-03 20:59:18 +02:00
}
2020-09-26 01:22:05 +02:00
async putCache(key: string, value: Response, type?: string, headers?: {[key: string]:string}) {
2020-12-05 16:19:37 +01:00
const cacheInstance = this.getCacheInstance();
if(!cacheInstance) { return; }
2020-05-03 20:59:18 +02:00
const new_headers = new Headers();
for(const key of value.headers.keys())
new_headers.set(key, value.headers.get(key));
if(type)
new_headers.set("Content-type", type);
for(const key of Object.keys(headers || {}))
new_headers.set(key, headers[key]);
2020-12-05 16:19:37 +01:00
await cacheInstance.put("https://_local_cache/cache_request_" + key, new Response(value.body, {
2020-05-03 20:59:18 +02:00
headers: new_headers
}));
}
async delete(key: string) {
2020-12-05 16:19:37 +01:00
const cacheInstance = this.getCacheInstance();
if(!cacheInstance) { return; }
const flag = await cacheInstance.delete("https://_local_cache/cache_request_" + key, {
2020-05-03 20:59:18 +02:00
ignoreVary: true,
ignoreMethod: true,
ignoreSearch: true
});
if(!flag) {
logWarn(LogCategory.FILE_TRANSFER, tr("Failed to delete key %s from cache!"), key);
2020-05-03 20:59:18 +02:00
}
}
}