diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index bd04fe6..611dc91 100644 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -6,6 +6,7 @@ const { configFeatures, featuresPath } = require("../config"); const { getThemes, reloadThemes } = require("./themeManager"); const config = require("../config"); const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator"); +const { userThemesPath } = require("../server/helpers/path.helpers"); const FEATURE_WHITELIST = new Set([ "autoUpdate", @@ -24,9 +25,7 @@ const FEATURE_WHITELIST = new Set([ const configFolderPath = featuresPath; -const themeFolderPath = config.localMode - ? path.join(__dirname, "..", "theme") - : path.join(process.resourcesPath, "theme"); +const themeFolderPath = userThemesPath; function registerIpcHandlers(getMainWindow) { diff --git a/main.js b/main.js index b985890..829a519 100644 --- a/main.js +++ b/main.js @@ -24,6 +24,7 @@ const { checkNativeDependencies } = require("./app/dependencyCheck"); const { updateYtDlp } = require("./app/ytDlpUpdater"); const { createMainWindow, getMainWindow } = require("./app/windowManager"); const { registerIpcHandlers } = require("./app/ipcHandlers"); +const { userThemesPath, initUserThemes } = require("./server/helpers/path.helpers"); app.setName("Freedom Loader"); app.setAppUserModelId("com.masteracnolo.freedomloader"); @@ -56,13 +57,9 @@ app.whenReady().then(async () => { setSplashProgress(1); // Starting server registerIpcHandlers(getMainWindow); - const themeFolderPath = config.localMode - ? path.join(__dirname, "theme") - : path.join(process.resourcesPath, "theme"); - setSplashProgress(2); // Loading themes - await initThemes(themeFolderPath); - + initUserThemes(); + await initThemes(userThemesPath); setSplashProgress(3); // Almost ready await createMainWindow(); diff --git a/package.json b/package.json index 6300b61..9fcef47 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "files": [ "**/*", "node_modules/**/*", - "!config/config.dev.json" + "!config/config.dev.json", + "!theme/**/*" ], "directories": { "buildResources": "build" diff --git a/server/helpers/path.helpers.js b/server/helpers/path.helpers.js index ed630c4..38efa9e 100644 --- a/server/helpers/path.helpers.js +++ b/server/helpers/path.helpers.js @@ -50,8 +50,74 @@ const binaryPaths = { deno: path.join(resourcesPath, "binaries", "deno.exe") }; +// Theme paths - themes stored in AppData to survive updates +const userThemesPath = path.join(app.getPath("userData"), "themes"); +const defaultThemesSourcePath = path.join(resourcesPath, "theme"); + +function initUserThemes() { + try { + if (!fs.existsSync(userThemesPath)) { + fs.mkdirSync(userThemesPath, { recursive: true }); + // Ensure folder is writable + fs.chmodSync(userThemesPath, 0o777); + logger.info("Created user themes directory"); + } + + // Copy default themes from resources to user directory if they don't exist + if (fs.existsSync(defaultThemesSourcePath)) { + const defaultThemes = fs.readdirSync(defaultThemesSourcePath); + for (const theme of defaultThemes) { + const srcPath = path.join(defaultThemesSourcePath, theme); + const destPath = path.join(userThemesPath, theme); + + if (!fs.existsSync(destPath)) { + try { + copyDirectory(srcPath, destPath); + fs.chmodSync(destPath, 0o777); + logger.info(`Copied default theme: ${theme}`); + } catch (copyErr) { + logger.warn(`Failed to copy theme ${theme}: ${copyErr.message}. Theme folder will be created, add theme files manually.`); + try { + fs.mkdirSync(destPath, { recursive: true }); + fs.chmodSync(destPath, 0o777); + } catch (mkdirErr) { + logger.warn(`Could not create theme directory ${theme}: ${mkdirErr.message}`); + } + } + } + } + } else { + logger.warn(`Default themes source path not found: ${defaultThemesSourcePath}`); + } + } catch (err) { + logger.error(`Failed to initialize user themes: ${err.message}`); + } +} + +function copyDirectory(src, dest) { + try { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest, { recursive: true, mode: 0o777 }); + } + + const files = fs.readdirSync(src); + for (const file of files) { + const srcFile = path.join(src, file); + const destFile = path.join(dest, file); + + if (fs.statSync(srcFile).isDirectory()) { + copyDirectory(srcFile, destFile); + } else { + fs.copyFileSync(srcFile, destFile); + } + } + } catch (err) { + throw new Error(`Copy error: ${err.message}`); + } +} + if (!userYtDlp){ logger.error("Missing YT-DLP")} if (!ffmpegPath){ logger.error("Missing FFMPEG")} if (!denoPath){ logger.error("Missing DENO")} -module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder }; +module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder, userThemesPath, initUserThemes };