diff --git a/components/SourceOptions/FlatFilesystem.vue b/components/SourceOptions/FlatFilesystem.vue new file mode 100644 index 0000000..9f3e310 --- /dev/null +++ b/components/SourceOptions/FlatFilesystem.vue @@ -0,0 +1,27 @@ + + + diff --git a/i18n/locales/en_us.json b/i18n/locales/en_us.json index c7f9bda..b28ca70 100644 --- a/i18n/locales/en_us.json +++ b/i18n/locales/en_us.json @@ -373,6 +373,7 @@ "fsPath": "Path", "fsPathDesc": "An absolute path to your game library.", "fsPathPlaceholder": "/mnt/games", + "fsFlatDesc": "Imports games from a path on disk, but without a separate version subfolder. Useful when migrating an existing library to Drop.", "link": "Sources {arrow}", "nameDesc": "The name of your source, for reference.", "namePlaceholder": "My New Source", diff --git a/package.json b/package.json index 9444eeb..bcffcac 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "@discordapp/twemoji": "^15.1.0", - "@drop-oss/droplet": "^1.3.1", + "@drop-oss/droplet": "1.5.3", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.1.5", "@lobomfz/prismark": "0.0.3", diff --git a/pages/admin/library/sources/index.vue b/pages/admin/library/sources/index.vue index 530eba3..bcadc6f 100644 --- a/pages/admin/library/sources/index.vue +++ b/pages/admin/library/sources/index.vue @@ -167,7 +167,7 @@ 'relative block cursor-pointer bg-zinc-800 rounded-lg border border-zinc-900 px-2 py-2 shadow-sm focus:outline-none sm:flex sm:justify-between', ]" > - +
{ @@ -57,7 +56,7 @@ export abstract class LibraryProvider { version: string, filename: string, options?: { start?: number; end?: number }, - ): Promise; + ): Promise; } export class GameNotFoundError extends Error {} diff --git a/server/internal/library/filesystem.ts b/server/internal/library/providers/filesystem.ts similarity index 90% rename from server/internal/library/filesystem.ts rename to server/internal/library/providers/filesystem.ts index 2381a18..23c281a 100644 --- a/server/internal/library/filesystem.ts +++ b/server/internal/library/providers/filesystem.ts @@ -3,12 +3,11 @@ import { GameNotFoundError, VersionNotFoundError, type LibraryProvider, -} from "./provider"; +} from "../provider"; import { LibraryBackend } from "~/prisma/client"; import fs from "fs"; import path from "path"; import droplet from "@drop-oss/droplet"; -import type { Readable } from "stream"; export const FilesystemProviderConfig = type({ baseDir: "string", @@ -86,24 +85,29 @@ export class FilesystemProvider return manifest; } - // TODO: move this over to the droplet.readfile function it works + async peekFile(game: string, version: string, filename: string) { + const filepath = path.join(this.config.baseDir, game, version); + if (!fs.existsSync(filepath)) return undefined; + const stat = droplet.peekFile(filepath, filename); + return { size: stat }; + } + async readFile( game: string, version: string, filename: string, options?: { start?: number; end?: number }, - ): Promise { - const filepath = path.join(this.config.baseDir, game, version, filename); + ) { + const filepath = path.join(this.config.baseDir, game, version); if (!fs.existsSync(filepath)) return undefined; - const stream = fs.createReadStream(filepath, options); + const stream = droplet.readFile( + filepath, + filename, + options?.start, + options?.end, + ); + if (!stream) return undefined; return stream; } - - async peekFile(game: string, version: string, filename: string) { - const filepath = path.join(this.config.baseDir, game, version, filename); - if (!fs.existsSync(filepath)) return undefined; - const stat = fs.statSync(filepath); - return { size: stat.size }; - } } diff --git a/server/internal/library/providers/flat.ts b/server/internal/library/providers/flat.ts new file mode 100644 index 0000000..a4bc3c3 --- /dev/null +++ b/server/internal/library/providers/flat.ts @@ -0,0 +1,109 @@ +import { ArkErrors, type } from "arktype"; +import type { LibraryProvider } from "../provider"; +import { VersionNotFoundError } from "../provider"; +import { LibraryBackend } from "~/prisma/client"; +import fs from "fs"; +import path from "path"; +import droplet from "@drop-oss/droplet"; + +export const FlatFilesystemProviderConfig = type({ + baseDir: "string", +}); + +export class FlatFilesystemProvider + implements LibraryProvider +{ + private config: typeof FlatFilesystemProviderConfig.infer; + private myId: string; + + constructor(rawConfig: unknown, id: string) { + const config = FlatFilesystemProviderConfig(rawConfig); + if (config instanceof ArkErrors) { + throw new Error( + `Failed to create filesystem provider: ${config.summary}`, + ); + } + + this.myId = id; + this.config = config; + + if (!fs.existsSync(this.config.baseDir)) + throw "Base directory does not exist."; + } + + type() { + return LibraryBackend.FlatFilesystem; + } + id() { + return this.myId; + } + + /** + * These are basically our versions, but also our games. + * @returns list of valid games + */ + async listGames() { + const versionDirs = fs.readdirSync(this.config.baseDir); + const validVersionDirs = versionDirs.filter((e) => { + const fullDir = path.join(this.config.baseDir, e); + return droplet.hasBackendForPath(fullDir); + }); + return validVersionDirs; + } + + /** + * Doesn't do anything, just returns "default" + * @param _game Ignored + * @returns + */ + async listVersions(_game: string) { + return ["default"]; + } + + async versionReaddir(game: string, _version: string) { + const versionDir = path.join(this.config.baseDir, game); + if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); + return droplet.listFiles(versionDir); + } + + async generateDropletManifest( + game: string, + _version: string, + progress: (err: Error | null, v: number) => void, + log: (err: Error | null, v: string) => void, + ) { + const versionDir = path.join(this.config.baseDir, game); + if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); + const manifest = await new Promise((r, j) => + droplet.generateManifest(versionDir, progress, log, (err, result) => { + if (err) return j(err); + r(result); + }), + ); + return manifest; + } + async peekFile(game: string, _version: string, filename: string) { + const filepath = path.join(this.config.baseDir, game); + if (!fs.existsSync(filepath)) return undefined; + const stat = droplet.peekFile(filepath, filename); + return { size: stat }; + } + async readFile( + game: string, + _version: string, + filename: string, + options?: { start?: number; end?: number }, + ) { + const filepath = path.join(this.config.baseDir, game); + if (!fs.existsSync(filepath)) return undefined; + const stream = droplet.readFile( + filepath, + filename, + options?.start, + options?.end, + ); + if (!stream) return undefined; + + return stream; + } +} diff --git a/server/plugins/05.library-init.ts b/server/plugins/05.library-init.ts index dc0bd7c..e0bdcd9 100644 --- a/server/plugins/05.library-init.ts +++ b/server/plugins/05.library-init.ts @@ -2,10 +2,11 @@ import { LibraryBackend } from "~/prisma/client"; import prisma from "../internal/db/database"; import type { JsonValue } from "@prisma/client/runtime/library"; import type { LibraryProvider } from "../internal/library/provider"; -import type { FilesystemProviderConfig } from "../internal/library/filesystem"; -import { FilesystemProvider } from "../internal/library/filesystem"; +import type { FilesystemProviderConfig } from "../internal/library/providers/filesystem"; +import { FilesystemProvider } from "../internal/library/providers/filesystem"; import libraryManager from "../internal/library"; import path from "path"; +import { FlatFilesystemProvider } from "../internal/library/providers/flat"; export const libraryConstructors: { [key in LibraryBackend]: ( @@ -19,6 +20,12 @@ export const libraryConstructors: { ): LibraryProvider { return new FilesystemProvider(value, id); }, + FlatFilesystem: function ( + value: JsonValue, + id: string, + ): LibraryProvider { + return new FlatFilesystemProvider(value, id); + }, }; export default defineNitroPlugin(async () => { diff --git a/yarn.lock b/yarn.lock index 120b1e8..354f156 100644 --- a/yarn.lock +++ b/yarn.lock @@ -330,71 +330,71 @@ jsonfile "^5.0.0" universalify "^0.1.2" -"@drop-oss/droplet-darwin-arm64@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-arm64/-/droplet-darwin-arm64-1.3.1.tgz#672484c15419dbc068950f2d53f130ccbea3ff17" - integrity sha512-rarsZtIiZhv2hb3bAZSJjxwnme+rWUFY+FY79MRrMnz7EuNBez063pFBqDhwFCz+0QqDBz7zKDUQR6v+6gnVFw== +"@drop-oss/droplet-darwin-arm64@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-arm64/-/droplet-darwin-arm64-1.5.3.tgz#98d34b7b35a9525cf760e17c41d7750f47dab7d1" + integrity sha512-5b7jMv8EgvE0J34dEgl6hitxk5aFtPCxH9xz95Ixz5A1vbqqYlIWWpk4jjBj78bqXQKufHk4ivHPi7btOlwFzQ== -"@drop-oss/droplet-darwin-universal@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-universal/-/droplet-darwin-universal-1.3.1.tgz#0a3e1663125349b2d443dbfaba1bb7b8d2f9c64d" - integrity sha512-PuN5FdotwYuZ7O2r1aAWiE8hH/gH7CrH+j33OdgS4FI4XIOeW6qq+14JECZp6JgWv0863/C7tD5Ll4yMgIRvUQ== +"@drop-oss/droplet-darwin-universal@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-universal/-/droplet-darwin-universal-1.5.3.tgz#3699a7acd958cc6f5a4ef55597dc4d24696133ff" + integrity sha512-IapfmWSEPTw8phdKaKT1Gf7FrHIsWtuCJT8dqtOXycq8ROrZBX3DT5JvhYkHkki0r3DrdOeTkkpy5DoEp2S9gA== -"@drop-oss/droplet-darwin-x64@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-x64/-/droplet-darwin-x64-1.3.1.tgz#755db12988b431eff24df9a7177453178f29ce47" - integrity sha512-IloUIHnEI67S38vJxADbcXk81tR8b4fFTTpyCNUlAwIXHGbhEjFrfu+sLdK94MHN/vjlafMBf0APwYF2vclCkw== +"@drop-oss/droplet-darwin-x64@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-x64/-/droplet-darwin-x64-1.5.3.tgz#c52de2f746fea9bb9ce8eb70f42ece28fdd7ee08" + integrity sha512-J8w0bBMSwXKYyeGTMkO/4ZmwRWcy5zVpiPQfdu0T3LFfjbMrhVNsFe/g7NyYGGaQKdjpuLIRHl8pS8U5XWWdog== -"@drop-oss/droplet-linux-arm64-gnu@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-gnu/-/droplet-linux-arm64-gnu-1.3.1.tgz#26ac0b24a08e6785742a19cbf97003f23d3b152d" - integrity sha512-aiesHfQushi+EGmTj970bxZvhNsBh90kzKbg14vdgFTL0/mhcturJSHa0VhJ2/m4qIg10IlmJpbuEm165Q25rQ== +"@drop-oss/droplet-linux-arm64-gnu@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-gnu/-/droplet-linux-arm64-gnu-1.5.3.tgz#90c1f37be61b12164205cb75ef5fded2a7d4c671" + integrity sha512-eh9Wa9GcV3wu2CjJehnx4y2xjwM2i5vk2uaHCUQa9y6G2bD5pqThtefEwViOfmh7ua6IxepAJ/CE+0eNKy/V3w== -"@drop-oss/droplet-linux-arm64-musl@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-musl/-/droplet-linux-arm64-musl-1.3.1.tgz#1449cd59a75363a01f2ed68c1921e6d93fbafccd" - integrity sha512-Oa6HvvBbflxoG1nmdYbcgMAf29aTZ6xCxC84X+Q8TjiO5Qx2PHI+nX+KKK8rrJdQszrqpdT9wZbnD4zDtLzSeQ== +"@drop-oss/droplet-linux-arm64-musl@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-musl/-/droplet-linux-arm64-musl-1.5.3.tgz#7ea5db5a1f05671adffc0560dbbf944825522601" + integrity sha512-1QC80pa2hPrQ/u15pSUhURH2s5Q1Ywz3Dzlr9zQaG+ZCAOoFvGZHTJX//I9gOYmKoKs0CJ5PrqWsdNjGBO9izA== -"@drop-oss/droplet-linux-riscv64-gnu@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-riscv64-gnu/-/droplet-linux-riscv64-gnu-1.3.1.tgz#6300be23128ed4fd8895ca610cc2fb9bdf9abc05" - integrity sha512-vAVUiMixfB/oXIZ7N6QhJB1N+lb96JLrs2EjZiPGNSgwKGMV0H+84ZI+5NJ30qoytm7WB8mm2beezoCpM8frjg== +"@drop-oss/droplet-linux-riscv64-gnu@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-riscv64-gnu/-/droplet-linux-riscv64-gnu-1.5.3.tgz#1052ca2c53f836f487986e77710a3053e45ced38" + integrity sha512-9L8AtfcZOVCtGnFfTfnVJNaA2JhDyVjtsLfXEmE9xqqYjO+JjzHU3KxApDLWuD7vnjv7YbJOn9NXJWF8gbIrXg== -"@drop-oss/droplet-linux-x64-gnu@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-gnu/-/droplet-linux-x64-gnu-1.3.1.tgz#5f59e8816978af444301ad7116a0377f9aa2633a" - integrity sha512-R3UtBIw5amY1ExaX8fZMcS1zLv0DF9Y8YoBgqk+VbQrHMVfiQKiktv/dXRp+9iWzLB/m5aG/Se5QuJazOMlwtA== +"@drop-oss/droplet-linux-x64-gnu@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-gnu/-/droplet-linux-x64-gnu-1.5.3.tgz#237c5fe83305f562817240c8d07edb6934bae598" + integrity sha512-CZUurI+0Sx0PZSVrA/yy7H0jAMWliv6CFgJVQ1KWnALRB39cW7iUX2EOwAWNbxIpT4KpgOuryH+njEbNLJrbrQ== -"@drop-oss/droplet-linux-x64-musl@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-musl/-/droplet-linux-x64-musl-1.3.1.tgz#84b136665dc713c66a3e5b2be6ce5d97514dcb25" - integrity sha512-rDtmTYzx39Y1xHyRvm2AW97GkHy4ZfhXsmYWSjqo0dmoM5BY/nHfmNO6kWOABg4WP6mr3NPZKJTe885JVNilcg== +"@drop-oss/droplet-linux-x64-musl@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-musl/-/droplet-linux-x64-musl-1.5.3.tgz#9f927983aab192dc1659d7d974832638afb04efb" + integrity sha512-Bh+579k2UqEP3WTlMKM5m5cygyx1sSTJo4jVSVkRjjvVJOjrwzwH8s+zm4jlHY/EyTM86QIz31dayg/1gDCY4A== -"@drop-oss/droplet-win32-arm64-msvc@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-arm64-msvc/-/droplet-win32-arm64-msvc-1.3.1.tgz#77948fe25f27dedda979367feadbe89622aa6b19" - integrity sha512-ZQVLgloMd7NIW3j1cvL7DKp9164K8luLxb692yuXRF6pQ7ok8IPWgwiWoeqQ1OE/msPkgXEC7hHupwWtfX6tHw== +"@drop-oss/droplet-win32-arm64-msvc@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-arm64-msvc/-/droplet-win32-arm64-msvc-1.5.3.tgz#80ad0c11f63902b940c8f1809f2840a9cb1069d7" + integrity sha512-aIuLKXKoOmwAWeShZP+LJGP/W5quiaRLoIh3gx53vWdxo1O+CamWvvg0NsbCgo7bEuWiHghl9NAS78Jb/UX2Tw== -"@drop-oss/droplet-win32-x64-msvc@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-x64-msvc/-/droplet-win32-x64-msvc-1.3.1.tgz#35f2dca041af48dec6743bf5c62a35582e25715d" - integrity sha512-NJsZM4g40I0b/MHFTvur3t30ULiU8D3DfhZTlLzyT+btiQ/8PdjCKRM4CPJJhs7JG8Bp30cl2n54XnsnyaFtJA== +"@drop-oss/droplet-win32-x64-msvc@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-x64-msvc/-/droplet-win32-x64-msvc-1.5.3.tgz#6b21f1c35fce2ed60b836f61b32d0127b98475ee" + integrity sha512-F2XRvlTzKIOKIewjbyBOffguUbsc2Ejga/UJohZJ6t/dBs3NCxcj9QSerxDoeiS470m5kbNWykdaoy+tBhebrg== -"@drop-oss/droplet@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@drop-oss/droplet/-/droplet-1.3.1.tgz#360faadadf50dbe3133ed8aadce6a077d289a48d" - integrity sha512-wXQof5rUiUujAiVwJovAu4Tj2Rhlb0pE/lHSEHF3ywcOLQFcSrE1naQVz3RQ7at+ZkwmDL9fFMXmGJkEKI8jog== +"@drop-oss/droplet@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@drop-oss/droplet/-/droplet-1.5.3.tgz#da06fdaffcbf99458ca3a03b6a5b2fc77f75f7fb" + integrity sha512-e/xNPZ+9gO2jdGlNgEFIFYrbweE8ITAKH9Qdl/lyFsfFOnzfHTUV8OI+8ZewliI84jEhxnMhC+tBC9tAVPR8vg== optionalDependencies: - "@drop-oss/droplet-darwin-arm64" "1.3.1" - "@drop-oss/droplet-darwin-universal" "1.3.1" - "@drop-oss/droplet-darwin-x64" "1.3.1" - "@drop-oss/droplet-linux-arm64-gnu" "1.3.1" - "@drop-oss/droplet-linux-arm64-musl" "1.3.1" - "@drop-oss/droplet-linux-riscv64-gnu" "1.3.1" - "@drop-oss/droplet-linux-x64-gnu" "1.3.1" - "@drop-oss/droplet-linux-x64-musl" "1.3.1" - "@drop-oss/droplet-win32-arm64-msvc" "1.3.1" - "@drop-oss/droplet-win32-x64-msvc" "1.3.1" + "@drop-oss/droplet-darwin-arm64" "1.5.3" + "@drop-oss/droplet-darwin-universal" "1.5.3" + "@drop-oss/droplet-darwin-x64" "1.5.3" + "@drop-oss/droplet-linux-arm64-gnu" "1.5.3" + "@drop-oss/droplet-linux-arm64-musl" "1.5.3" + "@drop-oss/droplet-linux-riscv64-gnu" "1.5.3" + "@drop-oss/droplet-linux-x64-gnu" "1.5.3" + "@drop-oss/droplet-linux-x64-musl" "1.5.3" + "@drop-oss/droplet-win32-arm64-msvc" "1.5.3" + "@drop-oss/droplet-win32-x64-msvc" "1.5.3" "@emnapi/core@^1.4.3": version "1.4.3"