mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-26 20:31:03 +00:00
Merge branch 'main' into build/win-portable-release
# Conflicts: # .github/workflows/build.yml
This commit is contained in:
@@ -1,95 +1,36 @@
|
||||
import { formatName, getSteamAppAsset, repackerFormatter } from "@main/helpers";
|
||||
import type { CatalogueCategory, CatalogueEntry, GameShop } from "@types";
|
||||
import { getSteamAppAsset } from "@main/helpers";
|
||||
import type { CatalogueEntry, GameShop } from "@types";
|
||||
|
||||
import { stateManager } from "@main/state-manager";
|
||||
import { searchGames, searchRepacks } from "../helpers/search-games";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { requestSteam250 } from "@main/services";
|
||||
|
||||
const repacks = stateManager.getValue("repacks");
|
||||
|
||||
const getStringForLookup = (index: number): string => {
|
||||
const repack = repacks[index];
|
||||
const formatter =
|
||||
repackerFormatter[repack.repacker as keyof typeof repackerFormatter];
|
||||
|
||||
return formatName(formatter(repack.title));
|
||||
};
|
||||
import { RepacksManager, requestSteam250 } from "@main/services";
|
||||
import { formatName } from "@shared";
|
||||
|
||||
const resultSize = 12;
|
||||
|
||||
const getCatalogue = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
category: CatalogueCategory
|
||||
) => {
|
||||
if (!repacks.length) return [];
|
||||
|
||||
if (category === "trending") {
|
||||
return getTrendingCatalogue(resultSize);
|
||||
}
|
||||
|
||||
return getRecentlyAddedCatalogue(resultSize);
|
||||
};
|
||||
|
||||
const getTrendingCatalogue = async (
|
||||
resultSize: number
|
||||
): Promise<CatalogueEntry[]> => {
|
||||
const results: CatalogueEntry[] = [];
|
||||
const getCatalogue = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
const trendingGames = await requestSteam250("/90day");
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < trendingGames.length && results.length < resultSize;
|
||||
i++
|
||||
) {
|
||||
if (!trendingGames[i]) continue;
|
||||
|
||||
const { title, objectID } = trendingGames[i]!;
|
||||
const repacks = searchRepacks(title);
|
||||
|
||||
if (title && repacks.length) {
|
||||
const catalogueEntry = {
|
||||
objectID,
|
||||
title,
|
||||
shop: "steam" as GameShop,
|
||||
cover: getSteamAppAsset("library", objectID),
|
||||
};
|
||||
|
||||
results.push({ ...catalogueEntry, repacks });
|
||||
}
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
const getRecentlyAddedCatalogue = async (
|
||||
resultSize: number
|
||||
): Promise<CatalogueEntry[]> => {
|
||||
const results: CatalogueEntry[] = [];
|
||||
|
||||
for (let i = 0; results.length < resultSize; i++) {
|
||||
const stringForLookup = getStringForLookup(i);
|
||||
|
||||
if (!stringForLookup) {
|
||||
for (let i = 0; i < resultSize; i++) {
|
||||
if (!trendingGames[i]) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const games = searchGames({ query: stringForLookup });
|
||||
const { title, objectID } = trendingGames[i]!;
|
||||
const repacks = RepacksManager.search({ query: formatName(title) });
|
||||
|
||||
for (const game of games) {
|
||||
const isAlreadyIncluded = results.some(
|
||||
(result) => result.objectID === game?.objectID
|
||||
);
|
||||
const catalogueEntry = {
|
||||
objectID,
|
||||
title,
|
||||
shop: "steam" as GameShop,
|
||||
cover: getSteamAppAsset("library", objectID),
|
||||
};
|
||||
|
||||
if (!game || !game.repacks.length || isAlreadyIncluded) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push(game);
|
||||
}
|
||||
results.push({ ...catalogueEntry, repacks });
|
||||
}
|
||||
|
||||
return results.slice(0, resultSize);
|
||||
return results;
|
||||
};
|
||||
|
||||
registerEvent("getCatalogue", getCatalogue);
|
||||
|
||||
@@ -4,9 +4,9 @@ import { getSteamAppDetails } from "@main/services";
|
||||
import type { ShopDetails, GameShop, SteamAppDetails } from "@types";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
|
||||
const getLocalizedSteamAppDetails = (
|
||||
const getLocalizedSteamAppDetails = async (
|
||||
objectID: string,
|
||||
language: string
|
||||
): Promise<ShopDetails | null> => {
|
||||
@@ -14,20 +14,22 @@ const getLocalizedSteamAppDetails = (
|
||||
return getSteamAppDetails(objectID, language);
|
||||
}
|
||||
|
||||
return getSteamAppDetails(objectID, language).then((localizedAppDetails) => {
|
||||
const steamGame = stateManager
|
||||
.getValue("steamGames")
|
||||
.find((game) => game.id === Number(objectID));
|
||||
return getSteamAppDetails(objectID, language).then(
|
||||
async (localizedAppDetails) => {
|
||||
const steamGame = await steamGamesWorker.run(Number(objectID), {
|
||||
name: "getById",
|
||||
});
|
||||
|
||||
if (steamGame && localizedAppDetails) {
|
||||
return {
|
||||
...localizedAppDetails,
|
||||
name: steamGame.name,
|
||||
};
|
||||
if (steamGame && localizedAppDetails) {
|
||||
return {
|
||||
...localizedAppDetails,
|
||||
name: steamGame.name,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
const getGameShopDetails = async (
|
||||
|
||||
@@ -1,39 +1,28 @@
|
||||
import type { CatalogueEntry, GameShop } from "@types";
|
||||
import type { CatalogueEntry } from "@types";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { searchRepacks } from "../helpers/search-games";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
import { getSteamAppAsset } from "@main/helpers";
|
||||
|
||||
const steamGames = stateManager.getValue("steamGames");
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
import { convertSteamGameToCatalogueEntry } from "../helpers/search-games";
|
||||
import { RepacksManager } from "@main/services";
|
||||
|
||||
const getGames = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
take = 12,
|
||||
cursor = 0
|
||||
): Promise<{ results: CatalogueEntry[]; cursor: number }> => {
|
||||
const results: CatalogueEntry[] = [];
|
||||
const steamGames = await steamGamesWorker.run(
|
||||
{ limit: take, offset: cursor },
|
||||
{ name: "list" }
|
||||
);
|
||||
|
||||
let i = 0 + cursor;
|
||||
const entries = RepacksManager.findRepacksForCatalogueEntries(
|
||||
steamGames.map((game) => convertSteamGameToCatalogueEntry(game))
|
||||
);
|
||||
|
||||
while (results.length < take) {
|
||||
const game = steamGames[i];
|
||||
const repacks = searchRepacks(game.name);
|
||||
|
||||
if (repacks.length) {
|
||||
results.push({
|
||||
objectID: String(game.id),
|
||||
title: game.name,
|
||||
shop: "steam" as GameShop,
|
||||
cover: getSteamAppAsset("library", String(game.id)),
|
||||
repacks,
|
||||
});
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return { results, cursor: i };
|
||||
return {
|
||||
results: entries,
|
||||
cursor: cursor + entries.length,
|
||||
};
|
||||
};
|
||||
|
||||
registerEvent("getGames", getGames);
|
||||
|
||||
@@ -3,21 +3,34 @@ import { shuffle } from "lodash-es";
|
||||
import { getSteam250List } from "@main/services";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { searchGames, searchRepacks } from "../helpers/search-games";
|
||||
import { searchSteamGames } from "../helpers/search-games";
|
||||
import type { Steam250Game } from "@types";
|
||||
|
||||
const state = { games: Array<Steam250Game>(), index: 0 };
|
||||
|
||||
const filterGames = async (games: Steam250Game[]) => {
|
||||
const results: Steam250Game[] = [];
|
||||
|
||||
for (const game of games) {
|
||||
const catalogue = await searchSteamGames({ query: game.title });
|
||||
|
||||
if (catalogue.length) {
|
||||
const [steamGame] = catalogue;
|
||||
|
||||
if (steamGame.repacks.length) {
|
||||
results.push(game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
const getRandomGame = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
if (state.games.length == 0) {
|
||||
const steam250List = await getSteam250List();
|
||||
|
||||
const filteredSteam250List = steam250List.filter((game) => {
|
||||
const repacks = searchRepacks(game.title);
|
||||
const catalogue = searchGames({ query: game.title });
|
||||
|
||||
return repacks.length && catalogue.length;
|
||||
});
|
||||
const filteredSteam250List = await filterGames(steam250List);
|
||||
|
||||
state.games = shuffle(filteredSteam250List);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { searchRepacks } from "../helpers/search-games";
|
||||
import { RepacksManager } from "@main/services";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const searchGameRepacks = (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
query: string
|
||||
) => {
|
||||
return searchRepacks(query);
|
||||
};
|
||||
) => RepacksManager.search({ query });
|
||||
|
||||
registerEvent("searchGameRepacks", searchGameRepacks);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { searchGames } from "../helpers/search-games";
|
||||
import { searchSteamGames } from "../helpers/search-games";
|
||||
import { CatalogueEntry } from "@types";
|
||||
|
||||
const searchGamesEvent = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
query: string
|
||||
): Promise<CatalogueEntry[]> => {
|
||||
return searchGames({ query, take: 12 });
|
||||
};
|
||||
): Promise<CatalogueEntry[]> => searchSteamGames({ query, limit: 12 });
|
||||
|
||||
registerEvent("searchGames", searchGamesEvent);
|
||||
|
||||
42
src/main/events/download-sources/add-download-source.ts
Normal file
42
src/main/events/download-sources/add-download-source.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { dataSource } from "@main/data-source";
|
||||
import { DownloadSource } from "@main/entity";
|
||||
import axios from "axios";
|
||||
import { downloadSourceSchema } from "../helpers/validators";
|
||||
import { insertDownloadsFromSource } from "@main/helpers";
|
||||
import { RepacksManager } from "@main/services";
|
||||
|
||||
const addDownloadSource = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
url: string
|
||||
) => {
|
||||
const response = await axios.get(url);
|
||||
|
||||
const source = downloadSourceSchema.parse(response.data);
|
||||
|
||||
const downloadSource = await dataSource.transaction(
|
||||
async (transactionalEntityManager) => {
|
||||
const downloadSource = await transactionalEntityManager
|
||||
.getRepository(DownloadSource)
|
||||
.save({
|
||||
url,
|
||||
name: source.name,
|
||||
downloadCount: source.downloads.length,
|
||||
});
|
||||
|
||||
await insertDownloadsFromSource(
|
||||
transactionalEntityManager,
|
||||
downloadSource,
|
||||
source.downloads
|
||||
);
|
||||
|
||||
return downloadSource;
|
||||
}
|
||||
);
|
||||
|
||||
await RepacksManager.updateRepacks();
|
||||
|
||||
return downloadSource;
|
||||
};
|
||||
|
||||
registerEvent("addDownloadSource", addDownloadSource);
|
||||
16
src/main/events/download-sources/get-download-sources.ts
Normal file
16
src/main/events/download-sources/get-download-sources.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { downloadSourceRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
return downloadSourceRepository
|
||||
.createQueryBuilder("downloadSource")
|
||||
.leftJoin("downloadSource.repacks", "repacks")
|
||||
.orderBy("downloadSource.createdAt", "DESC")
|
||||
.loadRelationCountAndMap(
|
||||
"downloadSource.repackCount",
|
||||
"downloadSource.repacks"
|
||||
)
|
||||
.getMany();
|
||||
};
|
||||
|
||||
registerEvent("getDownloadSources", getDownloadSources);
|
||||
13
src/main/events/download-sources/remove-download-source.ts
Normal file
13
src/main/events/download-sources/remove-download-source.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { downloadSourceRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { RepacksManager } from "@main/services";
|
||||
|
||||
const removeDownloadSource = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
id: number
|
||||
) => {
|
||||
await downloadSourceRepository.delete(id);
|
||||
await RepacksManager.updateRepacks();
|
||||
};
|
||||
|
||||
registerEvent("removeDownloadSource", removeDownloadSource);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { fetchDownloadSourcesAndUpdate } from "@main/helpers";
|
||||
|
||||
const syncDownloadSources = async (_event: Electron.IpcMainInvokeEvent) =>
|
||||
fetchDownloadSourcesAndUpdate();
|
||||
|
||||
registerEvent("syncDownloadSources", syncDownloadSources);
|
||||
34
src/main/events/download-sources/validate-download-source.ts
Normal file
34
src/main/events/download-sources/validate-download-source.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import axios from "axios";
|
||||
import { downloadSourceRepository } from "@main/repository";
|
||||
import { downloadSourceSchema } from "../helpers/validators";
|
||||
import { RepacksManager } from "@main/services";
|
||||
|
||||
const validateDownloadSource = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
url: string
|
||||
) => {
|
||||
const response = await axios.get(url);
|
||||
|
||||
const source = downloadSourceSchema.parse(response.data);
|
||||
|
||||
const existingSource = await downloadSourceRepository.findOne({
|
||||
where: { url },
|
||||
});
|
||||
|
||||
if (existingSource)
|
||||
throw new Error("Source with the same url already exists");
|
||||
|
||||
const repacks = RepacksManager.repacks;
|
||||
|
||||
const existingUris = source.downloads
|
||||
.flatMap((download) => download.uris)
|
||||
.filter((uri) => repacks.some((repack) => repack.magnet === uri));
|
||||
|
||||
return {
|
||||
name: source.name,
|
||||
downloadCount: source.downloads.length - existingUris.length,
|
||||
};
|
||||
};
|
||||
|
||||
registerEvent("validateDownloadSource", validateDownloadSource);
|
||||
@@ -1,40 +1,11 @@
|
||||
import flexSearch from "flexsearch";
|
||||
import { orderBy } from "lodash-es";
|
||||
import flexSearch from "flexsearch";
|
||||
|
||||
import type { GameRepack, GameShop, CatalogueEntry } from "@types";
|
||||
import type { GameShop, CatalogueEntry, SteamGame } from "@types";
|
||||
|
||||
import { formatName, getSteamAppAsset, repackerFormatter } from "@main/helpers";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
|
||||
const { Index } = flexSearch;
|
||||
const repacksIndex = new Index();
|
||||
const steamGamesIndex = new Index({ tokenize: "forward" });
|
||||
|
||||
const repacks = stateManager.getValue("repacks");
|
||||
const steamGames = stateManager.getValue("steamGames");
|
||||
|
||||
for (let i = 0; i < repacks.length; i++) {
|
||||
const repack = repacks[i];
|
||||
const formatter =
|
||||
repackerFormatter[repack.repacker as keyof typeof repackerFormatter];
|
||||
|
||||
repacksIndex.add(i, formatName(formatter(repack.title)));
|
||||
}
|
||||
|
||||
for (let i = 0; i < steamGames.length; i++) {
|
||||
const steamGame = steamGames[i];
|
||||
steamGamesIndex.add(i, formatName(steamGame.name));
|
||||
}
|
||||
|
||||
export const searchRepacks = (title: string): GameRepack[] => {
|
||||
return orderBy(
|
||||
repacksIndex
|
||||
.search(formatName(title))
|
||||
.map((index) => repacks.at(index as number)!),
|
||||
["uploadDate"],
|
||||
"desc"
|
||||
);
|
||||
};
|
||||
import { getSteamAppAsset } from "@main/helpers";
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
import { RepacksManager } from "@main/services";
|
||||
|
||||
export interface SearchGamesArgs {
|
||||
query?: string;
|
||||
@@ -42,27 +13,29 @@ export interface SearchGamesArgs {
|
||||
skip?: number;
|
||||
}
|
||||
|
||||
export const searchGames = ({
|
||||
query,
|
||||
take,
|
||||
skip,
|
||||
}: SearchGamesArgs): CatalogueEntry[] => {
|
||||
const results = steamGamesIndex
|
||||
.search(formatName(query || ""), { limit: take, offset: skip })
|
||||
.map((index) => {
|
||||
const result = steamGames.at(index as number)!;
|
||||
export const convertSteamGameToCatalogueEntry = (
|
||||
game: SteamGame
|
||||
): CatalogueEntry => ({
|
||||
objectID: String(game.id),
|
||||
title: game.name,
|
||||
shop: "steam" as GameShop,
|
||||
cover: getSteamAppAsset("library", String(game.id)),
|
||||
repacks: [],
|
||||
});
|
||||
|
||||
return {
|
||||
objectID: String(result.id),
|
||||
title: result.name,
|
||||
shop: "steam" as GameShop,
|
||||
cover: getSteamAppAsset("library", String(result.id)),
|
||||
repacks: searchRepacks(result.name),
|
||||
};
|
||||
});
|
||||
export const searchSteamGames = async (
|
||||
options: flexSearch.SearchOptions
|
||||
): Promise<CatalogueEntry[]> => {
|
||||
const steamGames = (await steamGamesWorker.run(options, {
|
||||
name: "search",
|
||||
})) as SteamGame[];
|
||||
|
||||
const result = RepacksManager.findRepacksForCatalogueEntries(
|
||||
steamGames.map((game) => convertSteamGameToCatalogueEntry(game))
|
||||
);
|
||||
|
||||
return orderBy(
|
||||
results,
|
||||
result,
|
||||
[({ repacks }) => repacks.length, "repacks"],
|
||||
["desc"]
|
||||
);
|
||||
|
||||
14
src/main/events/helpers/validators.ts
Normal file
14
src/main/events/helpers/validators.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const downloadSourceSchema = z.object({
|
||||
name: z.string().max(255),
|
||||
downloads: z.array(
|
||||
z.object({
|
||||
title: z.string().max(255),
|
||||
downloaders: z.array(z.enum(["real_debrid", "torrent"])),
|
||||
uris: z.array(z.string()),
|
||||
uploadDate: z.string().max(255),
|
||||
fileSize: z.string().max(255),
|
||||
})
|
||||
),
|
||||
});
|
||||
@@ -10,12 +10,16 @@ import "./catalogue/search-games";
|
||||
import "./catalogue/search-game-repacks";
|
||||
import "./hardware/get-disk-free-space";
|
||||
import "./library/add-game-to-library";
|
||||
import "./library/create-game-shortcut";
|
||||
import "./library/close-game";
|
||||
import "./library/delete-game-folder";
|
||||
import "./library/get-game-by-object-id";
|
||||
import "./library/get-library";
|
||||
import "./library/open-game";
|
||||
import "./library/open-game-executable-path";
|
||||
import "./library/open-game-installer";
|
||||
import "./library/open-game-installer-path";
|
||||
import "./library/update-executable-path";
|
||||
import "./library/remove-game";
|
||||
import "./library/remove-game-from-library";
|
||||
import "./misc/open-external";
|
||||
@@ -30,6 +34,11 @@ import "./user-preferences/auto-launch";
|
||||
import "./autoupdater/check-for-updates";
|
||||
import "./autoupdater/restart-and-install-update";
|
||||
import "./user-preferences/authenticate-real-debrid";
|
||||
import "./download-sources/get-download-sources";
|
||||
import "./download-sources/validate-download-source";
|
||||
import "./download-sources/add-download-source";
|
||||
import "./download-sources/remove-download-source";
|
||||
import "./download-sources/sync-download-sources";
|
||||
|
||||
ipcMain.handle("ping", () => "pong");
|
||||
ipcMain.handle("getVersion", () => app.getVersion());
|
||||
|
||||
@@ -4,14 +4,14 @@ import { registerEvent } from "../register-event";
|
||||
|
||||
import type { GameShop } from "@types";
|
||||
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
|
||||
const addGameToLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
objectID: string,
|
||||
title: string,
|
||||
shop: GameShop,
|
||||
executablePath: string | null
|
||||
shop: GameShop
|
||||
) => {
|
||||
return gameRepository
|
||||
.update(
|
||||
@@ -21,15 +21,14 @@ const addGameToLibrary = async (
|
||||
{
|
||||
shop,
|
||||
status: null,
|
||||
executablePath,
|
||||
isDeleted: false,
|
||||
}
|
||||
)
|
||||
.then(async ({ affected }) => {
|
||||
if (!affected) {
|
||||
const steamGame = stateManager
|
||||
.getValue("steamGames")
|
||||
.find((game) => game.id === Number(objectID));
|
||||
const steamGame = await steamGamesWorker.run(Number(objectID), {
|
||||
name: "getById",
|
||||
});
|
||||
|
||||
const iconUrl = steamGame?.clientIcon
|
||||
? getSteamAppAsset("icon", objectID, steamGame.clientIcon)
|
||||
@@ -41,7 +40,6 @@ const addGameToLibrary = async (
|
||||
iconUrl,
|
||||
objectID,
|
||||
shop,
|
||||
executablePath,
|
||||
})
|
||||
.then(() => {
|
||||
if (iconUrl) {
|
||||
|
||||
29
src/main/events/library/create-game-shortcut.ts
Normal file
29
src/main/events/library/create-game-shortcut.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { IsNull, Not } from "typeorm";
|
||||
import createDesktopShortcut from "create-desktop-shortcuts";
|
||||
|
||||
const createGameShortcut = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
id: number
|
||||
): Promise<boolean> => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: { id, executablePath: Not(IsNull()) },
|
||||
});
|
||||
|
||||
if (game) {
|
||||
const filePath = game.executablePath;
|
||||
|
||||
const options = { filePath, name: game.title };
|
||||
|
||||
return createDesktopShortcut({
|
||||
windows: options,
|
||||
linux: options,
|
||||
osx: options,
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
registerEvent("createGameShortcut", createGameShortcut);
|
||||
@@ -1,8 +1,6 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
import { In } from "typeorm";
|
||||
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
@@ -14,11 +12,18 @@ const deleteGameFolder = async (
|
||||
gameId: number
|
||||
): Promise<void> => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: {
|
||||
id: gameId,
|
||||
status: In(["removed", "complete"]),
|
||||
isDeleted: false,
|
||||
},
|
||||
where: [
|
||||
{
|
||||
id: gameId,
|
||||
isDeleted: false,
|
||||
status: "removed",
|
||||
},
|
||||
{
|
||||
id: gameId,
|
||||
progress: 1,
|
||||
isDeleted: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!game) return;
|
||||
@@ -30,7 +35,7 @@ const deleteGameFolder = async (
|
||||
);
|
||||
|
||||
if (fs.existsSync(folderPath)) {
|
||||
return new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
fs.rm(
|
||||
folderPath,
|
||||
{ recursive: true, force: true, maxRetries: 5, retryDelay: 200 },
|
||||
@@ -40,12 +45,21 @@ const deleteGameFolder = async (
|
||||
reject();
|
||||
}
|
||||
|
||||
const aria2ControlFilePath = `${folderPath}.aria2`;
|
||||
if (fs.existsSync(aria2ControlFilePath))
|
||||
fs.rmSync(aria2ControlFilePath);
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await gameRepository.update(
|
||||
{ id: gameId },
|
||||
{ downloadPath: null, folderName: null, status: null, progress: 0 }
|
||||
);
|
||||
};
|
||||
|
||||
registerEvent("deleteGameFolder", deleteGameFolder);
|
||||
|
||||
@@ -11,9 +11,6 @@ const getGameByObjectID = async (
|
||||
objectID,
|
||||
isDeleted: false,
|
||||
},
|
||||
relations: {
|
||||
repack: true,
|
||||
},
|
||||
});
|
||||
|
||||
registerEvent("getGameByObjectID", getGameByObjectID);
|
||||
|
||||
@@ -1,30 +1,17 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { searchRepacks } from "../helpers/search-games";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { sortBy } from "lodash-es";
|
||||
|
||||
const getLibrary = async () =>
|
||||
gameRepository
|
||||
.find({
|
||||
where: {
|
||||
isDeleted: false,
|
||||
},
|
||||
order: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
relations: {
|
||||
repack: true,
|
||||
},
|
||||
})
|
||||
.then((games) =>
|
||||
sortBy(
|
||||
games.map((game) => ({
|
||||
...game,
|
||||
repacks: searchRepacks(game.title),
|
||||
})),
|
||||
(game) => (game.status !== "removed" ? 0 : 1)
|
||||
)
|
||||
);
|
||||
gameRepository.find({
|
||||
where: {
|
||||
isDeleted: false,
|
||||
},
|
||||
relations: {
|
||||
downloadQueue: true,
|
||||
},
|
||||
order: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
registerEvent("getLibrary", getLibrary);
|
||||
|
||||
18
src/main/events/library/open-game-executable-path.ts
Normal file
18
src/main/events/library/open-game-executable-path.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { shell } from "electron";
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const openGameExecutablePath = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: { id: gameId, isDeleted: false },
|
||||
});
|
||||
|
||||
if (!game || !game.executablePath) return;
|
||||
|
||||
shell.showItemInFolder(game.executablePath);
|
||||
};
|
||||
|
||||
registerEvent("openGameExecutablePath", openGameExecutablePath);
|
||||
27
src/main/events/library/open-game-installer-path.ts
Normal file
27
src/main/events/library/open-game-installer-path.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { shell } from "electron";
|
||||
import path from "node:path";
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const openGameInstallerPath = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: { id: gameId, isDeleted: false },
|
||||
});
|
||||
|
||||
if (!game || !game.folderName || !game.downloadPath) return true;
|
||||
|
||||
const gamePath = path.join(
|
||||
game.downloadPath ?? (await getDownloadsPath()),
|
||||
game.folderName!
|
||||
);
|
||||
|
||||
shell.showItemInFolder(gamePath);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
registerEvent("openGameInstallerPath", openGameInstallerPath);
|
||||
@@ -5,7 +5,10 @@ const removeGameFromLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
) => {
|
||||
gameRepository.update({ id: gameId }, { isDeleted: true });
|
||||
gameRepository.update(
|
||||
{ id: gameId },
|
||||
{ isDeleted: true, executablePath: null }
|
||||
);
|
||||
};
|
||||
|
||||
registerEvent("removeGameFromLibrary", removeGameFromLibrary);
|
||||
|
||||
20
src/main/events/library/update-executable-path.ts
Normal file
20
src/main/events/library/update-executable-path.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const updateExecutablePath = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
id: number,
|
||||
executablePath: string
|
||||
) => {
|
||||
return gameRepository.update(
|
||||
{
|
||||
id,
|
||||
},
|
||||
{
|
||||
executablePath,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
registerEvent("updateExecutablePath", updateExecutablePath);
|
||||
@@ -1,25 +1,31 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import { DownloadManager } from "@main/services";
|
||||
import { dataSource } from "@main/data-source";
|
||||
import { DownloadQueue, Game } from "@main/entity";
|
||||
|
||||
const cancelGameDownload = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
) => {
|
||||
await DownloadManager.cancelDownload(gameId);
|
||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
||||
await DownloadManager.cancelDownload(gameId);
|
||||
|
||||
await gameRepository.update(
|
||||
{
|
||||
id: gameId,
|
||||
},
|
||||
{
|
||||
status: "removed",
|
||||
bytesDownloaded: 0,
|
||||
progress: 0,
|
||||
}
|
||||
);
|
||||
await transactionalEntityManager.getRepository(DownloadQueue).delete({
|
||||
game: { id: gameId },
|
||||
});
|
||||
|
||||
await transactionalEntityManager.getRepository(Game).update(
|
||||
{
|
||||
id: gameId,
|
||||
},
|
||||
{
|
||||
status: "removed",
|
||||
bytesDownloaded: 0,
|
||||
progress: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
registerEvent("cancelGameDownload", cancelGameDownload);
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gameRepository } from "../../repository";
|
||||
|
||||
import { DownloadManager } from "@main/services";
|
||||
import { dataSource } from "@main/data-source";
|
||||
import { DownloadQueue, Game } from "@main/entity";
|
||||
|
||||
const pauseGameDownload = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
) => {
|
||||
await DownloadManager.pauseDownload();
|
||||
await gameRepository.update({ id: gameId }, { status: "paused" });
|
||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
||||
await DownloadManager.pauseDownload();
|
||||
|
||||
await transactionalEntityManager.getRepository(DownloadQueue).delete({
|
||||
game: { id: gameId },
|
||||
});
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(Game)
|
||||
.update({ id: gameId }, { status: "paused" });
|
||||
});
|
||||
};
|
||||
|
||||
registerEvent("pauseGameDownload", pauseGameDownload);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { gameRepository } from "../../repository";
|
||||
|
||||
import { DownloadManager } from "@main/services";
|
||||
import { dataSource } from "@main/data-source";
|
||||
import { Game } from "@main/entity";
|
||||
import { DownloadQueue, Game } from "@main/entity";
|
||||
|
||||
const resumeGameDownload = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -16,7 +16,6 @@ const resumeGameDownload = async (
|
||||
id: gameId,
|
||||
isDeleted: false,
|
||||
},
|
||||
relations: { repack: true },
|
||||
});
|
||||
|
||||
if (!game) return;
|
||||
@@ -31,6 +30,14 @@ const resumeGameDownload = async (
|
||||
|
||||
await DownloadManager.resumeDownload(game);
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(DownloadQueue)
|
||||
.delete({ game: { id: gameId } });
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(DownloadQueue)
|
||||
.insert({ game: { id: gameId } });
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(Game)
|
||||
.update({ id: gameId }, { status: "active" });
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { gameRepository, repackRepository } from "@main/repository";
|
||||
import {
|
||||
downloadQueueRepository,
|
||||
gameRepository,
|
||||
repackRepository,
|
||||
} from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import type { StartGameDownloadPayload } from "@types";
|
||||
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
||||
import { DownloadManager } from "@main/services";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
|
||||
import { Not } from "typeorm";
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
|
||||
const startGameDownload = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -20,7 +25,6 @@ const startGameDownload = async (
|
||||
objectID,
|
||||
shop,
|
||||
},
|
||||
relations: { repack: true },
|
||||
}),
|
||||
repackRepository.findOne({
|
||||
where: {
|
||||
@@ -49,14 +53,14 @@ const startGameDownload = async (
|
||||
bytesDownloaded: 0,
|
||||
downloadPath,
|
||||
downloader,
|
||||
repack: { id: repackId },
|
||||
uri: repack.magnet,
|
||||
isDeleted: false,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const steamGame = stateManager
|
||||
.getValue("steamGames")
|
||||
.find((game) => game.id === Number(objectID));
|
||||
const steamGame = await steamGamesWorker.run(Number(objectID), {
|
||||
name: "getById",
|
||||
});
|
||||
|
||||
const iconUrl = steamGame?.clientIcon
|
||||
? getSteamAppAsset("icon", objectID, steamGame.clientIcon)
|
||||
@@ -71,7 +75,7 @@ const startGameDownload = async (
|
||||
shop,
|
||||
status: "active",
|
||||
downloadPath,
|
||||
repack: { id: repackId },
|
||||
uri: repack.magnet,
|
||||
})
|
||||
.then((result) => {
|
||||
if (iconUrl) {
|
||||
@@ -88,9 +92,11 @@ const startGameDownload = async (
|
||||
where: {
|
||||
objectID,
|
||||
},
|
||||
relations: { repack: true },
|
||||
});
|
||||
|
||||
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
||||
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
||||
|
||||
await DownloadManager.startDownload(updatedGame!);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user