mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Merge branch 'main' into feature/custom-themes
This commit is contained in:
commit
b202dafb84
18 changed files with 108 additions and 70 deletions
|
@ -159,6 +159,8 @@ def action():
|
||||||
downloader = downloads.get(game_id)
|
downloader = downloads.get(game_id)
|
||||||
if downloader:
|
if downloader:
|
||||||
downloader.pause_download()
|
downloader.pause_download()
|
||||||
|
|
||||||
|
if downloading_game_id == game_id:
|
||||||
downloading_game_id = -1
|
downloading_game_id = -1
|
||||||
elif action == 'cancel':
|
elif action == 'cancel':
|
||||||
downloader = downloads.get(game_id)
|
downloader = downloads.get(game_id)
|
||||||
|
|
|
@ -107,7 +107,10 @@ const copyAria2Macos = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyAria2 = () => {
|
const copyAria2 = () => {
|
||||||
if (fs.existsSync("aria2")) {
|
const aria2Path =
|
||||||
|
process.platform === "win32" ? "aria2/aria2c.exe" : "aria2/aria2c";
|
||||||
|
|
||||||
|
if (fs.existsSync(aria2Path)) {
|
||||||
console.log("Aria2 already exists, skipping download...");
|
console.log("Aria2 already exists, skipping download...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type { Game, GameShop } from "@types";
|
||||||
import { steamGamesWorker } from "@main/workers";
|
import { steamGamesWorker } from "@main/workers";
|
||||||
import { createGame } from "@main/services/library-sync";
|
import { createGame } from "@main/services/library-sync";
|
||||||
import { steamUrlBuilder } from "@shared";
|
import { steamUrlBuilder } from "@shared";
|
||||||
import { updateLocalUnlockedAchivements } from "@main/services/achievements/update-local-unlocked-achivements";
|
import { updateLocalUnlockedAchievements } from "@main/services/achievements/update-local-unlocked-achivements";
|
||||||
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
const addGameToLibrary = async (
|
const addGameToLibrary = async (
|
||||||
|
@ -46,9 +46,9 @@ const addGameToLibrary = async (
|
||||||
|
|
||||||
await gamesSublevel.put(levelKeys.game(shop, objectId), game);
|
await gamesSublevel.put(levelKeys.game(shop, objectId), game);
|
||||||
|
|
||||||
updateLocalUnlockedAchivements(game);
|
await createGame(game).catch(() => {});
|
||||||
|
|
||||||
createGame(game).catch(() => {});
|
updateLocalUnlockedAchievements(game);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,14 @@ const cancelGameDownload = async (
|
||||||
|
|
||||||
await DownloadManager.cancelDownload(downloadKey);
|
await DownloadManager.cancelDownload(downloadKey);
|
||||||
|
|
||||||
await downloadsSublevel.del(downloadKey);
|
const download = await downloadsSublevel.get(downloadKey);
|
||||||
|
|
||||||
|
if (!download) return;
|
||||||
|
|
||||||
|
await downloadsSublevel.put(downloadKey, {
|
||||||
|
...download,
|
||||||
|
status: "removed",
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("cancelGameDownload", cancelGameDownload);
|
registerEvent("cancelGameDownload", cancelGameDownload);
|
||||||
|
|
|
@ -15,6 +15,7 @@ const pauseGameSeed = async (
|
||||||
|
|
||||||
await downloadsSublevel.put(downloadKey, {
|
await downloadsSublevel.put(downloadKey, {
|
||||||
...download,
|
...download,
|
||||||
|
status: "complete",
|
||||||
shouldSeed: false,
|
shouldSeed: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,14 @@ const resumeGameSeed = async (
|
||||||
shop: GameShop,
|
shop: GameShop,
|
||||||
objectId: string
|
objectId: string
|
||||||
) => {
|
) => {
|
||||||
const download = await downloadsSublevel.get(levelKeys.game(shop, objectId));
|
const downloadKey = levelKeys.game(shop, objectId);
|
||||||
|
const download = await downloadsSublevel.get(downloadKey);
|
||||||
|
|
||||||
if (!download) return;
|
if (!download) return;
|
||||||
|
|
||||||
await downloadsSublevel.put(levelKeys.game(shop, objectId), {
|
await downloadsSublevel.put(downloadKey, {
|
||||||
...download,
|
...download,
|
||||||
|
status: "seeding",
|
||||||
shouldSeed: true,
|
shouldSeed: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const getUnlockedAchievements = async (
|
||||||
|
|
||||||
return achievementsData
|
return achievementsData
|
||||||
.map((achievementData) => {
|
.map((achievementData) => {
|
||||||
const unlockedAchiementData = unlockedAchievements.find(
|
const unlockedAchievementData = unlockedAchievements.find(
|
||||||
(localAchievement) => {
|
(localAchievement) => {
|
||||||
return (
|
return (
|
||||||
localAchievement.name.toUpperCase() ==
|
localAchievement.name.toUpperCase() ==
|
||||||
|
@ -45,11 +45,11 @@ export const getUnlockedAchievements = async (
|
||||||
? achievementData.icon
|
? achievementData.icon
|
||||||
: achievementData.icongray;
|
: achievementData.icongray;
|
||||||
|
|
||||||
if (unlockedAchiementData) {
|
if (unlockedAchievementData) {
|
||||||
return {
|
return {
|
||||||
...achievementData,
|
...achievementData,
|
||||||
unlocked: true,
|
unlocked: true,
|
||||||
unlockTime: unlockedAchiementData.unlockTime,
|
unlockTime: unlockedAchievementData.unlockTime,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,23 +49,21 @@ export const loadState = async () => {
|
||||||
.values()
|
.values()
|
||||||
.all()
|
.all()
|
||||||
.then((games) => {
|
.then((games) => {
|
||||||
return sortBy(
|
return sortBy(games, "timestamp", "DESC");
|
||||||
games.filter((game) => game.queued),
|
|
||||||
"timestamp",
|
|
||||||
"DESC"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [nextItemOnQueue] = downloads;
|
const [nextItemOnQueue] = downloads.filter((game) => game.queued);
|
||||||
|
|
||||||
const downloadsToSeed = downloads.filter(
|
const downloadsToSeed = downloads.filter(
|
||||||
(download) =>
|
(game) =>
|
||||||
download.shouldSeed &&
|
game.shouldSeed &&
|
||||||
download.downloader === Downloader.Torrent &&
|
game.downloader === Downloader.Torrent &&
|
||||||
download.progress === 1 &&
|
game.progress === 1 &&
|
||||||
download.uri !== null
|
game.uri !== null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log("downloadsToSeed", downloadsToSeed);
|
||||||
|
|
||||||
await DownloadManager.startRPC(nextItemOnQueue, downloadsToSeed);
|
await DownloadManager.startRPC(nextItemOnQueue, downloadsToSeed);
|
||||||
|
|
||||||
startMainLoop();
|
startMainLoop();
|
||||||
|
|
|
@ -23,23 +23,21 @@ const saveAchievementsOnLocal = async (
|
||||||
return gameAchievementsSublevel
|
return gameAchievementsSublevel
|
||||||
.get(levelKey)
|
.get(levelKey)
|
||||||
.then(async (gameAchievement) => {
|
.then(async (gameAchievement) => {
|
||||||
if (gameAchievement) {
|
await gameAchievementsSublevel.put(levelKey, {
|
||||||
await gameAchievementsSublevel.put(levelKey, {
|
achievements: gameAchievement?.achievements ?? [],
|
||||||
...gameAchievement,
|
unlockedAchievements: unlockedAchievements,
|
||||||
unlockedAchievements: unlockedAchievements,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!sendUpdateEvent) return;
|
if (!sendUpdateEvent) return;
|
||||||
|
|
||||||
return getUnlockedAchievements(objectId, shop, true)
|
return getUnlockedAchievements(objectId, shop, true)
|
||||||
.then((achievements) => {
|
.then((achievements) => {
|
||||||
WindowManager.mainWindow?.webContents.send(
|
WindowManager.mainWindow?.webContents.send(
|
||||||
`on-update-achievements-${objectId}-${shop}`,
|
`on-update-achievements-${objectId}-${shop}`,
|
||||||
achievements
|
achievements
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -133,7 +131,7 @@ export const mergeAchievements = async (
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err! instanceof SubscriptionRequiredError) {
|
if (err instanceof SubscriptionRequiredError) {
|
||||||
achievementsLogger.log(
|
achievementsLogger.log(
|
||||||
"Achievements not synchronized on API due to lack of subscription",
|
"Achievements not synchronized on API due to lack of subscription",
|
||||||
game.objectId,
|
game.objectId,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { parseAchievementFile } from "./parse-achievement-file";
|
||||||
import { mergeAchievements } from "./merge-achievements";
|
import { mergeAchievements } from "./merge-achievements";
|
||||||
import type { Game, UnlockedAchievement } from "@types";
|
import type { Game, UnlockedAchievement } from "@types";
|
||||||
|
|
||||||
export const updateLocalUnlockedAchivements = async (game: Game) => {
|
export const updateLocalUnlockedAchievements = async (game: Game) => {
|
||||||
const gameAchievementFiles = findAchievementFiles(game);
|
const gameAchievementFiles = findAchievementFiles(game);
|
||||||
|
|
||||||
const achievementFileInsideDirectory =
|
const achievementFileInsideDirectory =
|
||||||
|
|
|
@ -219,8 +219,10 @@ export class DownloadManager {
|
||||||
} as PauseDownloadPayload)
|
} as PauseDownloadPayload)
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
|
||||||
WindowManager.mainWindow?.setProgressBar(-1);
|
if (downloadKey === this.downloadingGameId) {
|
||||||
this.downloadingGameId = null;
|
WindowManager.mainWindow?.setProgressBar(-1);
|
||||||
|
this.downloadingGameId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async resumeDownload(download: Download) {
|
static async resumeDownload(download: Download) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const mergeWithRemoteGames = async () => {
|
||||||
? game.playTimeInMilliseconds
|
? game.playTimeInMilliseconds
|
||||||
: localGame.playTimeInMilliseconds;
|
: localGame.playTimeInMilliseconds;
|
||||||
|
|
||||||
gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
|
await gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
|
||||||
...localGame,
|
...localGame,
|
||||||
remoteId: game.id,
|
remoteId: game.id,
|
||||||
lastTimePlayed: updatedLastTimePlayed,
|
lastTimePlayed: updatedLastTimePlayed,
|
||||||
|
@ -39,7 +39,7 @@ export const mergeWithRemoteGames = async () => {
|
||||||
? steamUrlBuilder.icon(game.objectId, steamGame.clientIcon)
|
? steamUrlBuilder.icon(game.objectId, steamGame.clientIcon)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
|
await gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
|
||||||
objectId: game.objectId,
|
objectId: game.objectId,
|
||||||
title: steamGame?.name,
|
title: steamGame?.name,
|
||||||
remoteId: game.id,
|
remoteId: game.id,
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
&__content {
|
&__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
padding: calc(globals.$spacing-unit * 2);
|
padding: calc(globals.$spacing-unit * 2);
|
||||||
gap: calc(globals.$spacing-unit * 2);
|
gap: calc(globals.$spacing-unit * 2);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
color: globals.$muted-color;
|
color: globals.$muted-color;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.15);
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
@ -104,13 +106,14 @@
|
||||||
&__container {
|
&__container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__section {
|
&__section {
|
||||||
gap: calc(globals.$spacing-unit * 2);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: calc(globals.$spacing-unit * 2);
|
||||||
padding-bottom: globals.$spacing-unit;
|
padding-bottom: globals.$spacing-unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ export function useDownload() {
|
||||||
const pauseDownload = async (shop: GameShop, objectId: string) => {
|
const pauseDownload = async (shop: GameShop, objectId: string) => {
|
||||||
await window.electron.pauseGameDownload(shop, objectId);
|
await window.electron.pauseGameDownload(shop, objectId);
|
||||||
await updateLibrary();
|
await updateLibrary();
|
||||||
dispatch(clearDownload());
|
if (lastPacket?.gameId === `${shop}:${objectId}`) dispatch(clearDownload());
|
||||||
};
|
};
|
||||||
|
|
||||||
const resumeDownload = async (shop: GameShop, objectId: string) => {
|
const resumeDownload = async (shop: GameShop, objectId: string) => {
|
||||||
|
|
|
@ -5,6 +5,14 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc(globals.$spacing-unit * 2);
|
gap: calc(globals.$spacing-unit * 2);
|
||||||
|
|
||||||
|
&__details-with-article {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: calc(globals.$spacing-unit / 2);
|
||||||
|
align-self: flex-start;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
|
QuestionIcon,
|
||||||
ThreeBarsIcon,
|
ThreeBarsIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
UnlinkIcon,
|
UnlinkIcon,
|
||||||
|
@ -123,8 +124,12 @@ export function DownloadGroup({
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{download.downloader === Downloader.Torrent && (
|
{download.downloader === Downloader.Torrent && (
|
||||||
<small>
|
<small
|
||||||
|
className="download-group__details-with-article"
|
||||||
|
data-open-article="peers-and-seeds"
|
||||||
|
>
|
||||||
{lastPacket?.numPeers} peers / {lastPacket?.numSeeds} seeds
|
{lastPacket?.numPeers} peers / {lastPacket?.numSeeds} seeds
|
||||||
|
<QuestionIcon size={12} />
|
||||||
</small>
|
</small>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -137,7 +142,14 @@ export function DownloadGroup({
|
||||||
return download.status === "seeding" &&
|
return download.status === "seeding" &&
|
||||||
download.downloader === Downloader.Torrent ? (
|
download.downloader === Downloader.Torrent ? (
|
||||||
<>
|
<>
|
||||||
<p>{t("seeding")}</p>
|
<p
|
||||||
|
data-open-article="seeding"
|
||||||
|
className="download-group__details-with-article"
|
||||||
|
>
|
||||||
|
{t("seeding")}
|
||||||
|
|
||||||
|
<QuestionIcon />
|
||||||
|
</p>
|
||||||
{uploadSpeed && <p>{uploadSpeed}/s</p>}
|
{uploadSpeed && <p>{uploadSpeed}/s</p>}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
@ -175,7 +187,7 @@ export function DownloadGroup({
|
||||||
|
|
||||||
const deleting = isGameDeleting(game.id);
|
const deleting = isGameDeleting(game.id);
|
||||||
|
|
||||||
if (download?.progress === 1) {
|
if (game.download?.progress === 1) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: t("install"),
|
label: t("install"),
|
||||||
|
@ -190,8 +202,8 @@ export function DownloadGroup({
|
||||||
disabled: deleting,
|
disabled: deleting,
|
||||||
icon: <UnlinkIcon />,
|
icon: <UnlinkIcon />,
|
||||||
show:
|
show:
|
||||||
download.status === "seeding" &&
|
game.download?.status === "seeding" &&
|
||||||
download.downloader === Downloader.Torrent,
|
game.download?.downloader === Downloader.Torrent,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
pauseSeeding(game.shop, game.objectId);
|
pauseSeeding(game.shop, game.objectId);
|
||||||
},
|
},
|
||||||
|
@ -201,8 +213,8 @@ export function DownloadGroup({
|
||||||
disabled: deleting,
|
disabled: deleting,
|
||||||
icon: <LinkIcon />,
|
icon: <LinkIcon />,
|
||||||
show:
|
show:
|
||||||
download.status !== "seeding" &&
|
game.download?.status !== "seeding" &&
|
||||||
download.downloader === Downloader.Torrent,
|
game.download?.downloader === Downloader.Torrent,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
resumeSeeding(game.shop, game.objectId);
|
resumeSeeding(game.shop, game.objectId);
|
||||||
},
|
},
|
||||||
|
@ -218,7 +230,7 @@ export function DownloadGroup({
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGameDownloading || download?.status === "active") {
|
if (isGameDownloading) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: t("pause"),
|
label: t("pause"),
|
||||||
|
|
|
@ -8,7 +8,7 @@ import "./downloads.scss";
|
||||||
import { DeleteGameModal } from "./delete-game-modal";
|
import { DeleteGameModal } from "./delete-game-modal";
|
||||||
import { DownloadGroup } from "./download-group";
|
import { DownloadGroup } from "./download-group";
|
||||||
import type { GameShop, LibraryGame, SeedingStatus } from "@types";
|
import type { GameShop, LibraryGame, SeedingStatus } from "@types";
|
||||||
import { orderBy, sortBy } from "lodash-es";
|
import { orderBy } from "lodash-es";
|
||||||
import { ArrowDownIcon } from "@primer/octicons-react";
|
import { ArrowDownIcon } from "@primer/octicons-react";
|
||||||
|
|
||||||
export default function Downloads() {
|
export default function Downloads() {
|
||||||
|
@ -58,24 +58,24 @@ export default function Downloads() {
|
||||||
complete: [],
|
complete: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = sortBy(library, (game) => game.download?.timestamp).reduce(
|
const result = orderBy(
|
||||||
(prev, next) => {
|
library,
|
||||||
/* Game has been manually added to the library or has been canceled */
|
(game) => game.download?.timestamp,
|
||||||
if (!next.download?.status || next.download?.status === "removed")
|
"desc"
|
||||||
return prev;
|
).reduce((prev, next) => {
|
||||||
|
/* Game has been manually added to the library */
|
||||||
|
if (!next.download) return prev;
|
||||||
|
|
||||||
/* Is downloading */
|
/* Is downloading */
|
||||||
if (lastPacket?.gameId === next.id)
|
if (lastPacket?.gameId === next.id)
|
||||||
return { ...prev, downloading: [...prev.downloading, next] };
|
return { ...prev, downloading: [...prev.downloading, next] };
|
||||||
|
|
||||||
/* Is either queued or paused */
|
/* Is either queued or paused */
|
||||||
if (next.download.queued || next.download?.status === "paused")
|
if (next.download.queued || next.download?.status === "paused")
|
||||||
return { ...prev, queued: [...prev.queued, next] };
|
return { ...prev, queued: [...prev.queued, next] };
|
||||||
|
|
||||||
return { ...prev, complete: [...prev.complete, next] };
|
return { ...prev, complete: [...prev.complete, next] };
|
||||||
},
|
}, initialValue);
|
||||||
initialValue
|
|
||||||
);
|
|
||||||
|
|
||||||
const queued = orderBy(result.queued, (game) => game.download?.timestamp, [
|
const queued = orderBy(result.queued, (game) => game.download?.timestamp, [
|
||||||
"desc",
|
"desc",
|
||||||
|
|
|
@ -163,8 +163,10 @@ export function DownloadSettingsModal({
|
||||||
selectedDownloader === downloader ? "primary" : "outline"
|
selectedDownloader === downloader ? "primary" : "outline"
|
||||||
}
|
}
|
||||||
disabled={
|
disabled={
|
||||||
downloader === Downloader.RealDebrid &&
|
(downloader === Downloader.RealDebrid &&
|
||||||
!userPreferences?.realDebridApiToken
|
!userPreferences?.realDebridApiToken) ||
|
||||||
|
(downloader === Downloader.TorBox &&
|
||||||
|
!userPreferences?.torBoxApiToken)
|
||||||
}
|
}
|
||||||
onClick={() => setSelectedDownloader(downloader)}
|
onClick={() => setSelectedDownloader(downloader)}
|
||||||
>
|
>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue