mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
refactor: prettier changes
This commit is contained in:
parent
a16a30761e
commit
e79b6f1391
16 changed files with 380 additions and 332 deletions
|
@ -21,6 +21,5 @@ export namespace GameStatus {
|
||||||
GameStatus.Decompressing == status;
|
GameStatus.Decompressing == status;
|
||||||
|
|
||||||
export const isReady = (status: GameStatus | null) =>
|
export const isReady = (status: GameStatus | null) =>
|
||||||
status === GameStatus.Finished ||
|
status === GameStatus.Finished || status === GameStatus.Seeding;
|
||||||
status === GameStatus.Seeding;
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -46,9 +46,9 @@ export class Game {
|
||||||
@Column("text", { nullable: true })
|
@Column("text", { nullable: true })
|
||||||
status: GameStatus | null;
|
status: GameStatus | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Progress is a float between 0 and 1
|
* Progress is a float between 0 and 1
|
||||||
*/
|
*/
|
||||||
@Column("float", { default: 0 })
|
@Column("float", { default: 0 })
|
||||||
progress: number;
|
progress: number;
|
||||||
|
|
||||||
|
|
|
@ -35,4 +35,3 @@ export class UserPreferences {
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ const cancelGameDownload = async (
|
||||||
game.status !== GameStatus.Seeding
|
game.status !== GameStatus.Seeding
|
||||||
) {
|
) {
|
||||||
Downloader.cancelDownload();
|
Downloader.cancelDownload();
|
||||||
if (result.affected) WindowManager.mainWindow.setProgressBar(-1);
|
if (result.affected) WindowManager.mainWindow?.setProgressBar(-1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getSteamGameIconUrl, writePipe } from "@main/services";
|
import { getSteamGameIconUrl } from "@main/services";
|
||||||
import { gameRepository, repackRepository } from "@main/repository";
|
import { gameRepository, repackRepository } from "@main/repository";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
|
|
@ -106,4 +106,4 @@ app.on("activate", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// In this file you can include the rest of your app's specific main process
|
// In this file you can include the rest of your app's specific main process
|
||||||
// code. You can also put them in separate files and import them here.
|
// code. You can also put them in separate files and import them here.
|
||||||
|
|
|
@ -123,4 +123,4 @@ const loadState = async () => {
|
||||||
import("./events");
|
import("./events");
|
||||||
};
|
};
|
||||||
|
|
||||||
loadState().then(() => checkForNewRepacks());
|
loadState().then(() => checkForNewRepacks());
|
||||||
|
|
|
@ -10,139 +10,168 @@ import { TorrentUpdate } from "./torrent-client";
|
||||||
import { HTTPDownloader } from "./http-downloader";
|
import { HTTPDownloader } from "./http-downloader";
|
||||||
import { Unrar } from "../unrar";
|
import { Unrar } from "../unrar";
|
||||||
import { GameStatus } from "@globals";
|
import { GameStatus } from "@globals";
|
||||||
import path from 'node:path';
|
import path from "node:path";
|
||||||
|
|
||||||
interface DownloadStatus {
|
interface DownloadStatus {
|
||||||
numPeers: number;
|
numPeers: number;
|
||||||
numSeeds: number;
|
numSeeds: number;
|
||||||
downloadSpeed: number;
|
downloadSpeed: number;
|
||||||
timeRemaining: number;
|
timeRemaining: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Downloader {
|
export class Downloader {
|
||||||
private static lastHttpDownloader: HTTPDownloader | null = null;
|
private static lastHttpDownloader: HTTPDownloader | null = null;
|
||||||
|
|
||||||
static async usesRealDebrid() {
|
static async usesRealDebrid() {
|
||||||
const userPreferences = await userPreferencesRepository.findOne({ where: { id: 1 } });
|
const userPreferences = await userPreferencesRepository.findOne({
|
||||||
return userPreferences!.realDebridApiToken !== null;
|
where: { id: 1 },
|
||||||
|
});
|
||||||
|
return userPreferences!.realDebridApiToken !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async cancelDownload() {
|
||||||
|
if (!(await this.usesRealDebrid())) {
|
||||||
|
writePipe.write({ action: "cancel" });
|
||||||
|
} else {
|
||||||
|
if (this.lastHttpDownloader) {
|
||||||
|
this.lastHttpDownloader.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async pauseDownload() {
|
||||||
|
if (!(await this.usesRealDebrid())) {
|
||||||
|
writePipe.write({ action: "pause" });
|
||||||
|
} else {
|
||||||
|
if (this.lastHttpDownloader) {
|
||||||
|
this.lastHttpDownloader.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async resumeDownload() {
|
||||||
|
if (!(await this.usesRealDebrid())) {
|
||||||
|
writePipe.write({ action: "pause" });
|
||||||
|
} else {
|
||||||
|
if (this.lastHttpDownloader) {
|
||||||
|
this.lastHttpDownloader.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async downloadGame(game: Game, repack: Repack) {
|
||||||
|
if (!(await this.usesRealDebrid())) {
|
||||||
|
writePipe.write({
|
||||||
|
action: "start",
|
||||||
|
game_id: game.id,
|
||||||
|
magnet: repack.magnet,
|
||||||
|
save_path: game.downloadPath,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const torrent = await RealDebridClient.addMagnet(repack.magnet);
|
||||||
|
if (torrent && torrent.id) {
|
||||||
|
await RealDebridClient.selectAllFiles(torrent.id);
|
||||||
|
const { links } = await RealDebridClient.getInfo(torrent.id);
|
||||||
|
const { download } = await RealDebridClient.unrestrictLink(links[0]);
|
||||||
|
this.lastHttpDownloader = new HTTPDownloader();
|
||||||
|
this.lastHttpDownloader.download(
|
||||||
|
download,
|
||||||
|
game.downloadPath!,
|
||||||
|
game.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateGameProgress(
|
||||||
|
gameId: number,
|
||||||
|
gameUpdate: QueryDeepPartialEntity<Game>,
|
||||||
|
downloadStatus: DownloadStatus
|
||||||
|
) {
|
||||||
|
await gameRepository.update({ id: gameId }, gameUpdate);
|
||||||
|
|
||||||
|
const game = await gameRepository.findOne({
|
||||||
|
where: { id: gameId },
|
||||||
|
relations: { repack: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
gameUpdate.progress === 1 &&
|
||||||
|
gameUpdate.status !== GameStatus.Decompressing
|
||||||
|
) {
|
||||||
|
const userPreferences = await userPreferencesRepository.findOne({
|
||||||
|
where: { id: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userPreferences?.downloadNotificationsEnabled) {
|
||||||
|
new Notification({
|
||||||
|
title: t("download_complete", {
|
||||||
|
ns: "notifications",
|
||||||
|
lng: userPreferences.language,
|
||||||
|
}),
|
||||||
|
body: t("game_ready_to_install", {
|
||||||
|
ns: "notifications",
|
||||||
|
lng: userPreferences.language,
|
||||||
|
title: game?.title,
|
||||||
|
}),
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async cancelDownload() {
|
if (
|
||||||
if (!await this.usesRealDebrid()) {
|
game &&
|
||||||
writePipe.write({ action: "cancel" });
|
gameUpdate.decompressionProgress === 0 &&
|
||||||
} else {
|
gameUpdate.status === GameStatus.Decompressing
|
||||||
if (this.lastHttpDownloader) {
|
) {
|
||||||
this.lastHttpDownloader.cancel();
|
const unrar = await Unrar.fromFilePath(
|
||||||
}
|
game.rarPath!,
|
||||||
}
|
path.join(game.downloadPath!, game.folderName!)
|
||||||
|
);
|
||||||
|
unrar.extract();
|
||||||
|
this.updateGameProgress(
|
||||||
|
gameId,
|
||||||
|
{
|
||||||
|
decompressionProgress: 1,
|
||||||
|
status: GameStatus.Finished,
|
||||||
|
},
|
||||||
|
downloadStatus
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async pauseDownload() {
|
if (WindowManager.mainWindow && game) {
|
||||||
if (!await this.usesRealDebrid()) {
|
const progress = this.getGameProgress(game);
|
||||||
writePipe.write({ action: "pause" });
|
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
|
||||||
} else {
|
|
||||||
if (this.lastHttpDownloader) {
|
WindowManager.mainWindow.webContents.send(
|
||||||
this.lastHttpDownloader.pause();
|
"on-download-progress",
|
||||||
}
|
JSON.parse(
|
||||||
}
|
JSON.stringify({
|
||||||
|
...({
|
||||||
|
progress: gameUpdate.progress,
|
||||||
|
bytesDownloaded: gameUpdate.bytesDownloaded,
|
||||||
|
fileSize: gameUpdate.fileSize,
|
||||||
|
gameId,
|
||||||
|
numPeers: downloadStatus.numPeers,
|
||||||
|
numSeeds: downloadStatus.numSeeds,
|
||||||
|
downloadSpeed: downloadStatus.downloadSpeed,
|
||||||
|
timeRemaining: downloadStatus.timeRemaining,
|
||||||
|
} as TorrentUpdate),
|
||||||
|
game,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async resumeDownload() {
|
static getGameProgress(game: Game) {
|
||||||
if (!await this.usesRealDebrid()) {
|
if (game.status === GameStatus.CheckingFiles)
|
||||||
writePipe.write({ action: "pause" });
|
return game.fileVerificationProgress;
|
||||||
} else {
|
if (game.status === GameStatus.Decompressing)
|
||||||
if (this.lastHttpDownloader) {
|
return game.decompressionProgress;
|
||||||
this.lastHttpDownloader.resume();
|
return game.progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static async downloadGame(game: Game, repack: Repack) {
|
|
||||||
if (!await this.usesRealDebrid()) {
|
|
||||||
writePipe.write({
|
|
||||||
action: "start",
|
|
||||||
game_id: game.id,
|
|
||||||
magnet: repack.magnet,
|
|
||||||
save_path: game.downloadPath,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const torrent = await RealDebridClient.addMagnet(repack.magnet);
|
|
||||||
if (torrent && torrent.id) {
|
|
||||||
await RealDebridClient.selectAllFiles(torrent.id);
|
|
||||||
const { links } = await RealDebridClient.getInfo(torrent.id);
|
|
||||||
const { download } = await RealDebridClient.unrestrictLink(links[0]);
|
|
||||||
this.lastHttpDownloader = new HTTPDownloader();
|
|
||||||
this.lastHttpDownloader.download(download, game.downloadPath!, game.id);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async updateGameProgress(gameId: number, gameUpdate: QueryDeepPartialEntity<Game>, downloadStatus: DownloadStatus) {
|
|
||||||
await gameRepository.update({ id: gameId }, gameUpdate);
|
|
||||||
|
|
||||||
const game = await gameRepository.findOne({
|
|
||||||
where: { id: gameId },
|
|
||||||
relations: { repack: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (gameUpdate.progress === 1 && gameUpdate.status !== GameStatus.Decompressing) {
|
|
||||||
const userPreferences = await userPreferencesRepository.findOne({
|
|
||||||
where: { id: 1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userPreferences?.downloadNotificationsEnabled) {
|
|
||||||
new Notification({
|
|
||||||
title: t("download_complete", {
|
|
||||||
ns: "notifications",
|
|
||||||
lng: userPreferences.language,
|
|
||||||
}),
|
|
||||||
body: t("game_ready_to_install", {
|
|
||||||
ns: "notifications",
|
|
||||||
lng: userPreferences.language,
|
|
||||||
title: game?.title,
|
|
||||||
}),
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game && gameUpdate.decompressionProgress === 0 && gameUpdate.status === GameStatus.Decompressing) {
|
|
||||||
const unrar = await Unrar.fromFilePath(game.rarPath!, path.join(game.downloadPath!, game.folderName!));
|
|
||||||
unrar.extract();
|
|
||||||
this.updateGameProgress(gameId, {
|
|
||||||
decompressionProgress: 1,
|
|
||||||
status: GameStatus.Finished,
|
|
||||||
}, downloadStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WindowManager.mainWindow && game) {
|
|
||||||
const progress = this.getGameProgress(game);
|
|
||||||
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
|
|
||||||
|
|
||||||
WindowManager.mainWindow.webContents.send(
|
|
||||||
"on-download-progress",
|
|
||||||
JSON.parse(JSON.stringify({
|
|
||||||
...{
|
|
||||||
progress: gameUpdate.progress,
|
|
||||||
bytesDownloaded: gameUpdate.bytesDownloaded,
|
|
||||||
fileSize: gameUpdate.fileSize,
|
|
||||||
gameId,
|
|
||||||
numPeers: downloadStatus.numPeers,
|
|
||||||
numSeeds: downloadStatus.numSeeds,
|
|
||||||
downloadSpeed: downloadStatus.downloadSpeed,
|
|
||||||
timeRemaining: downloadStatus.timeRemaining,
|
|
||||||
} as TorrentUpdate, game
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static getGameProgress(game: Game) {
|
|
||||||
if (game.status === GameStatus.CheckingFiles) return game.fileVerificationProgress;
|
|
||||||
if (game.status === GameStatus.Decompressing) return game.decompressionProgress;
|
|
||||||
return game.progress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,94 +1,106 @@
|
||||||
import { Game } from '@main/entity';
|
import { Game } from "@main/entity";
|
||||||
import { ElectronDownloadManager } from 'electron-dl-manager';
|
import { ElectronDownloadManager } from "electron-dl-manager";
|
||||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
import { WindowManager } from '../window-manager';
|
import { WindowManager } from "../window-manager";
|
||||||
import { Downloader } from './downloader';
|
import { Downloader } from "./downloader";
|
||||||
import { GameStatus } from '@globals';
|
import { GameStatus } from "@globals";
|
||||||
|
|
||||||
function dropExtension(fileName: string) {
|
function dropExtension(fileName: string) {
|
||||||
return fileName.split('.').slice(0, -1).join('.');
|
return fileName.split(".").slice(0, -1).join(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HTTPDownloader {
|
export class HTTPDownloader {
|
||||||
private downloadManager: ElectronDownloadManager;
|
private downloadManager: ElectronDownloadManager;
|
||||||
private downloadId: string | null = null;
|
private downloadId: string | null = null;
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.downloadManager = new ElectronDownloadManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
async download(url: string, destination: string, gameId: number) {
|
|
||||||
const window = WindowManager.mainWindow;
|
|
||||||
|
|
||||||
this.downloadId = await this.downloadManager.download({
|
constructor() {
|
||||||
url,
|
this.downloadManager = new ElectronDownloadManager();
|
||||||
window: window!,
|
}
|
||||||
callbacks: {
|
|
||||||
onDownloadStarted: async (ev) => {
|
|
||||||
const updatePayload: QueryDeepPartialEntity<Game> = {
|
|
||||||
status: GameStatus.Downloading,
|
|
||||||
progress: 0,
|
|
||||||
bytesDownloaded: 0,
|
|
||||||
fileSize: ev.item.getTotalBytes(),
|
|
||||||
rarPath: `${destination}/.rd/${ev.resolvedFilename}`,
|
|
||||||
folderName: dropExtension(ev.resolvedFilename)
|
|
||||||
};
|
|
||||||
const downloadStatus = {
|
|
||||||
numPeers: 0,
|
|
||||||
numSeeds: 0,
|
|
||||||
downloadSpeed: 0,
|
|
||||||
timeRemaining: Number.POSITIVE_INFINITY,
|
|
||||||
};
|
|
||||||
await Downloader.updateGameProgress(gameId, updatePayload, downloadStatus);
|
|
||||||
},
|
|
||||||
onDownloadCompleted: async (ev) => {
|
|
||||||
const updatePayload: QueryDeepPartialEntity<Game> = {
|
|
||||||
progress: 1,
|
|
||||||
decompressionProgress: 0,
|
|
||||||
bytesDownloaded: ev.item.getReceivedBytes(),
|
|
||||||
status: GameStatus.Decompressing,
|
|
||||||
};
|
|
||||||
const downloadStatus = {
|
|
||||||
numPeers: 1,
|
|
||||||
numSeeds: 1,
|
|
||||||
downloadSpeed: 0,
|
|
||||||
timeRemaining: 0,
|
|
||||||
};
|
|
||||||
await Downloader.updateGameProgress(gameId, updatePayload, downloadStatus);
|
|
||||||
},
|
|
||||||
onDownloadProgress: async (ev) => {
|
|
||||||
const updatePayload: QueryDeepPartialEntity<Game> = {
|
|
||||||
progress: ev.percentCompleted / 100,
|
|
||||||
bytesDownloaded: ev.item.getReceivedBytes(),
|
|
||||||
};
|
|
||||||
const downloadStatus = {
|
|
||||||
numPeers: 1,
|
|
||||||
numSeeds: 1,
|
|
||||||
downloadSpeed: ev.downloadRateBytesPerSecond,
|
|
||||||
timeRemaining: ev.estimatedTimeRemainingSeconds,
|
|
||||||
};
|
|
||||||
await Downloader.updateGameProgress(gameId, updatePayload, downloadStatus);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directory: `${destination}/.rd/`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pause() {
|
async download(url: string, destination: string, gameId: number) {
|
||||||
if (this.downloadId) {
|
const window = WindowManager.mainWindow;
|
||||||
this.downloadManager.pauseDownload(this.downloadId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel() {
|
this.downloadId = await this.downloadManager.download({
|
||||||
if (this.downloadId) {
|
url,
|
||||||
this.downloadManager.cancelDownload(this.downloadId);
|
window: window!,
|
||||||
}
|
callbacks: {
|
||||||
}
|
onDownloadStarted: async (ev) => {
|
||||||
|
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||||
|
status: GameStatus.Downloading,
|
||||||
|
progress: 0,
|
||||||
|
bytesDownloaded: 0,
|
||||||
|
fileSize: ev.item.getTotalBytes(),
|
||||||
|
rarPath: `${destination}/.rd/${ev.resolvedFilename}`,
|
||||||
|
folderName: dropExtension(ev.resolvedFilename),
|
||||||
|
};
|
||||||
|
const downloadStatus = {
|
||||||
|
numPeers: 0,
|
||||||
|
numSeeds: 0,
|
||||||
|
downloadSpeed: 0,
|
||||||
|
timeRemaining: Number.POSITIVE_INFINITY,
|
||||||
|
};
|
||||||
|
await Downloader.updateGameProgress(
|
||||||
|
gameId,
|
||||||
|
updatePayload,
|
||||||
|
downloadStatus
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onDownloadCompleted: async (ev) => {
|
||||||
|
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||||
|
progress: 1,
|
||||||
|
decompressionProgress: 0,
|
||||||
|
bytesDownloaded: ev.item.getReceivedBytes(),
|
||||||
|
status: GameStatus.Decompressing,
|
||||||
|
};
|
||||||
|
const downloadStatus = {
|
||||||
|
numPeers: 1,
|
||||||
|
numSeeds: 1,
|
||||||
|
downloadSpeed: 0,
|
||||||
|
timeRemaining: 0,
|
||||||
|
};
|
||||||
|
await Downloader.updateGameProgress(
|
||||||
|
gameId,
|
||||||
|
updatePayload,
|
||||||
|
downloadStatus
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onDownloadProgress: async (ev) => {
|
||||||
|
const updatePayload: QueryDeepPartialEntity<Game> = {
|
||||||
|
progress: ev.percentCompleted / 100,
|
||||||
|
bytesDownloaded: ev.item.getReceivedBytes(),
|
||||||
|
};
|
||||||
|
const downloadStatus = {
|
||||||
|
numPeers: 1,
|
||||||
|
numSeeds: 1,
|
||||||
|
downloadSpeed: ev.downloadRateBytesPerSecond,
|
||||||
|
timeRemaining: ev.estimatedTimeRemainingSeconds,
|
||||||
|
};
|
||||||
|
await Downloader.updateGameProgress(
|
||||||
|
gameId,
|
||||||
|
updatePayload,
|
||||||
|
downloadStatus
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
directory: `${destination}/.rd/`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
resume() {
|
pause() {
|
||||||
if (this.downloadId) {
|
if (this.downloadId) {
|
||||||
this.downloadManager.resumeDownload(this.downloadId);
|
this.downloadManager.pauseDownload(this.downloadId);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
if (this.downloadId) {
|
||||||
|
this.downloadManager.cancelDownload(this.downloadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resume() {
|
||||||
|
if (this.downloadId) {
|
||||||
|
this.downloadManager.resumeDownload(this.downloadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,53 +1,53 @@
|
||||||
export interface RealDebridUnrestrictLink {
|
export interface RealDebridUnrestrictLink {
|
||||||
id: string;
|
id: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
filesize: number;
|
filesize: number;
|
||||||
link: string;
|
link: string;
|
||||||
host: string;
|
host: string;
|
||||||
host_icon: string;
|
host_icon: string;
|
||||||
chunks: number;
|
chunks: number;
|
||||||
crc: number;
|
crc: number;
|
||||||
download: string;
|
download: string;
|
||||||
streamable: number;
|
streamable: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RealDebridAddMagnet {
|
export interface RealDebridAddMagnet {
|
||||||
"id": string,
|
id: string;
|
||||||
// URL of the created ressource
|
// URL of the created ressource
|
||||||
"uri": string
|
uri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RealDebridTorrentInfo {
|
export interface RealDebridTorrentInfo {
|
||||||
"id": string,
|
id: string;
|
||||||
"filename": string,
|
filename: string;
|
||||||
"original_filename": string, // Original name of the torrent
|
original_filename: string; // Original name of the torrent
|
||||||
"hash": string, // SHA1 Hash of the torrent
|
hash: string; // SHA1 Hash of the torrent
|
||||||
"bytes": number, // Size of selected files only
|
bytes: number; // Size of selected files only
|
||||||
"original_bytes": number, // Total size of the torrent
|
original_bytes: number; // Total size of the torrent
|
||||||
"host": string, // Host main domain
|
host: string; // Host main domain
|
||||||
"split": number, // Split size of links
|
split: number; // Split size of links
|
||||||
"progress": number, // Possible values: 0 to 100
|
progress: number; // Possible values: 0 to 100
|
||||||
"status": "downloaded", // Current status of the torrent: magnet_error, magnet_conversion, waiting_files_selection, queued, downloading, downloaded, error, virus, compressing, uploading, dead
|
status: "downloaded"; // Current status of the torrent: magnet_error, magnet_conversion, waiting_files_selection, queued, downloading, downloaded, error, virus, compressing, uploading, dead
|
||||||
"added": string, // jsonDate
|
added: string; // jsonDate
|
||||||
"files": [
|
files: [
|
||||||
{
|
{
|
||||||
"id": number,
|
id: number;
|
||||||
"path": string, // Path to the file inside the torrent, starting with "/"
|
path: string; // Path to the file inside the torrent, starting with "/"
|
||||||
"bytes": number,
|
bytes: number;
|
||||||
"selected": number // 0 or 1
|
selected: number; // 0 or 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": number,
|
id: number;
|
||||||
"path": string, // Path to the file inside the torrent, starting with "/"
|
path: string; // Path to the file inside the torrent, starting with "/"
|
||||||
"bytes": number,
|
bytes: number;
|
||||||
"selected": number // 0 or 1
|
selected: number; // 0 or 1
|
||||||
}
|
},
|
||||||
],
|
];
|
||||||
"links": [
|
links: [
|
||||||
"string" // Host URL
|
"string", // Host URL
|
||||||
],
|
];
|
||||||
"ended": string, // !! Only present when finished, jsonDate
|
ended: string; // !! Only present when finished, jsonDate
|
||||||
"speed": number, // !! Only present in "downloading", "compressing", "uploading" status
|
speed: number; // !! Only present in "downloading", "compressing", "uploading" status
|
||||||
"seeders": number // !! Only present in "downloading", "magnet_conversion" status
|
seeders: number; // !! Only present in "downloading", "magnet_conversion" status
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,61 @@
|
||||||
import { userPreferencesRepository } from "@main/repository";
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { RealDebridAddMagnet, RealDebridTorrentInfo, RealDebridUnrestrictLink } from "./real-debrid-types";
|
import {
|
||||||
|
RealDebridAddMagnet,
|
||||||
|
RealDebridTorrentInfo,
|
||||||
|
RealDebridUnrestrictLink,
|
||||||
|
} from "./real-debrid-types";
|
||||||
|
|
||||||
const base = "https://api.real-debrid.com/rest/1.0";
|
const base = "https://api.real-debrid.com/rest/1.0";
|
||||||
|
|
||||||
export class RealDebridClient {
|
export class RealDebridClient {
|
||||||
static async addMagnet(magnet: string) {
|
static async addMagnet(magnet: string) {
|
||||||
const response = await fetch(`${base}/torrents/addMagnet`, {
|
const response = await fetch(`${base}/torrents/addMagnet`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${await this.getApiToken()}`,
|
Authorization: `Bearer ${await this.getApiToken()}`,
|
||||||
},
|
},
|
||||||
body: `magnet=${encodeURIComponent(magnet)}`
|
body: `magnet=${encodeURIComponent(magnet)}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.json() as Promise<RealDebridAddMagnet>;
|
return response.json() as Promise<RealDebridAddMagnet>;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getInfo(id: string) {
|
static async getInfo(id: string) {
|
||||||
const response = await fetch(`${base}/torrents/info/${id}`, {
|
const response = await fetch(`${base}/torrents/info/${id}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${await this.getApiToken()}`
|
Authorization: `Bearer ${await this.getApiToken()}`,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.json() as Promise<RealDebridTorrentInfo>;
|
return response.json() as Promise<RealDebridTorrentInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async selectAllFiles(id: string) {
|
static async selectAllFiles(id: string) {
|
||||||
const response = await fetch(`${base}/torrents/selectFiles/${id}`, {
|
await fetch(`${base}/torrents/selectFiles/${id}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${await this.getApiToken()}`,
|
Authorization: `Bearer ${await this.getApiToken()}`,
|
||||||
},
|
},
|
||||||
body: "files=all"
|
body: "files=all",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async unrestrictLink(link: string) {
|
static async unrestrictLink(link: string) {
|
||||||
const response = await fetch(`${base}/unrestrict/link`, {
|
const response = await fetch(`${base}/unrestrict/link`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${await this.getApiToken()}`,
|
Authorization: `Bearer ${await this.getApiToken()}`,
|
||||||
},
|
},
|
||||||
body: `link=${link}`
|
body: `link=${link}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.json() as Promise<RealDebridUnrestrictLink>;
|
return response.json() as Promise<RealDebridUnrestrictLink>;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getApiToken() {
|
static getApiToken() {
|
||||||
return userPreferencesRepository.findOne({ where: { id: 1 } }).then(userPreferences => userPreferences!.realDebridApiToken);
|
return userPreferencesRepository
|
||||||
}
|
.findOne({ where: { id: 1 } })
|
||||||
}
|
.then((userPreferences) => userPreferences!.realDebridApiToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -131,4 +131,4 @@ export class TorrentClient {
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
import { Extractor, createExtractorFromFile } from 'node-unrar-js';
|
import { Extractor, createExtractorFromFile } from "node-unrar-js";
|
||||||
import fs from 'node:fs';
|
import fs from "node:fs";
|
||||||
|
|
||||||
const wasmBinary = fs.readFileSync(require.resolve('node-unrar-js/esm/js/unrar.wasm'));
|
const wasmBinary = fs.readFileSync(
|
||||||
|
require.resolve("node-unrar-js/esm/js/unrar.wasm")
|
||||||
|
);
|
||||||
|
|
||||||
export class Unrar {
|
export class Unrar {
|
||||||
private constructor(private extractor: Extractor<Uint8Array>) { }
|
private constructor(private extractor: Extractor<Uint8Array>) {}
|
||||||
|
|
||||||
static async fromFilePath(filePath: string, targetFolder: string) {
|
static async fromFilePath(filePath: string, targetFolder: string) {
|
||||||
const extractor = await createExtractorFromFile({
|
const extractor = await createExtractorFromFile({
|
||||||
filepath: filePath,
|
filepath: filePath,
|
||||||
targetPath: targetFolder,
|
targetPath: targetFolder,
|
||||||
wasmBinary,
|
wasmBinary,
|
||||||
});
|
});
|
||||||
return new Unrar(extractor);
|
return new Unrar(extractor);
|
||||||
}
|
}
|
||||||
|
|
||||||
extract() {
|
extract() {
|
||||||
const files = this.extractor.extract().files;
|
const files = this.extractor.extract().files;
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
console.log("File:", file.fileHeader.name);
|
console.log("File:", file.fileHeader.name);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,8 @@ export function Sidebar() {
|
||||||
}, [isResizing]);
|
}, [isResizing]);
|
||||||
|
|
||||||
const getGameTitle = (game: Game) => {
|
const getGameTitle = (game: Game) => {
|
||||||
if (game.status === GameStatus.Paused) return t("paused", { title: game.title });
|
if (game.status === GameStatus.Paused)
|
||||||
|
return t("paused", { title: game.title });
|
||||||
|
|
||||||
if (gameDownloading?.id === game.id) {
|
if (gameDownloading?.id === game.id) {
|
||||||
const isVerifying = GameStatus.isVerifying(gameDownloading.status);
|
const isVerifying = GameStatus.isVerifying(gameDownloading.status);
|
||||||
|
|
|
@ -99,7 +99,7 @@ export function useDownload() {
|
||||||
dispatch(setGameDeleting(gameId));
|
dispatch(setGameDeleting(gameId));
|
||||||
return window.electron.deleteGameFolder(gameId);
|
return window.electron.deleteGameFolder(gameId);
|
||||||
})
|
})
|
||||||
.catch(() => { })
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
updateLibrary();
|
updateLibrary();
|
||||||
dispatch(removeGameFromDeleting(gameId));
|
dispatch(removeGameFromDeleting(gameId));
|
||||||
|
|
|
@ -111,12 +111,12 @@ export function Settings() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label={t("real_debrid_api_token")}
|
label={t("real_debrid_api_token")}
|
||||||
value={form.realDebridApiToken ?? ""}
|
value={form.realDebridApiToken ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
updateUserPreferences("realDebridApiToken", event.target.value);
|
updateUserPreferences("realDebridApiToken", event.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue