diff --git a/src/main/constants.ts b/src/main/constants.ts index 48390250..8650defa 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -1,23 +1,24 @@ import { app } from "electron"; import path from "node:path"; +import { SystemPath } from "./services/system-path"; export const LUDUSAVI_MANIFEST_URL = "https://cdn.losbroxas.org/manifest.yaml"; -export const defaultDownloadsPath = app.getPath("downloads"); +export const defaultDownloadsPath = SystemPath.getPath("downloads"); export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging"); export const levelDatabasePath = path.join( - app.getPath("userData"), + SystemPath.getPath("userData"), `hydra-db${isStaging ? "-staging" : ""}` ); export const commonRedistPath = path.join( - app.getPath("userData"), + SystemPath.getPath("userData"), "CommonRedist" ); -export const logsPath = path.join(app.getPath("userData"), "logs"); +export const logsPath = path.join(SystemPath.getPath("userData"), "logs"); export const seedsPath = app.isPackaged ? path.join(process.resourcesPath, "seeds") @@ -27,7 +28,7 @@ export const achievementSoundPath = app.isPackaged ? path.join(process.resourcesPath, "achievement.wav") : path.join(__dirname, "..", "..", "resources", "achievement.wav"); -export const backupsPath = path.join(app.getPath("userData"), "Backups"); +export const backupsPath = path.join(SystemPath.getPath("userData"), "Backups"); export const appVersion = app.getVersion() + (isStaging ? "-staging" : ""); diff --git a/src/main/events/cloud-save/download-game-artifact.ts b/src/main/events/cloud-save/download-game-artifact.ts index 4fd31b44..364e253a 100644 --- a/src/main/events/cloud-save/download-game-artifact.ts +++ b/src/main/events/cloud-save/download-game-artifact.ts @@ -4,13 +4,13 @@ import * as tar from "tar"; import { registerEvent } from "../register-event"; import axios from "axios"; import os from "node:os"; -import { app } from "electron"; import path from "node:path"; import { backupsPath } from "@main/constants"; import type { GameShop } from "@types"; import YAML from "yaml"; import { normalizePath } from "@main/helpers"; +import { SystemPath } from "@main/services/system-path"; export interface LudusaviBackup { files: { @@ -35,7 +35,7 @@ const replaceLudusaviBackupWithCurrentUser = ( drives: Record; }; - const currentHomeDir = normalizePath(app.getPath("home")); + const currentHomeDir = normalizePath(SystemPath.getPath("home")); /* Renaming logic */ if (os.platform() === "win32") { @@ -84,7 +84,7 @@ const downloadGameArtifact = async ( homeDir: string; }>(`/profile/games/artifacts/${gameArtifactId}/download`); - const zipLocation = path.join(app.getPath("userData"), objectKey); + const zipLocation = path.join(SystemPath.getPath("userData"), objectKey); const backupPath = path.join(backupsPath, `${shop}-${objectId}`); if (fs.existsSync(backupPath)) { diff --git a/src/main/events/library/create-game-shortcut.ts b/src/main/events/library/create-game-shortcut.ts index 6e278871..0c7a75df 100644 --- a/src/main/events/library/create-game-shortcut.ts +++ b/src/main/events/library/create-game-shortcut.ts @@ -5,6 +5,7 @@ import { app } from "electron"; import { removeSymbolsFromName } from "@shared"; import { GameShop } from "@types"; import { gamesSublevel, levelKeys } from "@main/level"; +import { SystemPath } from "@main/services/system-path"; const createGameShortcut = async ( _event: Electron.IpcMainInvokeEvent, @@ -24,7 +25,7 @@ const createGameShortcut = async ( const options = { filePath, name: removeSymbolsFromName(game.title), - outputPath: app.getPath("desktop"), + outputPath: SystemPath.getPath("desktop"), }; return createDesktopShortcut({ diff --git a/src/main/events/user-preferences/auto-launch.ts b/src/main/events/user-preferences/auto-launch.ts index 0b1bba21..dc5e2a02 100644 --- a/src/main/events/user-preferences/auto-launch.ts +++ b/src/main/events/user-preferences/auto-launch.ts @@ -4,9 +4,10 @@ import { app } from "electron"; import path from "path"; import fs from "node:fs"; import { logger } from "@main/services"; +import { SystemPath } from "@main/services/system-path"; const windowsStartupPath = path.join( - app.getPath("appData"), + SystemPath.getPath("appData"), "Microsoft", "Windows", "Start Menu", diff --git a/src/main/main.ts b/src/main/main.ts index 5869ab31..fa20979a 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -9,8 +9,11 @@ import { levelKeys, db } from "./level"; import type { UserPreferences } from "@types"; import { TorBoxClient } from "./services/download/torbox"; import { CommonRedistManager } from "./services/common-redist-manager"; +import { SystemPath } from "./services/system-path"; export const loadState = async () => { + SystemPath.checkIfPathsAreAvailable(); + const userPreferences = await db.get( levelKeys.userPreferences, { diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index 7c0660cc..9fb1977d 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -1,26 +1,26 @@ import path from "node:path"; import fs from "node:fs"; -import { app } from "electron"; import type { Game, AchievementFile } from "@types"; import { Cracker } from "@shared"; import { achievementsLogger } from "../logger"; +import { SystemPath } from "../system-path"; const getAppDataPath = () => { if (process.platform === "win32") { - return app.getPath("appData"); + return SystemPath.getPath("appData"); } - const user = app.getPath("home").split("/").pop(); + const user = SystemPath.getPath("home").split("/").pop(); return path.join("drive_c", "users", user || "", "AppData", "Roaming"); }; const getDocumentsPath = () => { if (process.platform === "win32") { - return app.getPath("documents"); + return SystemPath.getPath("documents"); } - const user = app.getPath("home").split("/").pop(); + const user = SystemPath.getPath("home").split("/").pop(); return path.join("drive_c", "users", user || "", "Documents"); }; @@ -38,7 +38,7 @@ const getLocalAppDataPath = () => { return path.join(appData, "..", "Local"); } - const user = app.getPath("home").split("/").pop(); + const user = SystemPath.getPath("home").split("/").pop(); return path.join("drive_c", "users", user || "", "AppData", "Local"); }; diff --git a/src/main/services/cloud-sync.ts b/src/main/services/cloud-sync.ts index bc2ef0c1..77b4ac65 100644 --- a/src/main/services/cloud-sync.ts +++ b/src/main/services/cloud-sync.ts @@ -1,5 +1,4 @@ import { levelKeys, gamesSublevel, db } from "@main/level"; -import { app } from "electron"; import path from "node:path"; import * as tar from "tar"; import crypto from "node:crypto"; @@ -15,6 +14,7 @@ import axios from "axios"; import { Ludusavi } from "./ludusavi"; import { formatDate, SubscriptionRequiredError } from "@shared"; import i18next, { t } from "i18next"; +import { SystemPath } from "./system-path"; export class CloudSync { public static getBackupLabel(automatic: boolean) { @@ -102,7 +102,7 @@ export class CloudSync { shop, objectId, hostname: os.hostname(), - homeDir: normalizePath(app.getPath("home")), + homeDir: normalizePath(SystemPath.getPath("home")), downloadOptionTitle, platform: os.platform(), label, diff --git a/src/main/services/common-redist-manager.ts b/src/main/services/common-redist-manager.ts index e738ed25..29fc6bb1 100644 --- a/src/main/services/common-redist-manager.ts +++ b/src/main/services/common-redist-manager.ts @@ -4,8 +4,8 @@ import fs from "node:fs"; import cp from "node:child_process"; import path from "node:path"; import { logger } from "./logger"; -import { app } from "electron"; import { WindowManager } from "./window-manager"; +import { SystemPath } from "./system-path"; export class CommonRedistManager { private static readonly redistributables = [ @@ -18,7 +18,7 @@ export class CommonRedistManager { ]; private static readonly installationTimeout = 1000 * 60 * 5; // 5 minutes private static readonly installationLog = path.join( - app.getPath("temp"), + SystemPath.getPath("temp"), "common_redist_install.log" ); diff --git a/src/main/services/ludusavi.ts b/src/main/services/ludusavi.ts index 55b1d116..81dae6bd 100644 --- a/src/main/services/ludusavi.ts +++ b/src/main/services/ludusavi.ts @@ -8,9 +8,13 @@ import YAML from "yaml"; import ludusaviWorkerPath from "../workers/ludusavi.worker?modulePath"; import { LUDUSAVI_MANIFEST_URL } from "@main/constants"; +import { SystemPath } from "./system-path"; export class Ludusavi { - private static ludusaviPath = path.join(app.getPath("appData"), "ludusavi"); + private static ludusaviPath = path.join( + SystemPath.getPath("appData"), + "ludusavi" + ); private static ludusaviConfigPath = path.join( this.ludusaviPath, "config.yaml" diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index 79f200fe..673c6374 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -1,4 +1,4 @@ -import { Notification, app } from "electron"; +import { Notification } from "electron"; import { t } from "i18next"; import trayIcon from "@resources/tray-icon.png?asset"; import fs from "node:fs"; @@ -13,13 +13,14 @@ import { WindowManager } from "../window-manager"; import type { Game, UserPreferences } from "@types"; import { db, levelKeys } from "@main/level"; import { restartAndInstallUpdate } from "@main/events/autoupdater/restart-and-install-update"; +import { SystemPath } from "../system-path"; async function downloadImage(url: string | null) { if (!url) return undefined; if (!url.startsWith("http")) return undefined; const fileName = url.split("/").pop()!; - const outputPath = path.join(app.getPath("temp"), fileName); + const outputPath = path.join(SystemPath.getPath("temp"), fileName); const writer = fs.createWriteStream(outputPath); const response = await axios.get(url, { diff --git a/src/main/services/system-path.ts b/src/main/services/system-path.ts new file mode 100644 index 00000000..17359f32 --- /dev/null +++ b/src/main/services/system-path.ts @@ -0,0 +1,45 @@ +import { app, dialog } from "electron"; +import { logger } from "./logger"; + +export class SystemPath { + static readonly paths = { + userData: "userData", + downloads: "downloads", + documents: "documents", + desktop: "desktop", + home: "home", + appData: "appData", + temp: "temp", + }; + + static checkIfPathsAreAvailable() { + const paths = Object.keys(SystemPath.paths) as Array< + keyof typeof SystemPath.paths + >; + + paths.forEach((pathName) => { + try { + app.getPath(pathName); + } catch (error) { + logger.error(`Error getting path ${pathName}`); + if (error instanceof Error) { + logger.error(error.message, error.stack); + } + + dialog.showErrorBox( + `Hydra was not able to find path for '${pathName}' system folder`, + `Some functionalities may not work as expected.\nPlease check your system settings.` + ); + } + }); + } + + static getPath(pathName: keyof typeof SystemPath.paths): string { + try { + return app.getPath(pathName); + } catch (error) { + logger.error(`Error getting path: ${error}`); + return ""; + } + } +}