163 lines
4.0 KiB
TypeScript
163 lines
4.0 KiB
TypeScript
"use strict";
|
|
// License: MIT
|
|
|
|
import { downloads, CHROME } from "./browser";
|
|
import { EventEmitter } from "../uikit/lib/events";
|
|
import { PromiseSerializer } from "./pserializer";
|
|
|
|
const VERSION = 1;
|
|
const STORE = "iconcache";
|
|
// eslint-disable-next-line no-magic-numbers
|
|
const CACHE_SIZES = CHROME ? [16, 32] : [16, 32, 64, 127];
|
|
|
|
const BLACKLISTED = Object.freeze(new Set([
|
|
"",
|
|
"ext",
|
|
"ico",
|
|
"pif",
|
|
"scr",
|
|
"ani",
|
|
"cur",
|
|
"ttf",
|
|
"otf",
|
|
"woff",
|
|
"woff2",
|
|
"cpl",
|
|
"desktop",
|
|
"app",
|
|
]));
|
|
|
|
async function getIcon(size: number, manId: number) {
|
|
const raw = await downloads.getFileIcon(manId, {size});
|
|
const icon = new URL(raw);
|
|
if (icon.protocol === "data:") {
|
|
const res = await fetch(icon.toString());
|
|
const blob = await res.blob();
|
|
return {size, icon: blob};
|
|
}
|
|
return {size, icon};
|
|
}
|
|
|
|
const SYNONYMS = Object.freeze(new Map<string, string>([
|
|
["jpe", "jpg"],
|
|
["jpeg", "jpg"],
|
|
["jfif", "jpg"],
|
|
["mpe", "mpg"],
|
|
["mpeg", "mpg"],
|
|
["m4v", "mp4"],
|
|
]));
|
|
|
|
export const IconCache = new class IconCache extends EventEmitter {
|
|
private db: Promise<IDBDatabase>;
|
|
|
|
private cache: Map<string, string>;
|
|
|
|
constructor() {
|
|
super();
|
|
this.db = this.init();
|
|
this.cache = new Map();
|
|
this.get = PromiseSerializer.wrapNew(8, this, this.get);
|
|
this.set = PromiseSerializer.wrapNew(1, this, this.set);
|
|
}
|
|
|
|
private async init() {
|
|
return await new Promise<IDBDatabase>((resolve, reject) => {
|
|
const req = indexedDB.open(STORE, VERSION);
|
|
req.onupgradeneeded = evt => {
|
|
const db = req.result;
|
|
switch (evt.oldVersion) {
|
|
case 0: {
|
|
db.createObjectStore(STORE);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
req.onerror = ex => reject(ex);
|
|
req.onsuccess = () => {
|
|
resolve(req.result);
|
|
};
|
|
});
|
|
}
|
|
|
|
private normalize(ext: string) {
|
|
ext = ext.toLocaleLowerCase();
|
|
return SYNONYMS.get(ext) || ext;
|
|
}
|
|
|
|
// eslint-disable-next-line no-magic-numbers
|
|
async get(ext: string, size = 16) {
|
|
ext = this.normalize(ext);
|
|
if (BLACKLISTED.has(ext)) {
|
|
return undefined;
|
|
}
|
|
const sext = `${ext}-${size}`;
|
|
let rv = this.cache.get(sext);
|
|
if (rv) {
|
|
return rv;
|
|
}
|
|
const db = await this.db;
|
|
rv = this.cache.get(sext);
|
|
if (rv) {
|
|
return rv;
|
|
}
|
|
return await new Promise<string | undefined>(resolve => {
|
|
const trans = db.transaction(STORE, "readonly");
|
|
trans.onerror = () => resolve(undefined);
|
|
const store = trans.objectStore(STORE);
|
|
const req = store.get(sext);
|
|
req.onerror = () => resolve(undefined);
|
|
req.onsuccess = () => {
|
|
const rv = this.cache.get(sext);
|
|
if (rv) {
|
|
resolve(rv);
|
|
return;
|
|
}
|
|
let {result} = req;
|
|
if (!result) {
|
|
resolve(undefined);
|
|
return;
|
|
}
|
|
if (typeof req.result !== "string") {
|
|
result = URL.createObjectURL(result).toString();
|
|
}
|
|
this.cache.set(sext, result);
|
|
this.cache.set(ext, "");
|
|
resolve(result);
|
|
};
|
|
});
|
|
}
|
|
|
|
async set(ext: string, manId: number) {
|
|
ext = this.normalize(ext);
|
|
if (BLACKLISTED.has(ext)) {
|
|
return;
|
|
}
|
|
if (this.cache.has(ext)) {
|
|
// already processed in this session
|
|
return;
|
|
}
|
|
// eslint-disable-next-line no-magic-numbers
|
|
const urls = await Promise.all(CACHE_SIZES.map(
|
|
size => getIcon(size, manId)));
|
|
if (this.cache.has(ext)) {
|
|
// already processed in this session
|
|
return;
|
|
}
|
|
for (const {size, icon} of urls) {
|
|
this.cache.set(`${ext}-${size}`, URL.createObjectURL(icon));
|
|
}
|
|
this.cache.set(ext, "");
|
|
const db = await this.db;
|
|
await new Promise((resolve, reject) => {
|
|
const trans = db.transaction(STORE, "readwrite");
|
|
trans.onerror = reject;
|
|
trans.oncomplete = resolve;
|
|
const store = trans.objectStore(STORE);
|
|
for (const {size, icon} of urls) {
|
|
store.put(icon, `${ext}-${size}`);
|
|
}
|
|
});
|
|
this.emit("cached", ext);
|
|
}
|
|
}();
|