Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: changing permission verify strategy #1374

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
31feb4a
feat: changing permission verify strategy
thegrannychaseroperation Jan 2, 2025
1cfe3dc
Merge branch 'main' into feature/check-directory-permission
thegrannychaseroperation Jan 2, 2025
1f42ebd
fix: ensure ipcHandles are loaded before create window
zamitto Jan 2, 2025
71decd9
feat: show featurebase dropdown changelog on bottom panel version
zamitto Jan 3, 2025
99364df
Merge branch 'main' into feature/check-directory-permission
zamitto Jan 3, 2025
0adcc73
feat: add optional game params back
zamitto Jan 5, 2025
74d4975
feat: handle open new browser window on link
zamitto Jan 5, 2025
9bb89a1
feat: remove unnecessary userPreferences findOne
zamitto Jan 5, 2025
47f7731
feat: prefer to play achievement sound in browser window if available
zamitto Jan 5, 2025
715c4a6
feat: update user language
zamitto Jan 5, 2025
92e641e
feat: sonar suggestions
zamitto Jan 5, 2025
097aff1
feat: delete hydra.db if migrations are corrupted
zamitto Jan 5, 2025
f682c56
feat: use fs already imported
zamitto Jan 5, 2025
fb66557
feat: reduce python log level
zamitto Jan 5, 2025
40552cb
Merge branch 'main' into feature/check-directory-permission
zamitto Jan 5, 2025
540fd80
feat: add featureBaseJwt to type
zamitto Jan 5, 2025
5ac0c04
fix: error handling
zamitto Jan 6, 2025
1d8a3c4
feat: undo knex error handling
zamitto Jan 6, 2025
1b4f962
feat: updating title bar z-index
thegrannychaseroperation Jan 6, 2025
abf9d9b
Merge branch 'feature/check-directory-permission' of github.com:hydra…
thegrannychaseroperation Jan 6, 2025
3f8a440
feat: adjust python log file
zamitto Jan 7, 2025
33e1de7
feat: check for updates every 60 minutes
zamitto Jan 7, 2025
16a3b1e
Merge branch 'main' into feature/check-directory-permission
zamitto Jan 7, 2025
8ef2377
feat: i18n
zamitto Jan 7, 2025
e4fefde
Merge branch 'main' into feature/check-directory-permission
thegrannychaseroperation Jan 10, 2025
8f1acb4
fix: missing first call on check for updates
zamitto Jan 10, 2025
5889f62
feat: add network logger file
zamitto Jan 10, 2025
04db9a7
feat: refactor check for updates to run loop on main process
zamitto Jan 11, 2025
64b10a0
feat: remove python log change
zamitto Jan 11, 2025
ce223a4
feat: update hook dependency
zamitto Jan 11, 2025
6698d2f
feat: remove unneeded async
zamitto Jan 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion python_rpc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
from http_downloader import HttpDownloader
from profile_image_processor import ProfileImageProcessor
import libtorrent as lt
import logging

log = logging.getLogger('werkzeug')

log.setLevel(logging.ERROR)
zamitto marked this conversation as resolved.
Show resolved Hide resolved

app = Flask(__name__)

Expand Down Expand Up @@ -94,7 +99,7 @@ def seed_status():

@app.route("/healthcheck", methods=["GET"])
def healthcheck():
return "", 200
return "ok", 200

@app.route("/process-list", methods=["GET"])
def process_list():
Expand Down
3 changes: 2 additions & 1 deletion src/locales/pt-BR/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@
"reset_achievements_description": "Isso irá resetar todas as conquistas de {{game}}",
"reset_achievements_title": "Tem certeza?",
"reset_achievements_success": "Conquistas resetadas com sucesso",
"reset_achievements_error": "Falha ao resetar conquistas"
"reset_achievements_error": "Falha ao resetar conquistas",
"no_write_permission": "Não é possível baixar nesse diretório. Clique aqui para saber mais."
},
"activation": {
"title": "Ativação",
Expand Down
20 changes: 13 additions & 7 deletions src/main/events/hardware/check-folder-write-permission.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import fs from "node:fs";
import path from "node:path";

import { registerEvent } from "../register-event";

const checkFolderWritePermission = async (
_event: Electron.IpcMainInvokeEvent,
path: string
) =>
new Promise((resolve) => {
fs.access(path, fs.constants.W_OK, (err) => {
resolve(!err);
});
});
testPath: string
) => {
Comment on lines 6 to +9
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: function is marked async but contains no await operations - should either use async fs operations or remove async keyword

const testFilePath = path.join(testPath, ".hydra-write-test");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: consider using a more unique filename with timestamp/uuid to avoid potential conflicts


try {
fs.writeFileSync(testFilePath, "");
fs.rmSync(testFilePath);
Comment on lines +13 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: using synchronous fs operations can block the main thread - consider using async writeFile/rm

return true;
} catch (err) {
return false;
}
Comment on lines +16 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: error is silently swallowed - should at least log it for debugging purposes

};

registerEvent("checkFolderWritePermission", checkFolderWritePermission);
7 changes: 7 additions & 0 deletions src/main/events/helpers/parse-launch-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const parseLaunchOptions = (params: string | null): string[] => {
if (!params) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: consider also checking for empty string case here to avoid unnecessary split operation

return [];
}

return params.split(" ");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: simple string split() will break arguments containing spaces (e.g. "C:\Program Files\Game.exe" or "-name "Player Name""). Consider using a more robust argument parser that handles quotes and escapes

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: multiple consecutive spaces will create empty strings in the output array. Should filter these out with .filter(Boolean) or similar

};
11 changes: 9 additions & 2 deletions src/main/events/library/open-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,30 @@ import { gameRepository } from "@main/repository";

import { registerEvent } from "../register-event";
import { shell } from "electron";
import { spawn } from "child_process";
import { parseExecutablePath } from "../helpers/parse-executable-path";
import { parseLaunchOptions } from "../helpers/parse-launch-options";

const openGame = async (
_event: Electron.IpcMainInvokeEvent,
gameId: number,
executablePath: string,
launchOptions: string | null
) => {
// TODO: revisit this for launchOptions
const parsedPath = parseExecutablePath(executablePath);
const parsedParams = parseLaunchOptions(launchOptions);

await gameRepository.update(
{ id: gameId },
{ executablePath: parsedPath, launchOptions }
);

shell.openPath(parsedPath);
if (parsedParams.length === 0) {
shell.openPath(parsedPath);
return;
}

spawn(parsedPath, parsedParams, { shell: false, detached: true });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: No error handling for spawn failures. Should wrap in try/catch and handle potential errors.

};

registerEvent("openGame", openGame);
2 changes: 1 addition & 1 deletion src/main/events/profile/update-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { omit } from "lodash-es";
import axios from "axios";
import { fileTypeFromFile } from "file-type";

const patchUserProfile = async (updateProfile: UpdateProfileRequest) => {
export const patchUserProfile = async (updateProfile: UpdateProfileRequest) => {
return HydraApi.patch<UserProfile>("/profile", updateProfile);
};

Expand Down
2 changes: 2 additions & 0 deletions src/main/events/user-preferences/update-user-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { registerEvent } from "../register-event";

import type { UserPreferences } from "@types";
import i18next from "i18next";
import { patchUserProfile } from "../profile/update-profile";

const updateUserPreferences = async (
_event: Electron.IpcMainInvokeEvent,
preferences: Partial<UserPreferences>
) => {
if (preferences.language) {
i18next.changeLanguage(preferences.language);
patchUserProfile({ language: preferences.language }).catch(() => {});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Empty catch block silently swallows errors from profile update. Should at least log the error for debugging.

}
Comment on lines 12 to 15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Local language change happens before profile update. If profile update fails, local and remote state will be out of sync.


return userPreferencesRepository.upsert(
Expand Down
5 changes: 3 additions & 2 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { knexClient, migrationConfig } from "./knex-client";
import { databaseDirectory } from "./constants";
import { PythonRPC } from "./services/python-rpc";
import { Aria2 } from "./services/aria2";
import { loadState } from "./main";

const { autoUpdater } = updater;

Expand Down Expand Up @@ -86,12 +87,12 @@ app.whenReady().then(async () => {

await dataSource.initialize();

await import("./main");

const userPreferences = await userPreferencesRepository.findOne({
where: { id: 1 },
});

await loadState(userPreferences);

if (userPreferences?.language) {
i18n.changeLanguage(userPreferences.language);
}
Expand Down
18 changes: 3 additions & 15 deletions src/main/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { DownloadManager, Ludusavi, startMainLoop } from "./services";
import {
downloadQueueRepository,
gameRepository,
userPreferencesRepository,
} from "./repository";
import { downloadQueueRepository, gameRepository } from "./repository";
import { UserPreferences } from "./entity";
import { RealDebridClient } from "./services/download/real-debrid";
import { HydraApi } from "./services/hydra-api";
Expand All @@ -12,8 +8,8 @@ import { Aria2 } from "./services/aria2";
import { Downloader } from "@shared";
import { IsNull, Not } from "typeorm";

const loadState = async (userPreferences: UserPreferences | null) => {
import("./events");
export const loadState = async (userPreferences: UserPreferences | null) => {
await import("./events");

Aria2.spawn();

Expand Down Expand Up @@ -49,11 +45,3 @@ const loadState = async (userPreferences: UserPreferences | null) => {

startMainLoop();
};

userPreferencesRepository
.findOne({
where: { id: 1 },
})
.then((userPreferences) => {
loadState(userPreferences);
});
22 changes: 11 additions & 11 deletions src/main/services/achievements/achievement-watcher-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,20 +144,20 @@ const processAchievementFileDiff = async (
export class AchievementWatcherManager {
private static hasFinishedMergingWithRemote = false;

public static watchAchievements = () => {
public static watchAchievements() {
if (!this.hasFinishedMergingWithRemote) return;

if (process.platform === "win32") {
return watchAchievementsWindows();
}

return watchAchievementsWithWine();
};
}

private static preProcessGameAchievementFiles = (
private static preProcessGameAchievementFiles(
game: Game,
gameAchievementFiles: AchievementFile[]
) => {
) {
const unlockedAchievements: UnlockedAchievement[] = [];
for (const achievementFile of gameAchievementFiles) {
const parsedAchievements = parseAchievementFile(
Expand Down Expand Up @@ -185,9 +185,9 @@ export class AchievementWatcherManager {
}

return mergeAchievements(game, unlockedAchievements, false);
};
}

private static preSearchAchievementsWindows = async () => {
private static async preSearchAchievementsWindows() {
const games = await gameRepository.find({
where: {
isDeleted: false,
Expand All @@ -213,9 +213,9 @@ export class AchievementWatcherManager {
return this.preProcessGameAchievementFiles(game, gameAchievementFiles);
})
);
};
}

private static preSearchAchievementsWithWine = async () => {
private static async preSearchAchievementsWithWine() {
const games = await gameRepository.find({
where: {
isDeleted: false,
Expand All @@ -233,9 +233,9 @@ export class AchievementWatcherManager {
return this.preProcessGameAchievementFiles(game, gameAchievementFiles);
})
);
};
}

public static preSearchAchievements = async () => {
public static async preSearchAchievements() {
try {
const newAchievementsCount =
process.platform === "win32"
Expand All @@ -261,5 +261,5 @@ export class AchievementWatcherManager {
}

this.hasFinishedMergingWithRemote = true;
};
}
}
4 changes: 2 additions & 2 deletions src/main/services/achievements/get-game-achievement-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const getGameAchievementData = async (
shop: GameShop,
cachedAchievements: GameAchievement | null
) => {
if (cachedAchievements && cachedAchievements.achievements) {
if (cachedAchievements?.achievements) {
return JSON.parse(cachedAchievements.achievements) as AchievementData[];
}

Expand Down Expand Up @@ -42,7 +42,7 @@ export const getGameAchievementData = async (
if (err instanceof UserNotLoggedInError) {
throw err;
}
logger.error("Failed to get game achievements", err);
logger.error("Failed to get game achievements for", objectId, err);
return gameAchievementRepository
.findOne({
where: { objectId, shop },
Expand Down
4 changes: 2 additions & 2 deletions src/main/services/achievements/merge-achievements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const mergeAchievements = async (
).filter((achievement) => achievement.name) as UnlockedAchievement[];

const newAchievementsMap = new Map(
achievements.reverse().map((achievement) => {
achievements.toReversed().map((achievement) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: toReversed() is an ES2023 feature - consider using [...achievements].reverse() for better compatibility

return [achievement.name.toUpperCase(), achievement];
})
);
Expand Down Expand Up @@ -92,7 +92,7 @@ export const mergeAchievements = async (
userPreferences?.achievementNotificationsEnabled
) {
const achievementsInfo = newAchievements
.sort((a, b) => {
.toSorted((a, b) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: toSorted() is an ES2023 feature - consider using [...newAchievements].sort() for better compatibility

return a.unlockTime - b.unlockTime;
})
.map((achievement) => {
Expand Down
3 changes: 2 additions & 1 deletion src/main/services/hosters/datanodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export class DatanodesApi {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
},
maxRedirects: 0, validateStatus: (status: number) => status === 302 || status < 400,
maxRedirects: 0,
validateStatus: (status: number) => status === 302 || status < 400,
}
);

Expand Down
28 changes: 19 additions & 9 deletions src/main/services/hydra-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export class HydraApi {
private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes
private static readonly ADD_LOG_INTERCEPTOR = true;

private static secondsToMilliseconds = (seconds: number) => seconds * 1000;
private static readonly secondsToMilliseconds = (seconds: number) =>
seconds * 1000;

private static userAuth: HydraApiUserAuth = {
authToken: "",
Expand Down Expand Up @@ -153,7 +154,8 @@ export class HydraApi {
(error) => {
logger.error(" ---- RESPONSE ERROR -----");
const { config } = error;
const data = JSON.parse(config.data);

const data = JSON.parse(config.data ?? null);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: JSON.parse may throw if config.data is undefined or invalid JSON, even with null coalescing. Consider wrapping in try/catch


logger.error(
config.method,
Expand All @@ -174,14 +176,22 @@ export class HydraApi {
error.response.status,
error.response.data
);
} else if (error.request) {

return Promise.reject(error as Error);
}

if (error.request) {
const errorData = error.toJSON();
logger.error("Request error:", errorData.message);
} else {
logger.error("Error", error.message);
logger.error("Request error:", errorData.code, errorData.message);
return Promise.reject(
new Error(
`Request failed with ${errorData.code} ${errorData.message}`
)
);
}
logger.error(" ----- END RESPONSE ERROR -------");
return Promise.reject(error);

logger.error("Error", error.message);
return Promise.reject(error as Error);
}
);
}
Expand Down Expand Up @@ -261,7 +271,7 @@ export class HydraApi {
};
}

private static handleUnauthorizedError = (err) => {
private static readonly handleUnauthorizedError = (err) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: handleUnauthorizedError parameter 'err' should be typed as 'unknown' for better type safety

if (err instanceof AxiosError && err.response?.status === 401) {
logger.error(
"401 - Current credentials:",
Expand Down
4 changes: 2 additions & 2 deletions src/main/services/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ log.transports.file.resolvePathFn = (
_: log.PathVariables,
message?: log.LogMessage | undefined
) => {
if (message?.scope === "python-instance") {
return path.join(logsPath, "pythoninstance.txt");
if (message?.scope === "python-rpc") {
return path.join(logsPath, "pythonrpc.txt");
}

if (message?.scope == "achievements") {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Inconsistent operator usage - line 9 uses === but line 13 uses ==. Should standardize on ===

Expand Down
9 changes: 7 additions & 2 deletions src/main/services/notifications/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { achievementSoundPath } from "@main/constants";
import icon from "@resources/icon.png?asset";
import { NotificationOptions, toXmlString } from "./xml";
import { logger } from "../logger";
import { WindowManager } from "../window-manager";

async function downloadImage(url: string | null) {
if (!url) return undefined;
Expand Down Expand Up @@ -93,7 +94,9 @@ export const publishCombinedNewAchievementNotification = async (
toastXml: toXmlString(options),
}).show();

if (process.platform !== "linux") {
if (WindowManager.mainWindow) {
WindowManager.mainWindow.webContents.send("on-achievement-unlocked");
} else if (process.platform !== "linux") {
sound.play(achievementSoundPath);
}
};
Expand Down Expand Up @@ -140,7 +143,9 @@ export const publishNewAchievementNotification = async (info: {
toastXml: toXmlString(options),
}).show();

if (process.platform !== "linux") {
if (WindowManager.mainWindow) {
WindowManager.mainWindow.webContents.send("on-achievement-unlocked");
} else if (process.platform !== "linux") {
sound.play(achievementSoundPath);
}
};
Loading
Loading