From f3b4898e9ce75ae653a21d67b3c453023c457beb Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 14:51:08 +0300 Subject: [PATCH] feat: proper cleanup of unused assets --- .../library/remove-game-from-library.ts | 53 +++++++++++++++++-- src/main/events/library/update-custom-game.ts | 38 +++++++++++++ .../library/update-game-custom-assets.ts | 29 ++++++++++ 3 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index 6a33ffaf..4868d588 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -1,6 +1,6 @@ import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; -import { gamesSublevel, levelKeys } from "@main/level"; +import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level"; import type { GameShop } from "@types"; const removeGameFromLibrary = async ( @@ -12,15 +12,62 @@ const removeGameFromLibrary = async ( const game = await gamesSublevel.get(gameKey); if (game) { - await gamesSublevel.put(gameKey, { + // Collect asset paths that need to be cleaned up before marking as deleted + const assetPathsToDelete: string[] = []; + + const assetUrls = game.shop === "custom" + ? [game.iconUrl, game.logoImageUrl, game.libraryHeroImageUrl] + : [game.customIconUrl, game.customLogoImageUrl, game.customHeroImageUrl]; + + assetUrls.forEach(url => { + if (url?.startsWith("local:")) { + assetPathsToDelete.push(url.replace("local:", "")); + } + }); + + + const updatedGame = { ...game, isDeleted: true, executablePath: null, - }); + ...(game.shop !== "custom" && { + customIconUrl: null, + customLogoImageUrl: null, + customHeroImageUrl: null, + }), + }; + + await gamesSublevel.put(gameKey, updatedGame); + + + if (game.shop !== "custom") { + const existingAssets = await gamesShopAssetsSublevel.get(gameKey); + if (existingAssets) { + const resetAssets = { + ...existingAssets, + title: existingAssets.title, + }; + await gamesShopAssetsSublevel.put(gameKey, resetAssets); + } + } if (game?.remoteId) { HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {}); } + + + if (assetPathsToDelete.length > 0) { + const fs = await import("fs"); + for (const assetPath of assetPathsToDelete) { + try { + if (fs.existsSync(assetPath)) { + await fs.promises.unlink(assetPath); + } + } catch (error) { + console.warn(`Failed to delete asset ${assetPath}:`, error); + } + } + } } }; diff --git a/src/main/events/library/update-custom-game.ts b/src/main/events/library/update-custom-game.ts index 6152c0df..168e0050 100644 --- a/src/main/events/library/update-custom-game.ts +++ b/src/main/events/library/update-custom-game.ts @@ -18,6 +18,30 @@ const updateCustomGame = async ( throw new Error("Game not found"); } + // Collect old asset paths that will be replaced + const oldAssetPaths: string[] = []; + + if (existingGame.iconUrl && iconUrl && existingGame.iconUrl !== iconUrl && existingGame.iconUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.iconUrl.replace("local:", "")); + } + if (existingGame.iconUrl && !iconUrl && existingGame.iconUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.iconUrl.replace("local:", "")); + } + + if (existingGame.logoImageUrl && logoImageUrl && existingGame.logoImageUrl !== logoImageUrl && existingGame.logoImageUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.logoImageUrl.replace("local:", "")); + } + if (existingGame.logoImageUrl && !logoImageUrl && existingGame.logoImageUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.logoImageUrl.replace("local:", "")); + } + + if (existingGame.libraryHeroImageUrl && libraryHeroImageUrl && existingGame.libraryHeroImageUrl !== libraryHeroImageUrl && existingGame.libraryHeroImageUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.libraryHeroImageUrl.replace("local:", "")); + } + if (existingGame.libraryHeroImageUrl && !libraryHeroImageUrl && existingGame.libraryHeroImageUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.libraryHeroImageUrl.replace("local:", "")); + } + const updatedGame = { ...existingGame, title, @@ -43,6 +67,20 @@ const updateCustomGame = async ( await gamesShopAssetsSublevel.put(gameKey, updatedAssets); } + // Manually delete specific old asset files instead of running full cleanup + if (oldAssetPaths.length > 0) { + const fs = await import("fs"); + for (const assetPath of oldAssetPaths) { + try { + if (fs.existsSync(assetPath)) { + await fs.promises.unlink(assetPath); + } + } catch (error) { + console.warn(`Failed to delete old asset ${assetPath}:`, error); + } + } + } + return updatedGame; }; diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 866cd60e..f8206904 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -18,6 +18,21 @@ const updateGameCustomAssets = async ( throw new Error("Game not found"); } + // Collect old custom asset paths that will be replaced + const oldAssetPaths: string[] = []; + + const assetPairs = [ + { existing: existingGame.customIconUrl, new: customIconUrl }, + { existing: existingGame.customLogoImageUrl, new: customLogoImageUrl }, + { existing: existingGame.customHeroImageUrl, new: customHeroImageUrl } + ]; + + assetPairs.forEach(({ existing, new: newUrl }) => { + if (existing && newUrl !== undefined && existing !== newUrl && existing.startsWith("local:")) { + oldAssetPaths.push(existing.replace("local:", "")); + } + }); + const updatedGame = { ...existingGame, title, @@ -39,6 +54,20 @@ const updateGameCustomAssets = async ( await gamesShopAssetsSublevel.put(gameKey, updatedAssets); } + // Manually delete specific old custom asset files instead of running full cleanup + if (oldAssetPaths.length > 0) { + const fs = await import("fs"); + for (const assetPath of oldAssetPaths) { + try { + if (fs.existsSync(assetPath)) { + await fs.promises.unlink(assetPath); + } + } catch (error) { + console.warn(`Failed to delete old custom asset ${assetPath}:`, error); + } + } + } + return updatedGame; };