Skip to content

Commit

Permalink
fix(achievements): linux more reliable
Browse files Browse the repository at this point in the history
  • Loading branch information
prostarz committed Jan 8, 2025
1 parent 6e65f6a commit 5707081
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 58 deletions.
109 changes: 58 additions & 51 deletions backend/handlers/achievements/locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Cracker } from "@/@types";
import { AchievementFile } from "@/@types/achievements/types";
import { app } from "electron";
import { existsSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { join, resolve } from "node:path";
import { logger } from "../logging";

type PathType =
Expand All @@ -20,13 +20,22 @@ interface FilePath {

class AchievementFileLocator {
private static isWindows = process.platform === "win32";
private static user = !this.isWindows
? app.getPath("home").split("/").pop()
: undefined;
private static user = this.isWindows
? undefined
: app.getPath("home").split("/").pop();

private static winePrefix =
process.env.WINEPREFIX || join(app.getPath("home"), ".wine");

private static validateWinePrefix(): void {
if (!existsSync(this.winePrefix)) {
logger.log(
"warn",
`Wine prefix not found at ${this.winePrefix}. Check WINEPREFIX environment variable.`
);
}
}

private static readonly crackerPaths: Readonly<Record<Cracker, FilePath[]>> =
Object.freeze({
codex: [
Expand Down Expand Up @@ -253,17 +262,6 @@ class AchievementFileLocator {
],
});

/**
* Allows updating the wine prefix dynamically.
* @param newPrefix The new custom Wine prefix path.
*/
static setWinePrefix(newPrefix: string): void {
if (!existsSync(newPrefix)) {
throw new Error(`Specified Wine prefix does not exist: ${newPrefix}`);
}
this.winePrefix = newPrefix;
}

private static getSystemPath(type: PathType): string {
const basePaths = {
appData: this.isWindows
Expand All @@ -272,7 +270,7 @@ class AchievementFileLocator {
this.winePrefix,
"drive_c",
"users",
this.user || "",
this.user || "unknown",
"AppData",
"Roaming"
),
Expand All @@ -282,7 +280,7 @@ class AchievementFileLocator {
this.winePrefix,
"drive_c",
"users",
this.user || "",
this.user || "unknown",
"Documents"
),
publicDocuments: this.isWindows
Expand All @@ -294,7 +292,7 @@ class AchievementFileLocator {
this.winePrefix,
"drive_c",
"users",
this.user || "",
this.user || "unknown",
"AppData",
"Local"
),
Expand All @@ -304,37 +302,19 @@ class AchievementFileLocator {
winePrefix: this.winePrefix,
};

return (
basePaths[type] ||
(() => {
throw new Error(`Unknown path type: ${type}`);
})()
);
}

static getCrackerPath(cracker: Cracker): FilePath[] {
return this.crackerPaths[cracker] || [];
}

private static replacePlaceholders(
path: string,
gameStoreId: string
): string {
if (!gameStoreId) {
throw new Error("Invalid gameStoreId provided");
const path = basePaths[type];
if (!path) {
throw new Error(`Unknown path type: ${type}`);
}
return path.replace(/<game_store_id>/g, gameStoreId);
return resolve(path);
}

private static buildFilePath(
folderPath: string,
fileLocations: string[],
gameStoreId: string
): string {
const mappedLocations = fileLocations.map((location) =>
this.replacePlaceholders(location, gameStoreId)
);
return join(folderPath, ...mappedLocations);
static setWinePrefix(newPrefix: string): void {
if (!existsSync(newPrefix)) {
throw new Error(`Specified Wine prefix does not exist: ${newPrefix}`);
}
this.winePrefix = newPrefix;
this.validateWinePrefix();
}

static findAllAchievementFiles(
Expand All @@ -343,27 +323,43 @@ class AchievementFileLocator {
if (winePrefix) this.setWinePrefix(winePrefix);

const gameAchievementFiles = new Map<string, AchievementFile[]>();
console.log(`Searching for achievement files`);

logger.log(
"info",
`Searching for achievement files for platform: ${process.platform}`
);

for (const [cracker, paths] of Object.entries(this.crackerPaths) as [
Cracker,
FilePath[],
][]) {
paths.forEach(
({ achievement_folder_location, achievement_file_location }) => {
if (!existsSync(achievement_folder_location)) return;

console.log(`${achievement_folder_location}`);
if (!existsSync(achievement_folder_location)) {
logger.log(
"debug",
`Folder not found: ${achievement_folder_location}`
);
return;
}

logger.log(
"debug",
`Processing folder: ${achievement_folder_location}`
);
const gameStoreIds = readdirSync(achievement_folder_location);

gameStoreIds.forEach((gameStoreId) => {
const filePath = this.buildFilePath(
achievement_folder_location,
achievement_file_location,
gameStoreId
);

if (!existsSync(filePath)) return;
if (!existsSync(filePath)) {
logger.log("debug", `File not found: ${filePath}`);
return;
}

const achievementFile: AchievementFile = {
cracker,
Expand All @@ -383,6 +379,17 @@ class AchievementFileLocator {
return gameAchievementFiles;
}

private static buildFilePath(
folderPath: string,
fileLocations: string[],
gameStoreId: string
): string {
const mappedLocations = fileLocations.map((location) =>
location.replace(/<game_store_id>/g, gameStoreId)
);
return join(folderPath, ...mappedLocations);
}

static findAchievementFiles(
gameStoreId: string,
winePrefix?: string | null
Expand Down
3 changes: 2 additions & 1 deletion backend/handlers/achievements/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ class AchievementParser {

private processFlatFileAchievements(filePath: string): UnlockedAchievement[] {
try {
const achievementFiles = readdirSync(filePath);
const normalizedPath = path.normalize(filePath);
const achievementFiles = readdirSync(normalizedPath);
return achievementFiles.map((filename) => ({
name: filename,
unlockTime: Date.now(),
Expand Down
32 changes: 26 additions & 6 deletions backend/handlers/achievements/watcher.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
import { FSWatcher, watch, WatchListener } from "node:fs";
import { FSWatcher, statSync, watch, WatchListener } from "node:fs";
import { platform } from "node:os";

class AchievementWatcher {
private filePath: string;
private watcher: FSWatcher | null = null;
private isLinux: boolean = platform() === "linux";

constructor(filePath: string) {
if (!filePath) {
throw new Error("filePath are required.");
throw new Error("filePath is required.");
}
this.filePath = filePath;

// Validate file path exists and is accessible
try {
const stats = statSync(this.filePath);
if (!stats.isFile()) {
throw new Error("Provided path is not a file.");
}
} catch (err) {
throw new Error(`Invalid file path: ${(err as Error).message}`);
}
}

/** Initializes the watcher if not already started */
start(callback?: WatchListener<string>): void {
if (this.watcher) {
console.warn("Watcher already running. Restarting...");
this.restart();
this.restart(callback);
return;
}

console.log(`Watching file: ${this.filePath}`);
this.watcher = watch(this.filePath, callback);
if (this.isLinux) {
console.log("Using Linux-compatible watcher settings.");
this.watcher = watch(this.filePath, { persistent: true }, callback);
} else {
console.log("Using default watcher settings.");
this.watcher = watch(this.filePath, callback);
}
}

/** Closes and nullifies the watcher */
Expand All @@ -33,9 +52,9 @@ class AchievementWatcher {
}

/** Restarts the watcher */
restart(): void {
restart(callback?: WatchListener<string>): void {
this.destroy();
this.start();
this.start(callback);
}

/** Attaches an event listener to the watcher */
Expand Down Expand Up @@ -72,6 +91,7 @@ class AchievementWatcher {
console.log("Watcher Status:");
console.log(`File Path: ${this.filePath}`);
console.log(`Running: ${this.isRunning()}`);
console.log(`Platform: ${this.isLinux ? "Linux" : "Other (Windows/Mac)"}`);
}

/** Ensures that the watcher is initialized before performing operations */
Expand Down

0 comments on commit 5707081

Please sign in to comment.