Skip to content

Commit abec952

Browse files
authored
Various fixes (#186)
* fix: #181 * fix: use taskHandler as source of truth for imports * fix: task formatting * fix: zip downloads * feat: re-enable import version button on delete + lint
1 parent 9ff5410 commit abec952

14 files changed

Lines changed: 180 additions & 185 deletions

File tree

components/GameEditor/Version.vue

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,17 @@
2222
<!-- import games button -->
2323

2424
<NuxtLink
25-
:href="
26-
unimportedVersions.length > 0
27-
? `/admin/library/${game.id}/import`
28-
: ''
29-
"
25+
:href="canImport ? `/admin/library/${game.id}/import` : ''"
3026
type="button"
3127
:class="[
32-
unimportedVersions.length > 0
28+
canImport
3329
? 'bg-blue-600 hover:bg-blue-700'
3430
: 'bg-blue-800/50',
3531
'inline-flex w-fit items-center gap-x-2 rounded-md px-3 py-1 text-sm font-semibold font-display text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600',
3632
]"
3733
>
3834
{{
39-
unimportedVersions.length > 0
35+
canImport
4036
? $t("library.admin.import.version.import")
4137
: $t("library.admin.import.version.noVersions")
4238
}}
@@ -124,10 +120,16 @@ import { ExclamationCircleIcon } from "@heroicons/vue/24/outline";
124120
125121
// TODO implement UI for this page
126122
127-
defineProps<{ unimportedVersions: string[] }>();
123+
const props = defineProps<{ unimportedVersions: string[] }>();
128124
129125
const { t } = useI18n();
130126
127+
const hasDeleted = ref(false);
128+
129+
const canImport = computed(
130+
() => hasDeleted.value || props.unimportedVersions.length > 0,
131+
);
132+
131133
type GameAndVersions = GameModel & { versions: GameVersionModel[] };
132134
const game = defineModel<SerializeObject<GameAndVersions>>() as Ref<
133135
SerializeObject<GameAndVersions>
@@ -176,6 +178,7 @@ async function deleteVersion(versionName: string) {
176178
game.value.versions.findIndex((e) => e.versionName === versionName),
177179
1,
178180
);
181+
hasDeleted.value = true;
179182
} catch (e) {
180183
createModal(
181184
ModalType.Notification,

components/LanguageSelector.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818
</i18n-t>
1919
</NuxtLink>
2020

21-
<DevOnly
22-
><h1 class="mt-3 text-sm text-gray-500">{{ $t("welcome") }}</h1>
21+
<DevOnly>
22+
<h1 class="mt-3 text-sm text-gray-500">{{ $t("welcome") }}</h1>
2323
</DevOnly>
2424
</div>
2525
</template>
26+
27+
<script setup lang="ts">
28+
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/outline";
29+
</script>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
},
2222
"dependencies": {
2323
"@discordapp/twemoji": "^16.0.1",
24-
"@drop-oss/droplet": "1.6.0",
24+
"@drop-oss/droplet": "2.3.0",
2525
"@headlessui/vue": "^1.7.23",
2626
"@heroicons/vue": "^2.1.5",
2727
"@lobomfz/prismark": "0.0.3",

pages/admin/task/index.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
/>
4848
</div>
4949
<p class="mt-1 truncate text-sm text-zinc-400">
50-
{{ parseTaskLog(task.value.log.at(-1) ?? "").message }}
50+
{{ parseTaskLog(task.value.log.at(-1)).message }}
5151
</p>
5252
<NuxtLink
5353
type="button"
@@ -115,7 +115,7 @@
115115
{{ task.id }}
116116
</p>
117117
<p class="mt-1 truncate text-sm text-zinc-400">
118-
{{ parseTaskLog(task.log.at(-1) ?? "").message }}
118+
{{ parseTaskLog(task.log.at(-1)).message }}
119119
</p>
120120
<NuxtLink
121121
type="button"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
Warnings:
3+
4+
- The primary key for the `Task` table will be changed. If it partially fails, the table could be left without primary key constraint.
5+
6+
*/
7+
-- DropIndex
8+
DROP INDEX "GameTag_name_idx";
9+
10+
-- AlterTable
11+
ALTER TABLE "Task" DROP CONSTRAINT "Task_pkey",
12+
ADD CONSTRAINT "Task_pkey" PRIMARY KEY ("id", "started");
13+
14+
-- CreateIndex
15+
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));

prisma/models/task.prisma

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
model Task {
2-
id String @id
2+
id String
33
taskGroup String
44
name String
55
@@ -12,4 +12,6 @@ model Task {
1212
log String[]
1313
1414
acls String[]
15+
16+
@@id([id, started])
1517
}

server/api/v2/client/chunk.post.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { type } from "arktype";
22
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
33
import contextManager from "~/server/internal/downloads/coordinator";
44
import libraryManager from "~/server/internal/library";
5+
import { logger } from "~/server/internal/logging";
56

67
const GetChunk = type({
78
context: "string",
@@ -58,13 +59,25 @@ export default defineEventHandler(async (h3) => {
5859
statusCode: 500,
5960
statusMessage: "Failed to create read stream",
6061
});
62+
let length = 0;
6163
await gameReadStream.pipeTo(
6264
new WritableStream({
6365
write(chunk) {
6466
h3.node.res.write(chunk);
67+
length += chunk.length;
6568
},
6669
}),
6770
);
71+
72+
if (length != file.end - file.start) {
73+
logger.warn(
74+
`failed to read enough from ${file.filename}. read ${length}, required: ${file.end - file.start}`,
75+
);
76+
throw createError({
77+
statusCode: 500,
78+
statusMessage: "Failed to read enough from stream.",
79+
});
80+
}
6881
}
6982

7083
await h3.node.res.end();

server/internal/library/index.ts

Lines changed: 16 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@ import { GameNotFoundError, type LibraryProvider } from "./provider";
1515
import { logger } from "../logging";
1616
import type { GameModel } from "~/prisma/client/models";
1717

18+
export function createGameImportTaskId(libraryId: string, libraryPath: string) {
19+
return btoa(`import:${libraryId}:${libraryPath}`);
20+
}
21+
22+
export function createVersionImportTaskId(gameId: string, versionName: string) {
23+
return btoa(`import:${gameId}:${versionName}`);
24+
}
25+
1826
class LibraryManager {
1927
private libraries: Map<string, LibraryProvider<unknown>> = new Map();
2028

21-
private gameImportLocks: Map<string, Array<string>> = new Map(); // Library ID to Library Path
22-
private versionImportLocks: Map<string, Array<string>> = new Map(); // Game ID to Version Name
23-
2429
addLibrary(library: LibraryProvider<unknown>) {
2530
this.libraries.set(library.id(), library);
2631
}
@@ -58,12 +63,10 @@ class LibraryManager {
5863

5964
for (const [id, library] of this.libraries.entries()) {
6065
const providerGames = await library.listGames();
61-
const locks = this.gameImportLocks.get(id) ?? [];
6266
const providerUnimportedGames = providerGames.filter(
6367
(libraryPath) =>
64-
instanceGames[id] &&
65-
!instanceGames[id][libraryPath] &&
66-
!locks.includes(libraryPath),
68+
!instanceGames[id]?.[libraryPath] &&
69+
!taskHandler.hasTask(createGameImportTaskId(id, libraryPath)),
6770
);
6871
unimportedGames[id] = providerUnimportedGames;
6972
}
@@ -93,7 +96,7 @@ class LibraryManager {
9396
const unimportedVersions = versions.filter(
9497
(e) =>
9598
game.versions.findIndex((v) => v.versionName == e) == -1 &&
96-
!(this.versionImportLocks.get(game.id) ?? []).includes(e),
99+
!taskHandler.hasTask(createVersionImportTaskId(game.id, e)),
97100
);
98101
return unimportedVersions;
99102
} catch (e) {
@@ -177,7 +180,8 @@ class LibraryManager {
177180
for (const filename of files) {
178181
const basename = path.basename(filename);
179182
const dotLocation = filename.lastIndexOf(".");
180-
const ext = dotLocation == -1 ? "" : filename.slice(dotLocation);
183+
const ext =
184+
dotLocation == -1 ? "" : filename.slice(dotLocation).toLowerCase();
181185
for (const [platform, checkExts] of Object.entries(fileExts)) {
182186
for (const checkExt of checkExts) {
183187
if (checkExt != ext) continue;
@@ -215,70 +219,6 @@ class LibraryManager {
215219
}
216220
*/
217221

218-
/**
219-
* Locks the game so you can't be imported
220-
* @param libraryId
221-
* @param libraryPath
222-
*/
223-
async lockGame(libraryId: string, libraryPath: string) {
224-
let games = this.gameImportLocks.get(libraryId);
225-
if (!games) this.gameImportLocks.set(libraryId, (games = []));
226-
227-
if (!games.includes(libraryPath)) games.push(libraryPath);
228-
229-
this.gameImportLocks.set(libraryId, games);
230-
}
231-
232-
/**
233-
* Unlocks the game, call once imported
234-
* @param libraryId
235-
* @param libraryPath
236-
*/
237-
async unlockGame(libraryId: string, libraryPath: string) {
238-
let games = this.gameImportLocks.get(libraryId);
239-
if (!games) this.gameImportLocks.set(libraryId, (games = []));
240-
241-
if (games.includes(libraryPath))
242-
games.splice(
243-
games.findIndex((e) => e === libraryPath),
244-
1,
245-
);
246-
247-
this.gameImportLocks.set(libraryId, games);
248-
}
249-
250-
/**
251-
* Locks a version so it can't be imported
252-
* @param gameId
253-
* @param versionName
254-
*/
255-
async lockVersion(gameId: string, versionName: string) {
256-
let versions = this.versionImportLocks.get(gameId);
257-
if (!versions) this.versionImportLocks.set(gameId, (versions = []));
258-
259-
if (!versions.includes(versionName)) versions.push(versionName);
260-
261-
this.versionImportLocks.set(gameId, versions);
262-
}
263-
264-
/**
265-
* Unlocks the version, call once imported
266-
* @param libraryId
267-
* @param libraryPath
268-
*/
269-
async unlockVersion(gameId: string, versionName: string) {
270-
let versions = this.versionImportLocks.get(gameId);
271-
if (!versions) this.versionImportLocks.set(gameId, (versions = []));
272-
273-
if (versions.includes(gameId))
274-
versions.splice(
275-
versions.findIndex((e) => e === versionName),
276-
1,
277-
);
278-
279-
this.versionImportLocks.set(gameId, versions);
280-
}
281-
282222
async importVersion(
283223
gameId: string,
284224
versionName: string,
@@ -295,7 +235,7 @@ class LibraryManager {
295235
umuId: string;
296236
},
297237
) {
298-
const taskId = `import:${gameId}:${versionName}`;
238+
const taskId = createVersionImportTaskId(gameId, versionName);
299239

300240
const platform = parsePlatform(metadata.platform);
301241
if (!platform) return undefined;
@@ -309,8 +249,6 @@ class LibraryManager {
309249
const library = this.libraries.get(game.libraryId);
310250
if (!library) return undefined;
311251

312-
await this.lockVersion(gameId, versionName);
313-
314252
taskHandler.create({
315253
id: taskId,
316254
taskGroup: "import:game",
@@ -387,9 +325,6 @@ class LibraryManager {
387325

388326
progress(100);
389327
},
390-
async finally() {
391-
await libraryManager.unlockVersion(gameId, versionName);
392-
},
393328
});
394329

395330
return taskId;
@@ -403,7 +338,7 @@ class LibraryManager {
403338
) {
404339
const library = this.libraries.get(libraryId);
405340
if (!library) return undefined;
406-
return library.peekFile(game, version, filename);
341+
return await library.peekFile(game, version, filename);
407342
}
408343

409344
async readFile(
@@ -415,7 +350,7 @@ class LibraryManager {
415350
) {
416351
const library = this.libraries.get(libraryId);
417352
if (!library) return undefined;
418-
return library.readFile(game, version, filename, options);
353+
return await library.readFile(game, version, filename, options);
419354
}
420355
}
421356

0 commit comments

Comments
 (0)