mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
experiments with saves
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
from config.config_manager import config_manager as cm
|
||||
from decorators.auth import protected_route
|
||||
from endpoints.responses import MessageResponse
|
||||
from endpoints.responses.assets import UploadedSavesResponse
|
||||
from endpoints.responses.assets import UploadedSavesResponse, SaveSchema
|
||||
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
|
||||
from handler import db_save_handler, fs_asset_handler, db_rom_handler
|
||||
from handler.scan_handler import scan_save
|
||||
from logger.logger import log
|
||||
from config import LIBRARY_BASE_PATH
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -55,9 +56,24 @@ def add_saves(
|
||||
# pass
|
||||
|
||||
|
||||
# @protected_route(router.put, "/saves/{id}", ["assets.write"])
|
||||
# def update_save(request: Request, id: int) -> MessageResponse:
|
||||
# pass
|
||||
@protected_route(router.put, "/saves/{id}", ["assets.write"])
|
||||
async def update_save(request: Request, id: int) -> SaveSchema:
|
||||
data = await request.form()
|
||||
|
||||
db_save = db_save_handler.get_save(id)
|
||||
if not db_save:
|
||||
error = f"Save with ID {id} not found"
|
||||
log.error(error)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
|
||||
|
||||
if "file" in data:
|
||||
file: UploadFile = data["file"]
|
||||
fs_asset_handler._write_file(
|
||||
file=file, path=f"{LIBRARY_BASE_PATH}/{db_save.file_path}"
|
||||
)
|
||||
db_save_handler.update_save(db_save.id, {"file_size_bytes": file.size})
|
||||
|
||||
return db_save
|
||||
|
||||
|
||||
@protected_route(router.post, "/saves/delete", ["assets.write"])
|
||||
@@ -84,7 +100,9 @@ async def delete_saves(request: Request) -> MessageResponse:
|
||||
log.info(f"Deleting {save.file_name} from filesystem")
|
||||
|
||||
try:
|
||||
fs_asset_handler.remove_file(file_name=save.file_name, file_path=save.file_path)
|
||||
fs_asset_handler.remove_file(
|
||||
file_name=save.file_name, file_path=save.file_path
|
||||
)
|
||||
except FileNotFoundError:
|
||||
error = f"Save file {save.file_name} not found for platform {save.rom.platform_slug}"
|
||||
log.error(error)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import secrets
|
||||
from functools import cached_property
|
||||
|
||||
from models.base import BaseModel
|
||||
@@ -33,7 +34,7 @@ class BaseAsset(BaseModel):
|
||||
|
||||
@cached_property
|
||||
def download_path(self) -> str:
|
||||
return f"/api/raw/{self.full_path}?timestamp={self.updated_at}"
|
||||
return f"/api/raw/{self.full_path}?s={secrets.token_hex(8)}"
|
||||
|
||||
|
||||
class Save(BaseAsset):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
import secrets
|
||||
from functools import cached_property
|
||||
|
||||
from config import (
|
||||
@@ -85,7 +86,7 @@ class Rom(BaseModel):
|
||||
|
||||
@cached_property
|
||||
def download_path(self) -> str:
|
||||
return f"/api/raw/{self.full_path}"
|
||||
return f"/api/raw/{self.full_path}?s={secrets.token_hex(8)}"
|
||||
|
||||
@cached_property
|
||||
def has_cover(self) -> bool:
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"axios": "^1.6.0",
|
||||
"core-js": "^3.8.3",
|
||||
"cronstrue": "^2.31.0",
|
||||
"emulatorjs": "github:GAntoine/emulatorjs",
|
||||
"emulatorjs": "github:GAntoine/emulatorjs#main",
|
||||
"file-saver": "^2.0.5",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -3191,7 +3191,7 @@
|
||||
},
|
||||
"node_modules/emulatorjs": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "git+ssh://git@github.com/GAntoine/emulatorjs.git#8015836f1dbed7d75b7ab77bf94669e58a77bee0",
|
||||
"resolved": "git+ssh://git@github.com/GAntoine/emulatorjs.git#8dfbfb0f32e94c77d49492fa34d27723a29d175e",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"http-server": "^14.1.1"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"axios": "^1.6.0",
|
||||
"core-js": "^3.8.3",
|
||||
"cronstrue": "^2.31.0",
|
||||
"emulatorjs": "github:GAntoine/emulatorjs",
|
||||
"emulatorjs": "github:GAntoine/emulatorjs#main",
|
||||
"file-saver": "^2.0.5",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
|
||||
@@ -47,9 +47,12 @@ async function deleteAssets() {
|
||||
});
|
||||
|
||||
result
|
||||
.then(({ data }) => {
|
||||
if (romRef.value) {
|
||||
romRef.value[assetType.value] = data;
|
||||
.then(() => {
|
||||
if (romRef.value?.[assetType.value]) {
|
||||
const deletedAssetIds = assets.value.map((asset) => asset.id);
|
||||
romRef.value[assetType.value] = romRef.value[assetType.value].filter(
|
||||
(asset) => !deletedAssetIds.includes(asset.id)
|
||||
);
|
||||
romsStore.update(romRef.value);
|
||||
emitter?.emit("romUpdated", romRef.value);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,19 @@ async function uploadSaves({ rom, saves }: { rom: Rom; saves: File[] }) {
|
||||
});
|
||||
}
|
||||
|
||||
async function updateSave({
|
||||
save,
|
||||
file,
|
||||
}: {
|
||||
save: SaveSchema;
|
||||
file: File;
|
||||
}): Promise<{ data: SaveSchema }> {
|
||||
var formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
return api.put(`/saves/${save.id}`, formData);
|
||||
}
|
||||
|
||||
async function deleteSaves({
|
||||
saves,
|
||||
deleteFromFs,
|
||||
@@ -30,5 +43,6 @@ async function deleteSaves({
|
||||
|
||||
export default {
|
||||
deleteSaves,
|
||||
updateSave,
|
||||
uploadSaves,
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ref, onMounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import romApi from "@/services/api/rom";
|
||||
import stateApi from "@/services/api/state";
|
||||
import saveApi, { saveApi as api } from "@/services/api/save";
|
||||
import type { Rom } from "@/stores/roms";
|
||||
import type { SaveSchema, StateSchema } from "@/__generated__";
|
||||
import { formatBytes } from "@/utils";
|
||||
@@ -39,6 +40,8 @@ declare global {
|
||||
EJS_onGameStart: () => void;
|
||||
EJS_onSaveState: (args: { screenshot: File; state: File }) => void;
|
||||
EJS_onLoadState: () => void;
|
||||
EJS_onSaveSave: (args: { screenshot: File; save: File }) => void;
|
||||
EJS_onLoadSave: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +54,7 @@ window.EJS_pathtodata = "/assets/emulatorjs/";
|
||||
window.EJS_color = "#A453FF";
|
||||
window.EJS_alignStartButton = "center";
|
||||
window.EJS_startOnLoaded = true;
|
||||
window.EJS_fullscreenOnLoaded = true;
|
||||
window.EJS_fullscreenOnLoaded = false;
|
||||
window.EJS_defaultOptions = {
|
||||
"save-state-location": "browser",
|
||||
};
|
||||
@@ -72,6 +75,22 @@ function buildStateName(rom: Rom): string {
|
||||
return stateName;
|
||||
}
|
||||
|
||||
function buildSaveName(rom: Rom): string {
|
||||
const saves = rom.saves.map((s) => s.file_name);
|
||||
const romName = rom.file_name.replace(EXTENSION_REGEX, "").trim();
|
||||
let saveName = `${romName}.srm`;
|
||||
if (!saves.includes(saveName)) return saveName;
|
||||
|
||||
let i = 2;
|
||||
saveName = `${romName} (${i}).srm`;
|
||||
while (saves.includes(saveName)) {
|
||||
i++;
|
||||
saveName = `${romName} (${i}).srm`;
|
||||
}
|
||||
|
||||
return saveName;
|
||||
}
|
||||
|
||||
window.EJS_onSaveState = function ({
|
||||
state,
|
||||
}: {
|
||||
@@ -115,7 +134,69 @@ window.EJS_onSaveState = function ({
|
||||
}
|
||||
};
|
||||
|
||||
async function getSave(): Promise<Uint8Array> {
|
||||
if (saveRef.value) {
|
||||
const { data } = await api.get(saveRef.value.download_path.replace("/api", ""));
|
||||
var enc = new TextEncoder();
|
||||
return enc.encode(data);
|
||||
} else {
|
||||
const file = await window.EJS_emulator.selectFile();
|
||||
return new Uint8Array(await file.arrayBuffer());
|
||||
}
|
||||
};
|
||||
|
||||
window.EJS_onLoadSave = async function () {
|
||||
const sav = await getSave();
|
||||
const FS = window.EJS_emulator.Module.FS;
|
||||
const path = window.EJS_emulator.gameManager.getSaveFilePath();
|
||||
const paths = path.split("/");
|
||||
let cp = "";
|
||||
for (let i=0; i<paths.length-1; i++) {
|
||||
if (paths[i] === "") continue;
|
||||
cp += "/"+paths[i];
|
||||
if (!FS.analyzePath(cp).exists) FS.mkdir(cp);
|
||||
}
|
||||
if (FS.analyzePath(path).exists) FS.unlink(path);
|
||||
FS.writeFile(path, sav);
|
||||
window.EJS_emulator.gameManager.loadSaveFiles();
|
||||
};
|
||||
|
||||
window.EJS_onSaveSave = function ({
|
||||
save,
|
||||
}: {
|
||||
screenshot: File;
|
||||
save: File;
|
||||
}) {
|
||||
if (saveRef.value) {
|
||||
saveApi
|
||||
.updateSave({
|
||||
save: saveRef.value,
|
||||
file: new File([save], saveRef.value.file_name, {
|
||||
type: "application/octet-stream",
|
||||
}),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
saveRef.value = data;
|
||||
});
|
||||
} else if (rom.value) {
|
||||
saveApi
|
||||
.uploadSaves({
|
||||
rom: rom.value,
|
||||
saves: [
|
||||
new File([save], buildSaveName(rom.value), {
|
||||
type: "application/octet-stream",
|
||||
}),
|
||||
],
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (rom.value) rom.value.saves = data.saves;
|
||||
saveRef.value = data.saves.pop() ?? null;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.EJS_onGameStart = () => {
|
||||
if (saveRef.value) window.EJS_onLoadSave();
|
||||
gameRunning.value = true;
|
||||
};
|
||||
|
||||
@@ -147,7 +228,6 @@ function onPlay() {
|
||||
<v-col v-if="rom && !gameRunning" class="v-col-3">
|
||||
<v-select
|
||||
clearable
|
||||
disabled
|
||||
label="Save"
|
||||
v-model="saveRef"
|
||||
:items="
|
||||
|
||||
Reference in New Issue
Block a user