2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-10-19 04:55:19 +00:00

feat: unlink fastfiles via ModMan

This commit is contained in:
Jan Laupetin
2025-10-11 18:56:11 +01:00
parent f53195d4bd
commit d86b70e006
8 changed files with 174 additions and 13 deletions

View File

@@ -1,5 +1,6 @@
#include "Binds.h"
#include "UnlinkingBinds.h"
#include "Web/Binds/DialogBinds.h"
#include "ZoneBinds.h"
@@ -8,6 +9,7 @@ namespace ui
void RegisterAllBinds(webview::webview& wv)
{
RegisterDialogHandlerBinds(wv);
RegisterUnlinkingBinds(wv);
RegisterZoneBinds(wv);
}
} // namespace ui

View File

@@ -0,0 +1,93 @@
#include "UnlinkingBinds.h"
#include "Context/ModManContext.h"
#include "IObjWriter.h"
#include "SearchPath/OutputPathFilesystem.h"
#include "SearchPath/SearchPaths.h"
#include "Utils/PathUtils.h"
#include "Web/UiCommunication.h"
#include "Json/JsonExtension.h"
#include <filesystem>
namespace fs = std::filesystem;
namespace
{
class ZoneLoadedDto
{
public:
std::string zoneName;
std::string filePath;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zoneName, filePath);
class ZoneUnloadedDto
{
public:
std::string zoneName;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName);
result::Expected<NoResult, std::string> UnlinkZoneInDbThread(const std::string& zoneName)
{
const auto& context = ModManContext::Get().m_fast_file;
const auto existingZone = std::ranges::find_if(context.m_loaded_zones,
[&zoneName](const std::unique_ptr<Zone>& zone)
{
return zone->m_name == zoneName;
});
if (existingZone == context.m_loaded_zones.end())
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
const auto& zone = *existingZone->get();
const auto* objWriter = IObjWriter::GetObjWriterForGame(zone.m_game_id);
const auto outputFolderPath = fs::path(utils::GetExecutablePath()).parent_path() / "zone_dump" / zoneName;
const auto outputFolderPathStr = outputFolderPath.string();
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
SearchPaths searchPaths;
AssetDumpingContext dumpingContext(zone, outputFolderPathStr, outputFolderOutputPath, searchPaths);
objWriter->DumpZone(dumpingContext);
return NoResult();
}
void UnlinkZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
{
ModManContext::Get().m_db_thread.Dispatch(
[&wv, id, zoneName]
{
auto result = UnlinkZoneInDbThread(zoneName);
if (result)
{
con::debug("Unlinked zone \"{}\"", zoneName);
ui::PromiseResolve(wv, id, true);
}
else
{
con::warn("Failed to unlink zone \"{}\": {}", zoneName, result.error());
ui::PromiseReject(wv, id, std::move(result).error());
}
});
}
} // namespace
namespace ui
{
void RegisterUnlinkingBinds(webview::webview& wv)
{
BindAsync<std::string>(wv,
"unlinkZone",
[&wv](const std::string& id, std::string zoneName)
{
UnlinkZone(wv, id, std::move(zoneName));
});
}
} // namespace ui

View File

@@ -0,0 +1,8 @@
#pragma once
#include "Web/WebViewLib.h"
namespace ui
{
void RegisterUnlinkingBinds(webview::webview& wv);
} // namespace ui

View File

@@ -33,7 +33,12 @@ namespace
if (maybeZone)
{
ui::PromiseResolve(wv, id, true);
ui::PromiseResolve(wv,
id,
ZoneLoadedDto{
.zoneName = maybeZone.value()->m_name,
.filePath = path,
});
con::debug("Loaded zone \"{}\"", maybeZone.value()->m_name);
}
else

View File

@@ -1,21 +1,29 @@
<script setup lang="ts">
import { ref } from "vue";
import { computed, ref } from "vue";
import { webviewBinds } from "@/native";
import { useZoneStore } from "@/stores/ZoneStore";
import SpinningLoader from "@/components/SpinningLoader.vue";
const zoneStore = useZoneStore();
const lastPath = ref("");
const loadingFastFile = ref(false);
const unlinkingFastFile = ref(false);
async function onOpenFastfileClick() {
lastPath.value =
(await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] })) ?? "";
const performingAction = computed<boolean>(() => loadingFastFile.value || unlinkingFastFile.value);
async function openFastFileSelect() {
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
}
async function onOpenFastFileClick() {
if (performingAction.value) return;
const fastFilePath = await openFastFileSelect();
if (!fastFilePath) return;
loadingFastFile.value = true;
webviewBinds
.loadFastFile(lastPath.value)
.loadFastFile(fastFilePath)
.catch((e: string) => {
console.error("Failed to load fastfile:", e);
})
@@ -24,6 +32,36 @@ async function onOpenFastfileClick() {
});
}
async function onUnlinkFastFileClick() {
if (performingAction.value) return;
const fastFilePath = await openFastFileSelect();
if (!fastFilePath) return;
try {
unlinkingFastFile.value = true;
let loadedZoneName: string;
try {
loadedZoneName = (await webviewBinds.loadFastFile(fastFilePath)).zoneName;
} catch (e: unknown) {
console.error("Failed to load fastfile:", e as string);
return;
}
try {
await webviewBinds.unlinkZone(loadedZoneName);
} catch (e: unknown) {
console.error("Failed to unlink fastfile:", e as string);
return;
} finally {
webviewBinds.unloadZone(loadedZoneName);
}
} finally {
unlinkingFastFile.value = false;
}
}
function onUnloadClicked(zoneName: string) {
webviewBinds.unloadZone(zoneName).catch((e: string) => {
console.error("Failed to unload zone:", e);
@@ -36,18 +74,23 @@ function onUnloadClicked(zoneName: string) {
<h1>Welcome to ModMan</h1>
<small>Nothing to see here yet, this is mainly for testing</small>
<p>
<button :disabled="loadingFastFile" @click="onOpenFastfileClick">
<div class="actions">
<button :disabled="performingAction" @click="onOpenFastFileClick">
<SpinningLoader v-if="loadingFastFile" class="loading" />
<span>Load fastfile</span>
</button>
</p>
<button :disabled="performingAction" @click="onUnlinkFastFileClick">
<SpinningLoader v-if="unlinkingFastFile" class="loading" />
<span>Unlink fastfile</span>
</button>
</div>
<div>
<h3>Loaded zones:</h3>
<div class="zone-list">
<div v-for="zone in zoneStore.loadedZones" :key="zone" class="zone">
<span>{{ zone }}</span>
<button @click="onUnloadClicked(zone)">Unload</button>
<button :disabled="performingAction" @click="onUnloadClicked(zone)">Unload</button>
</div>
</div>
</div>
@@ -55,6 +98,12 @@ function onUnloadClicked(zoneName: string) {
</template>
<style scoped>
.actions {
display: flex;
justify-content: center;
column-gap: 0.5em;
}
.loading {
margin-right: 0.2em;
}

View File

@@ -0,0 +1,3 @@
export interface UnlinkingBinds {
unlinkZone(zoneName: string): Promise<void>;
}

View File

@@ -8,7 +8,7 @@ export interface ZoneUnloadedDto {
}
export interface ZoneBinds {
loadFastFile(path: string): Promise<void>;
loadFastFile(path: string): Promise<ZoneLoadedDto>;
unloadZone(zoneName: string): Promise<void>;
}

View File

@@ -1,7 +1,8 @@
import type { DialogBinds } from "./DialogBinds";
import type { UnlinkingBinds } from "./UnlinkingBinds";
import type { ZoneBinds, ZoneEventMap } from "./ZoneBinds";
export type NativeMethods = DialogBinds & ZoneBinds;
export type NativeMethods = DialogBinds & UnlinkingBinds & ZoneBinds;
type NativeEventMap = ZoneEventMap;