Skip to content

Commit

Permalink
Add support to upload custom file
Browse files Browse the repository at this point in the history
  • Loading branch information
lifegpc committed Dec 15, 2023
1 parent efcb276 commit cf38e9e
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 43 deletions.
5 changes: 5 additions & 0 deletions config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type ConfigType = {
flutter_frontend?: string;
fetch_timeout: number;
download_timeout: number;
ffprobe_path: string;
};

export enum ThumbnailMethod {
Expand Down Expand Up @@ -172,6 +173,9 @@ export class Config {
get download_timeout() {
return this._return_number("download_timeout") || 10000;
}
get ffprobe_path() {
return this._return_string("ffprobe_path") || "ffprobe";
}
to_json(): ConfigType {
return {
cookies: typeof this.cookies === "string",
Expand Down Expand Up @@ -200,6 +204,7 @@ export class Config {
flutter_frontend: this.flutter_frontend,
fetch_timeout: this.fetch_timeout,
download_timeout: this.download_timeout,
ffprobe_path: this.ffprobe_path,
};
}
}
Expand Down
78 changes: 42 additions & 36 deletions fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,30 @@ import * as $4 from "./routes/api/exit.ts";
import * as $5 from "./routes/api/export/gallery/zip/[gid].ts";
import * as $6 from "./routes/api/file/[id].ts";
import * as $7 from "./routes/api/file/random.ts";
import * as $8 from "./routes/api/filemeta.ts";
import * as $9 from "./routes/api/filemeta/[token].ts";
import * as $10 from "./routes/api/files/[token].ts";
import * as $11 from "./routes/api/gallery/[gid].ts";
import * as $12 from "./routes/api/gallery/list.ts";
import * as $13 from "./routes/api/status.ts";
import * as $14 from "./routes/api/tag/[id].ts";
import * as $15 from "./routes/api/tag/rows.ts";
import * as $16 from "./routes/api/task.ts";
import * as $17 from "./routes/api/thumbnail/[id].ts";
import * as $18 from "./routes/api/token.ts";
import * as $19 from "./routes/api/user.ts";
import * as $20 from "./routes/file/[id].ts";
import * as $21 from "./routes/file/_middleware.ts";
import * as $22 from "./routes/index.tsx";
import * as $23 from "./routes/manifest.json.ts";
import * as $24 from "./routes/thumbnail/[id].ts";
import * as $25 from "./routes/thumbnail/_middleware.ts";
import * as $8 from "./routes/api/file/upload.ts";
import * as $9 from "./routes/api/filemeta.ts";
import * as $10 from "./routes/api/filemeta/[token].ts";
import * as $11 from "./routes/api/files/[token].ts";
import * as $12 from "./routes/api/gallery/[gid].ts";
import * as $13 from "./routes/api/gallery/list.ts";
import * as $14 from "./routes/api/status.ts";
import * as $15 from "./routes/api/tag/[id].ts";
import * as $16 from "./routes/api/tag/rows.ts";
import * as $17 from "./routes/api/task.ts";
import * as $18 from "./routes/api/thumbnail/[id].ts";
import * as $19 from "./routes/api/token.ts";
import * as $20 from "./routes/api/user.ts";
import * as $21 from "./routes/file/[id].ts";
import * as $22 from "./routes/file/_middleware.ts";
import * as $23 from "./routes/index.tsx";
import * as $24 from "./routes/manifest.json.ts";
import * as $25 from "./routes/thumbnail/[id].ts";
import * as $26 from "./routes/thumbnail/_middleware.ts";
import * as $27 from "./routes/upload.tsx";
import * as $$0 from "./islands/Container.tsx";
import * as $$1 from "./islands/Settings.tsx";
import * as $$2 from "./islands/TaskManager.tsx";
import * as $$3 from "./islands/Upload.tsx";

const manifest = {
routes: {
Expand All @@ -42,29 +45,32 @@ const manifest = {
"./routes/api/export/gallery/zip/[gid].ts": $5,
"./routes/api/file/[id].ts": $6,
"./routes/api/file/random.ts": $7,
"./routes/api/filemeta.ts": $8,
"./routes/api/filemeta/[token].ts": $9,
"./routes/api/files/[token].ts": $10,
"./routes/api/gallery/[gid].ts": $11,
"./routes/api/gallery/list.ts": $12,
"./routes/api/status.ts": $13,
"./routes/api/tag/[id].ts": $14,
"./routes/api/tag/rows.ts": $15,
"./routes/api/task.ts": $16,
"./routes/api/thumbnail/[id].ts": $17,
"./routes/api/token.ts": $18,
"./routes/api/user.ts": $19,
"./routes/file/[id].ts": $20,
"./routes/file/_middleware.ts": $21,
"./routes/index.tsx": $22,
"./routes/manifest.json.ts": $23,
"./routes/thumbnail/[id].ts": $24,
"./routes/thumbnail/_middleware.ts": $25,
"./routes/api/file/upload.ts": $8,
"./routes/api/filemeta.ts": $9,
"./routes/api/filemeta/[token].ts": $10,
"./routes/api/files/[token].ts": $11,
"./routes/api/gallery/[gid].ts": $12,
"./routes/api/gallery/list.ts": $13,
"./routes/api/status.ts": $14,
"./routes/api/tag/[id].ts": $15,
"./routes/api/tag/rows.ts": $16,
"./routes/api/task.ts": $17,
"./routes/api/thumbnail/[id].ts": $18,
"./routes/api/token.ts": $19,
"./routes/api/user.ts": $20,
"./routes/file/[id].ts": $21,
"./routes/file/_middleware.ts": $22,
"./routes/index.tsx": $23,
"./routes/manifest.json.ts": $24,
"./routes/thumbnail/[id].ts": $25,
"./routes/thumbnail/_middleware.ts": $26,
"./routes/upload.tsx": $27,
},
islands: {
"./islands/Container.tsx": $$0,
"./islands/Settings.tsx": $$1,
"./islands/TaskManager.tsx": $$2,
"./islands/Upload.tsx": $$3,
},
baseUrl: import.meta.url,
};
Expand Down
44 changes: 44 additions & 0 deletions islands/Upload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import t, { i18n_map, I18NMap } from "../server/i18n.ts";

export type UploaderProps = {
i18n?: I18NMap;
lang?: string;
};

export default function Uploader(props: UploaderProps) {
if (props.i18n) i18n_map.value = props.i18n;
return (
<form
action="/api/file/upload"
method="post"
encType="multipart/form-data"
>
<input
type="file"
name="file"
required={true}
accept=".jpg,.jpeg,.png"
/>
<br />
{t("upload.filename")}{" "}
<input
type="text"
name="filename"
placeholder={t("upload.filename")}
/>
<br />
<input id="is_original" type="checkbox" name="is_original" />{" "}
<label for="is_original">{t("upload.is_original")}</label>
<br />
<input
id="token"
type="text"
name="token"
placeholder={t("upload.token")}
required={true}
/>
<br />
<input type="submit" value={t("upload.upload")} />
</form>
);
}
75 changes: 75 additions & 0 deletions routes/api/file/upload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Handlers } from "$fresh/server.ts";
import type { EhFile } from "../../../db.ts";
import { get_task_manager } from "../../../server.ts";
import { return_data, return_error } from "../../../server/utils.ts";
import { get_string, parse_bool } from "../../../server/parse_form.ts";
import { fb_get_size } from "../../../thumbnail/ffmpeg_binary.ts";
import { sure_dir } from "../../../utils.ts";
import mime from "mime";
import { extname, join, resolve } from "std/path/mod.ts";

export const handler: Handlers = {
async POST(req, _ctx) {
const m = get_task_manager();
try {
const form = await req.formData();
const file = form.get("file");
if (!file) {
return return_error(1, "Missing file.");
}
const mext = typeof file === "string"
? null
: `.${mime.getExtension(file.type)}`;
const filename = (await get_string(form.get("filename"))) ||
(typeof file === "string" ? null : file.name);
if (!filename) {
return return_error(2, "Missing filename.");
}
const fext = extname(filename);
const fn = mext == fext
? filename
: `${filename.slice(0, filename.length - fext.length)}${mext}`;
const dir = (await get_string(form.get("dir"))) ||
join(m.cfg.base, "uploaded");
const is_original = await parse_bool(
form.get("is_original"),
false,
);
const token = await get_string(form.get("token"));
if (!token) {
return return_error(3, "Missing token.");
}
const path = join(dir, fn);
await sure_dir(dir);
try {
if (typeof file === "string") {
await Deno.writeTextFile(path, file);
} else {
await Deno.writeFile(path, file.stream());
}
const size = await fb_get_size(path);
if (!size) {
await Deno.remove(path);
return return_error(4, "Failed to get file size.");
}
const rpath = resolve(path);
const f = {
id: 0,
path: rpath,
width: size.width,
height: size.height,
is_original,
token,
} as EhFile;
const nf = m.db.add_file(f, false);
return return_data(nf);
} catch (e) {
await Deno.remove(path);
throw e;
}
} catch (e) {
console.error(e);
return return_error(500, "Internal Server Error.");
}
},
};
4 changes: 4 additions & 0 deletions routes/api/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const handler: Handlers = {
m.cfg.ffmpeg_path,
);
const ffmpeg_api_enabled = await check_ffmpeg_api();
const ffprobe_binary_enabled = await check_ffmpeg_binary(
m.cfg.ffprobe_path,
);
const meilisearch_enabled = m.meilisearch !== undefined;
let meilisearch;
if (
Expand All @@ -50,6 +53,7 @@ export const handler: Handlers = {
return return_data<StatusData>({
ffmpeg_api_enabled,
ffmpeg_binary_enabled,
ffprobe_binary_enabled,
meilisearch_enabled,
meilisearch,
no_user,
Expand Down
31 changes: 31 additions & 0 deletions routes/upload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Handlers, PageProps } from "$fresh/server.ts";
import GlobalContext from "../components/GlobalContext.tsx";
import Uploader from "../islands/Upload.tsx";
import { get_i18nmap, i18n_handle_request } from "../server/i18ns.ts";

type Props = {
lang: string;
};

export const handler: Handlers<Props> = {
GET(req, ctx) {
const re = i18n_handle_request(req);
if (typeof re === "string") {
return ctx.render({
lang: re,
});
}
return re;
},
};

export default function Upload({ data }: PageProps<Props>) {
const i18n = get_i18nmap(data.lang, "upload");
return (
<body>
<GlobalContext>
<Uploader i18n={i18n} lang={data.lang} />
</GlobalContext>
</body>
);
}
4 changes: 2 additions & 2 deletions server/i18ns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { get_host } from "./utils.ts";

const whole_maps = new Map<string, I18NMap>();
const LANGUAGES = ["zh-cn"];
type MODULE = "common" | "settings" | "task" | "user";
const MODULES: MODULE[] = ["common", "settings", "task", "user"];
type MODULE = "common" | "settings" | "task" | "upload" | "user";
const MODULES: MODULE[] = ["common", "settings", "task", "upload", "user"];

export async function load_translation(signal?: AbortSignal) {
let base = import.meta.resolve("../translation").slice(7);
Expand Down
1 change: 1 addition & 0 deletions server/status.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type StatusData = {
ffmpeg_api_enabled: boolean;
ffmpeg_binary_enabled: boolean;
ffprobe_binary_enabled: boolean;
meilisearch_enabled: boolean;
meilisearch?: {
host: string;
Expand Down
13 changes: 8 additions & 5 deletions tasks/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { RecoverableError, TaskManager } from "../task_manager.ts";
import {
add_suffix_to_path,
asyncEvery,
asyncFilter,
promiseState,
PromiseStatus,
Expand Down Expand Up @@ -235,11 +236,13 @@ export async function download_task(
async function download_task(names: Record<string, number>, i: Image) {
const ofiles = db.get_files(i.page_token);
if (ofiles.length) {
const t = ofiles[0];
if (
(t.is_original || !download_original_img) &&
(await exists(t.path))
) {
const need = await asyncEvery(
ofiles,
async (t) =>
(!t.is_original && download_original_img) ||
(!await exists(t.path)),
);
if (!need) {
const p = db.get_pmeta_by_index(task.gid, i.index);
if (!p) {
const op = db.get_pmeta_by_token(
Expand Down
28 changes: 28 additions & 0 deletions thumbnail/ffmpeg_binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,34 @@ export async function check_ffmpeg_binary(p: string) {
return o.code === 0;
}

export async function fb_get_size(i: string) {
const cmd = new Deno.Command("ffprobe", {
stdout: "piped",
stderr: "piped",
args: [
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
"stream=width,height",
"-of",
"csv=s=x:p=0",
i,
],
});
const c = cmd.spawn();
const o = await c.output();
if (o.code !== 0) {
return null;
}
const s = (new TextDecoder()).decode(o.stdout).trim().split("x");
return {
width: parseInt(s[0]),
height: parseInt(s[1]),
};
}

export async function fb_generate_thumbnail(
p: string,
i: string,
Expand Down
6 changes: 6 additions & 0 deletions translation/en/upload.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"upload": "Upload file",
"filename": "Filename",
"is_original": "Marked as original file",
"token": "Token"
}
6 changes: 6 additions & 0 deletions translation/zh-cn/upload.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"upload": "上传文件",
"filename": "文件名",
"is_original": "标记为原始文件",
"token": "Token"
}

0 comments on commit cf38e9e

Please sign in to comment.