This commit is contained in:
2023-06-12 12:35:59 +08:00
parent f16264dfbc
commit 23eb6ca71c
10 changed files with 269 additions and 13 deletions

View File

@@ -2,7 +2,7 @@
"importMap": "./import_map.json",
"tasks": {
"cache": "deno cache main.ts server-dev.ts",
"server-dev": "deno run -A --unstable --watch=static/,routes/,translation/ server-dev.ts",
"server-dev": "deno run -A --unstable \"--watch=static/*.css,static/*.ts,static/*/,routes/,translation/\" server-dev.ts",
"test": "deno test --allow-read=./ --allow-net --allow-write=./ --allow-run=tasklist.exe --unstable",
"run": "deno run --allow-read=./ --allow-write=./ --allow-run=tasklist.exe --allow-env=DENO_DEPLOYMENT_ID --allow-net --unstable",
"compile": "deno compile --allow-read=./ --allow-write=./ --allow-run=tasklist.exe --allow-env=DENO_DEPLOYMENT_ID --allow-net --unstable",
@@ -11,7 +11,7 @@
},
"fmt": {
"indentWidth": 4,
"exclude": ["config.json"]
"exclude": ["config.json", "static/sw.js", "static/sw.meta.json"]
},
"compilerOptions": {
"jsx": "react-jsx",

12
deno.lock generated
View File

@@ -121,6 +121,7 @@
"https://deno.land/x/[email protected]/mod.js": "4f4e61964a551d9c0baf5bb19e973cf631cf8c66ddaf01e70070f8a100fc938c",
"https://deno.land/x/[email protected]/wasm.d.ts": "dc279a3a46f084484453e617c0cabcd5b8bd1920c0e562e4ea02dfc828c8f968",
"https://deno.land/x/[email protected]/wasm.js": "4030e7b50941ec6e06704c6b5f1f6416cc0f7f35f63daf63f184b728bea79a30",
"https://deno.land/x/[email protected]/mod.js": "ef6f332d1d96e5aee3eb12e9e9eccc64d9df4b500fbb1346bac6b8263fafb6c7",
"https://deno.land/x/[email protected]/dev.ts": "a66c7d64be35bcd6a8e12eec9c27ae335044c70363a241f2e36ee776db468622",
"https://deno.land/x/[email protected]/plugins/twind.ts": "c0570d6010e29ba24ee5f43b9d3f1fe735f7fac76d9a3e680c9896373d669876",
"https://deno.land/x/[email protected]/plugins/twind/shared.ts": "023e0ffcd66668753b5049edab0de46e6d66194fb6026c679034b9bbf04ad6f3",
@@ -198,6 +199,7 @@
"https://esm.sh/*[email protected]": "88ec8d8706b6a3f1e0fdad3862a2690dcd9b350d87bdc8e7bd0e27fbc0f7d29e",
"https://esm.sh/[email protected]/": "16d82ee0a75451f75b42d9a20db4da0ccae7ccc8cc09a41c73b4488aba010b94",
"https://esm.sh/gh/SortableJS/[email protected]/Sortable.min.js": "0a3e3cf471bf4566d7a3f9823b1d0d3b2bd075404e93419cd4074bd23310a9b0",
"https://esm.sh/[email protected]": "07fb48e0e08189de5f370ab8a1fa9d4dfd1cdb31a1843fba7e6f30d1c28575cd",
"https://esm.sh/[email protected]/Button": "bc60923d511c6e2e33a7064339b3e643a9c15e3ef232ab063ef570af2ef83dc8",
"https://esm.sh/[email protected]/Checkbox": "bf34f5cd8c6d015916d854d91aab2caf115463e97be9a461f8dd3370ea11a49c",
"https://esm.sh/[email protected]/Dialog": "b0ff8da9c770456748f7e065fecda2fc90f5364ea66cae75ff5f51d57f6a87eb",
@@ -320,7 +322,17 @@
"https://esm.sh/v124/[email protected]/deno/twind.mjs": "ac4bf729653ee66349a518ee4949f22e84b7f626e8f74de1066e798a7c1ca12a",
"https://esm.sh/v124/[email protected]/sheets/sheets.d.ts": "9cd4663d180023e49d9379777c8926347f3f79bf3bee546e7e5be1524fe55e70",
"https://esm.sh/v124/[email protected]/twind.d.ts": "48c49da7d770f1236ec8a9397af053e6fb5a2bedacf431f2189eeecc1468c01b",
"https://esm.sh/v125/@stablelib/[email protected]/denonext/binary.mjs": "3dba03d945d82efa1771daa39193e920171562b0c64868c505bd3e68c14f79f8",
"https://esm.sh/v125/@stablelib/[email protected]/denonext/constant-time.mjs": "5a3711ba33bc29b816dd413aad3378f765b656196252f34622277eb955e7c223",
"https://esm.sh/v125/@stablelib/[email protected]/denonext/hash.mjs": "887c7f363d7d9bb5978727f12d6344aabd79d5fc7523f1943271e2e5403d944b",
"https://esm.sh/v125/@stablelib/[email protected]/lib/hash.d.ts": "6562fc4b9b470ee474add192278b9ad7d863b1096f57c7aeb26e6b15a86e51a7",
"https://esm.sh/v125/@stablelib/[email protected]/denonext/hmac.mjs": "9901cc0f5e96d86a039f964e122d6f130c0a722a86bf6287a1ebbb420f81e964",
"https://esm.sh/v125/@stablelib/[email protected]/denonext/int.mjs": "179398595f1fcf9e85c3251583cf23031bf1b5b2040d7ad731de19cdb1e61adc",
"https://esm.sh/v125/@stablelib/[email protected]/denonext/wipe.mjs": "116b9cbca9c97e0a997d7cc3ebd9e60883d99eaa2345d48cafeb783b44808123",
"https://esm.sh/v125/[email protected]/denonext/array-buffer-to-hex.mjs": "e9c4132a6dc310f33ffc362e0becd7ab12d209962f7028ad1b9b58439cd9b35e",
"https://esm.sh/v125/gh/SortableJS/[email protected]/denonext/Sortable.min.js": "aeba191bb6622c4ad41ae5d8f5d4dd6bf15074f264cb3640ed223fbaf052263b",
"https://esm.sh/v125/[email protected]/denonext/lifegpc-md5.mjs": "332f72d899b7fa2a74c4a1a4f00616fa5543bcfd9a182314b9408501c3b0bb10",
"https://esm.sh/v125/[email protected]/lib/md5.d.ts": "82fd2b0c148e013ade0b46149953270220d0fd61835f2d12a95832fa2e0dcfc0",
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/deps.ts": "b7248e5b750be62613a9417f407e65ed43726d83b11f9631d6dbb58634bbd7d1",
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/mod.ts": "3e507379372361162f93325a216b86f6098defb5bb60144555b507bca26d061f",
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/src/deno.ts": "71bee6b14e72ca193c0686d8b4f1f47d639a64745b6f5c7576f7a3616f436f57",

View File

@@ -3,22 +3,26 @@
// This file is automatically updated during development when running `dev.ts`.
import config from "./deno.json" assert { type: "json" };
import * as $0 from "./routes/api/config.ts";
import * as $1 from "./routes/api/exit.ts";
import * as $2 from "./routes/api/export/gallery/zip/[gid].ts";
import * as $3 from "./routes/api/task.ts";
import * as $4 from "./routes/index.tsx";
import * as $0 from "./routes/_middleware.ts";
import * as $1 from "./routes/api/config.ts";
import * as $2 from "./routes/api/deploy_id.ts";
import * as $3 from "./routes/api/exit.ts";
import * as $4 from "./routes/api/export/gallery/zip/[gid].ts";
import * as $5 from "./routes/api/task.ts";
import * as $6 from "./routes/index.tsx";
import * as $$0 from "./islands/Container.tsx";
import * as $$1 from "./islands/Settings.tsx";
import * as $$2 from "./islands/TaskManager.tsx";
const manifest = {
routes: {
"./routes/api/config.ts": $0,
"./routes/api/exit.ts": $1,
"./routes/api/export/gallery/zip/[gid].ts": $2,
"./routes/api/task.ts": $3,
"./routes/index.tsx": $4,
"./routes/_middleware.ts": $0,
"./routes/api/config.ts": $1,
"./routes/api/deploy_id.ts": $2,
"./routes/api/exit.ts": $3,
"./routes/api/export/gallery/zip/[gid].ts": $4,
"./routes/api/task.ts": $5,
"./routes/index.tsx": $6,
},
islands: {
"./islands/Container.tsx": $$0,

View File

@@ -15,6 +15,8 @@
"$std/": "https://deno.land/[email protected]/",
"preact-material-components/": "https://esm.sh/[email protected]/",
"accept-language-parser/": "https://esm.sh/[email protected]/",
"sortable": "https://esm.sh/gh/SortableJS/[email protected]/Sortable.min.js"
"sortable": "https://esm.sh/gh/SortableJS/[email protected]/Sortable.min.js",
"esbuild/": "https://deno.land/x/[email protected]/",
"lifegpc-md5": "https://esm.sh/[email protected]"
}
}

53
routes/_middleware.ts Normal file
View File

@@ -0,0 +1,53 @@
import { MiddlewareHandlerContext } from "$fresh/server.ts";
import { build } from "esbuild/mod.js";
import { join, resolve } from "std/path/mod.ts";
import { asyncForEach, calFileMd5, checkMapFile } from "../utils.ts";
export async function handler(req: Request, ctx: MiddlewareHandlerContext) {
const url = new URL(req.url);
if (url.pathname == "/sw.js") {
const base = import.meta.resolve("../static").slice(8);
const map_file = join(base, "sw.meta.json");
if (!(await checkMapFile(map_file))) {
const data = await build({
entryPoints: [join(base, "sw.ts")],
outfile: join(base, "sw.js"),
metafile: true,
});
const map = data.metafile;
await asyncForEach(
Object.getOwnPropertyNames(map.inputs),
async (k) => {
const p = resolve(k);
if (p !== k) {
map.inputs[p] = map.inputs[k];
delete map.inputs[k];
k = p;
}
const data = map.inputs[k];
data.md5 = await calFileMd5(k);
},
);
await asyncForEach(
Object.getOwnPropertyNames(map.outputs),
async (k) => {
const p = resolve(k);
if (p !== k) {
map.outputs[p] = map.outputs[k];
delete map.outputs[k];
k = p;
}
const data = map.outputs[k];
data.md5 = await calFileMd5(k);
},
);
await Deno.writeTextFile(map_file, JSON.stringify(map));
console.log("Rebuild.");
}
}
const res = await ctx.next();
if (url.pathname == "/sw.js") {
res.headers.delete("etag");
}
return res;
}

10
routes/api/deploy_id.ts Normal file
View File

@@ -0,0 +1,10 @@
import { Handlers } from "$fresh/server.ts";
export const handler: Handlers = {
GET(_req, _ctx) {
const data = { id: Deno.env.get("DENO_DEPLOYMENT_ID") };
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
},
};

2
static/.gitignore vendored
View File

@@ -1 +1,3 @@
preact-material-components/
sw.js
sw.meta.json

57
static/sw.ts Normal file
View File

@@ -0,0 +1,57 @@
import {
ActivateEvent,
} from "https://gist.githubusercontent.com/ithinkihaveacat/227bfe8aa81328c5d64ec48f4e4df8e5/raw/f69f0783e69f5827b20dbe3f3509ddbf73933768/service-worker.d.ts";
async function get_deploy_id(): Promise<string | undefined> {
const re = await (await fetch("/api/deploy_id")).json();
return re.id;
}
let deploy_id: string | undefined = undefined;
const deleteCache = async (key: string) => {
await caches.delete(key);
};
const deleteOldCaches = async () => {
deploy_id = await get_deploy_id();
const keyList = await caches.keys();
const cachesToDelete = keyList.filter((key) => key !== deploy_id);
await Promise.all(cachesToDelete.map(deleteCache));
};
/**@ts-ignore */
self.addEventListener("activate", (event: ActivateEvent) => {
event.waitUntil(deleteOldCaches());
});
const CACHES = ["/common.css", "/preact-material-components/style.css"];
function match_url(u: URL) {
const pn = u.pathname;
const ori = u.origin;
if (ori == self.location.origin) {
if (CACHES.includes(pn)) return true;
if (pn.startsWith("/_frsh/")) return true;
}
if (ori === "https://fonts.gstatic.com") return true;
return false;
}
/**@ts-ignore */
self.addEventListener("fetch", async (e: FetchEvent) => {
const r = e.request;
const responseFromCache = await caches.match(r);
if (responseFromCache) {
return responseFromCache;
}
const res = await fetch(r);
if (res.ok) {
const url = new URL(r.url);
if (deploy_id && match_url(url)) {
const cache = await caches.open(deploy_id);
await cache.put(r, res.clone());
}
}
return res;
});

View File

@@ -2,6 +2,7 @@ import { exists, existsSync } from "std/fs/exists.ts";
import { extname } from "std/path/mod.ts";
import { initParser } from "deno_dom/deno-dom-wasm-noinit.ts";
import { configure } from "zipjs/index.js";
import { MD5 } from "lifegpc-md5";
export function sleep(time: number): Promise<undefined> {
return new Promise((r) => {
@@ -164,3 +165,69 @@ export function add_suffix_to_path(path: string, suffix?: string) {
return `${path}-${suffix}`;
}
}
export async function calFileMd5(p: string | URL) {
const h = new MD5();
const f = await Deno.open(p);
try {
const buf = new Uint8Array(4096);
let readed: number | null = null;
do {
readed = await f.read(buf);
if (readed) {
h.update(buf.slice(0, readed));
}
} while (readed !== null);
return h.digest_hex();
} finally {
f.close();
}
}
export async function checkMapFile(p: string | URL, signal?: AbortSignal) {
if (!(await exists(p))) return false;
const map = JSON.parse(await Deno.readTextFile(p, { signal }));
if (
!(await asyncEvery(
Object.getOwnPropertyNames(map.inputs),
async (k) => {
const data = map.inputs[k];
const md5 = data.md5;
if (!md5) return false;
if (!(await exists(k))) return false;
const m = await calFileMd5(k);
return md5 === m;
},
))
) return false;
if (
!(await asyncEvery(
Object.getOwnPropertyNames(map.outputs),
async (k) => {
const data = map.outputs[k];
const md5 = data.md5;
if (!md5) return false;
if (!(await exists(k))) return false;
const m = await calFileMd5(k);
return md5 === m;
},
))
) return false;
return true;
}
export async function asyncEvery<T, V>(
arr: ArrayLike<T>,
callback: (
this: V | undefined,
element: T,
index: number,
array: ArrayLike<T>,
) => Promise<boolean>,
thisArg?: V,
) {
for (let i = 0; i < arr.length; i++) {
if (!await callback.apply(thisArg, [arr[i], i, arr])) return false;
}
return true;
}

View File

@@ -2,13 +2,17 @@ import { assert, assertEquals } from "std/testing/asserts.ts";
import { check_running } from "./pid_check.ts";
import {
add_suffix_to_path,
asyncEvery,
asyncFilter,
asyncForEach,
calFileMd5,
filterFilename,
promiseState,
PromiseStatus,
sleep,
sure_dir,
} from "./utils.ts";
import { md5 } from "lifegpc-md5";
Deno.test("promiseState_test", async () => {
const p1 = new Promise((res) => setTimeout(() => res(100), 100));
@@ -79,3 +83,48 @@ Deno.test("add_suffix_to_path_test", () => {
assert(t.startsWith("test-"));
assert(t.endsWith(".ts"));
});
Deno.test("calFileMd5_test", async () => {
await sure_dir();
const text = `Hello World.te${Math.random()}`;
await Deno.writeTextFile("./test/test.txt", text);
assertEquals(await calFileMd5("./test/test.txt"), md5(text));
});
Deno.test("asyncEvery_test", async () => {
const e = [new Promise<number>((res) => setTimeout(() => res(100), 100))];
const e2 = [
new Promise<number>((res) => setTimeout(() => res(100), 100)),
new Promise<number>((res) => setTimeout(() => res(200), 100)),
];
const e3 = [
new Promise<number>((res) => setTimeout(() => res(100), 100)),
new Promise<number>((res) => setTimeout(() => res(200), 100)),
new Promise<number>((res) => setTimeout(() => res(150), 100)),
];
const t = { test: 2 };
assertEquals(
await asyncEvery(e, async function (e) {
assertEquals(this, t);
return (await e) === 100;
}, t),
true,
);
assertEquals(
await asyncEvery(e2, async function (e) {
assertEquals(this, t);
const d = await e;
return d === 100;
}, t),
false,
);
assertEquals(
await asyncEvery(e3, async function (e) {
assertEquals(this, t);
const d = await e;
if (d === 150) throw Error("Should exited.");
return d === 100;
}, t),
false,
);
});