mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-15 04:32:13 +00:00
feat: adding real debrid downloads
This commit is contained in:
parent
a1e41ea464
commit
13644c60e8
3 changed files with 203 additions and 46 deletions
|
@ -4,56 +4,58 @@ import { TorrentDownloader } from "./torrent-downloader";
|
||||||
import { WindowManager } from "../window-manager";
|
import { WindowManager } from "../window-manager";
|
||||||
import { downloadQueueRepository, gameRepository } from "@main/repository";
|
import { downloadQueueRepository, gameRepository } from "@main/repository";
|
||||||
import { publishDownloadCompleteNotification } from "../notifications";
|
import { publishDownloadCompleteNotification } from "../notifications";
|
||||||
|
import { RealDebridDownloader } from "./real-debrid-downloader";
|
||||||
|
import type { DownloadProgress } from "@types";
|
||||||
|
|
||||||
export class DownloadManager {
|
export class DownloadManager {
|
||||||
private static currentDownloader: Downloader | null = null;
|
private static currentDownloader: Downloader | null = null;
|
||||||
|
|
||||||
public static async watchDownloads() {
|
public static async watchDownloads() {
|
||||||
|
let status: DownloadProgress | null = null;
|
||||||
|
|
||||||
if (this.currentDownloader === Downloader.RealDebrid) {
|
if (this.currentDownloader === Downloader.RealDebrid) {
|
||||||
throw new Error();
|
status = await RealDebridDownloader.getStatus();
|
||||||
} else {
|
} else {
|
||||||
const status = await TorrentDownloader.getStatus();
|
status = await TorrentDownloader.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
const { gameId, progress } = status;
|
const { gameId, progress } = status;
|
||||||
|
|
||||||
const game = await gameRepository.findOne({
|
const game = await gameRepository.findOne({
|
||||||
where: { id: gameId, isDeleted: false },
|
where: { id: gameId, isDeleted: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (WindowManager.mainWindow && game) {
|
||||||
|
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
|
||||||
|
|
||||||
|
WindowManager.mainWindow.webContents.send(
|
||||||
|
"on-download-progress",
|
||||||
|
JSON.parse(
|
||||||
|
JSON.stringify({
|
||||||
|
...status,
|
||||||
|
game,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress === 1 && game) {
|
||||||
|
publishDownloadCompleteNotification(game);
|
||||||
|
|
||||||
|
await downloadQueueRepository.delete({ game });
|
||||||
|
|
||||||
|
const [nextQueueItem] = await downloadQueueRepository.find({
|
||||||
|
order: {
|
||||||
|
id: "DESC",
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
game: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (WindowManager.mainWindow && game) {
|
if (nextQueueItem) {
|
||||||
WindowManager.mainWindow.setProgressBar(
|
this.resumeDownload(nextQueueItem.game);
|
||||||
progress === 1 ? -1 : progress
|
|
||||||
);
|
|
||||||
|
|
||||||
WindowManager.mainWindow.webContents.send(
|
|
||||||
"on-download-progress",
|
|
||||||
JSON.parse(
|
|
||||||
JSON.stringify({
|
|
||||||
...status,
|
|
||||||
game,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.progress === 1 && game) {
|
|
||||||
publishDownloadCompleteNotification(game);
|
|
||||||
|
|
||||||
await downloadQueueRepository.delete({ game });
|
|
||||||
|
|
||||||
const [nextQueueItem] = await downloadQueueRepository.find({
|
|
||||||
order: {
|
|
||||||
id: "DESC",
|
|
||||||
},
|
|
||||||
relations: {
|
|
||||||
game: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (nextQueueItem) {
|
|
||||||
this.resumeDownload(nextQueueItem.game);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +63,7 @@ export class DownloadManager {
|
||||||
|
|
||||||
static async pauseDownload() {
|
static async pauseDownload() {
|
||||||
if (this.currentDownloader === Downloader.RealDebrid) {
|
if (this.currentDownloader === Downloader.RealDebrid) {
|
||||||
throw new Error();
|
RealDebridDownloader.pauseDownload();
|
||||||
} else {
|
} else {
|
||||||
await TorrentDownloader.pauseDownload();
|
await TorrentDownloader.pauseDownload();
|
||||||
}
|
}
|
||||||
|
@ -72,7 +74,8 @@ export class DownloadManager {
|
||||||
|
|
||||||
static async resumeDownload(game: Game) {
|
static async resumeDownload(game: Game) {
|
||||||
if (game.downloader === Downloader.RealDebrid) {
|
if (game.downloader === Downloader.RealDebrid) {
|
||||||
throw new Error();
|
RealDebridDownloader.startDownload(game);
|
||||||
|
this.currentDownloader = Downloader.RealDebrid;
|
||||||
} else {
|
} else {
|
||||||
TorrentDownloader.startDownload(game);
|
TorrentDownloader.startDownload(game);
|
||||||
this.currentDownloader = Downloader.Torrent;
|
this.currentDownloader = Downloader.Torrent;
|
||||||
|
@ -81,7 +84,7 @@ export class DownloadManager {
|
||||||
|
|
||||||
static async cancelDownload(gameId: number) {
|
static async cancelDownload(gameId: number) {
|
||||||
if (this.currentDownloader === Downloader.RealDebrid) {
|
if (this.currentDownloader === Downloader.RealDebrid) {
|
||||||
throw new Error();
|
RealDebridDownloader.cancelDownload();
|
||||||
} else {
|
} else {
|
||||||
TorrentDownloader.cancelDownload(gameId);
|
TorrentDownloader.cancelDownload(gameId);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +95,8 @@ export class DownloadManager {
|
||||||
|
|
||||||
static async startDownload(game: Game) {
|
static async startDownload(game: Game) {
|
||||||
if (game.downloader === Downloader.RealDebrid) {
|
if (game.downloader === Downloader.RealDebrid) {
|
||||||
throw new Error();
|
RealDebridDownloader.startDownload(game);
|
||||||
|
this.currentDownloader = Downloader.RealDebrid;
|
||||||
} else {
|
} else {
|
||||||
TorrentDownloader.startDownload(game);
|
TorrentDownloader.startDownload(game);
|
||||||
this.currentDownloader = Downloader.Torrent;
|
this.currentDownloader = Downloader.Torrent;
|
||||||
|
|
152
src/main/services/download/real-debrid-downloader.ts
Normal file
152
src/main/services/download/real-debrid-downloader.ts
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { Game } from "@main/entity";
|
||||||
|
import { RealDebridClient } from "../real-debrid";
|
||||||
|
import axios, { AxiosProgressEvent } from "axios";
|
||||||
|
import { gameRepository } from "@main/repository";
|
||||||
|
import { calculateETA } from "./helpers";
|
||||||
|
import { DownloadProgress } from "@types";
|
||||||
|
|
||||||
|
export class RealDebridDownloader {
|
||||||
|
private static downloadingGame: Game | null = null;
|
||||||
|
|
||||||
|
private static realDebridTorrentId: string | null = null;
|
||||||
|
private static lastProgressEvent: AxiosProgressEvent | null = null;
|
||||||
|
private static abortController: AbortController | null = null;
|
||||||
|
|
||||||
|
private static async getRealDebridDownloadUrl() {
|
||||||
|
if (this.realDebridTorrentId) {
|
||||||
|
const torrentInfo = await RealDebridClient.getTorrentInfo(
|
||||||
|
this.realDebridTorrentId
|
||||||
|
);
|
||||||
|
|
||||||
|
const { status, links } = torrentInfo;
|
||||||
|
|
||||||
|
if (status === "waiting_files_selection") {
|
||||||
|
await RealDebridClient.selectAllFiles(this.realDebridTorrentId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "downloaded") {
|
||||||
|
const [link] = links;
|
||||||
|
const { download } = await RealDebridClient.unrestrictLink(link);
|
||||||
|
return decodeURIComponent(download);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getStatus() {
|
||||||
|
if (this.lastProgressEvent) {
|
||||||
|
await gameRepository.update(
|
||||||
|
{ id: this.downloadingGame!.id },
|
||||||
|
{
|
||||||
|
bytesDownloaded: this.lastProgressEvent.loaded,
|
||||||
|
fileSize: this.lastProgressEvent.total,
|
||||||
|
progress: this.lastProgressEvent.progress,
|
||||||
|
status: "active",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const progress = {
|
||||||
|
numPeers: 0,
|
||||||
|
numSeeds: 0,
|
||||||
|
downloadSpeed: this.lastProgressEvent.rate,
|
||||||
|
timeRemaining: calculateETA(
|
||||||
|
this.lastProgressEvent.total ?? 0,
|
||||||
|
this.lastProgressEvent.loaded,
|
||||||
|
this.lastProgressEvent.rate ?? 0
|
||||||
|
),
|
||||||
|
isDownloadingMetadata: false,
|
||||||
|
isCheckingFiles: false,
|
||||||
|
progress: this.lastProgressEvent.progress,
|
||||||
|
gameId: this.downloadingGame!.id,
|
||||||
|
} as DownloadProgress;
|
||||||
|
|
||||||
|
if (this.lastProgressEvent.progress === 1) {
|
||||||
|
this.pauseDownload();
|
||||||
|
}
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.realDebridTorrentId && this.downloadingGame) {
|
||||||
|
const torrentInfo = await RealDebridClient.getTorrentInfo(
|
||||||
|
this.realDebridTorrentId
|
||||||
|
);
|
||||||
|
|
||||||
|
const { status } = torrentInfo;
|
||||||
|
|
||||||
|
if (status === "downloaded") {
|
||||||
|
this.startDownload(this.downloadingGame);
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress = torrentInfo.progress / 100;
|
||||||
|
const totalDownloaded = progress * torrentInfo.bytes;
|
||||||
|
|
||||||
|
return {
|
||||||
|
numPeers: 0,
|
||||||
|
numSeeds: torrentInfo.seeders,
|
||||||
|
downloadSpeed: torrentInfo.speed,
|
||||||
|
timeRemaining: calculateETA(
|
||||||
|
torrentInfo.bytes,
|
||||||
|
totalDownloaded,
|
||||||
|
torrentInfo.speed
|
||||||
|
),
|
||||||
|
isDownloadingMetadata: status === "magnet_conversion",
|
||||||
|
} as DownloadProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async pauseDownload() {
|
||||||
|
if (this.abortController) {
|
||||||
|
this.abortController.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.abortController = null;
|
||||||
|
this.realDebridTorrentId = null;
|
||||||
|
this.lastProgressEvent = null;
|
||||||
|
this.downloadingGame = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async startDownload(game: Game) {
|
||||||
|
this.realDebridTorrentId = await RealDebridClient.getTorrentId(game!.uri!);
|
||||||
|
this.downloadingGame = game;
|
||||||
|
|
||||||
|
const downloadUrl = await this.getRealDebridDownloadUrl();
|
||||||
|
|
||||||
|
if (downloadUrl) {
|
||||||
|
this.realDebridTorrentId = null;
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
|
const response = await axios.get(downloadUrl, {
|
||||||
|
responseType: "stream",
|
||||||
|
signal: this.abortController.signal,
|
||||||
|
headers: {
|
||||||
|
Range: `bytes=0-`,
|
||||||
|
},
|
||||||
|
onDownloadProgress: (progressEvent) => {
|
||||||
|
this.lastProgressEvent = progressEvent;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const filename = path.win32.basename(downloadUrl);
|
||||||
|
|
||||||
|
const downloadPath = path.join(game.downloadPath!, filename);
|
||||||
|
|
||||||
|
await gameRepository.update(
|
||||||
|
{ id: this.downloadingGame.id },
|
||||||
|
{ folderName: filename }
|
||||||
|
);
|
||||||
|
|
||||||
|
response.data.pipe(fs.createWriteStream(downloadPath, { flags: "a" }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async cancelDownload() {
|
||||||
|
return this.pauseDownload();
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,9 +8,9 @@ import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity
|
||||||
import { calculateETA } from "./helpers";
|
import { calculateETA } from "./helpers";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {
|
import {
|
||||||
type CancelDownloadPayload,
|
CancelDownloadPayload,
|
||||||
type StartDownloadPayload,
|
StartDownloadPayload,
|
||||||
type PauseDownloadPayload,
|
PauseDownloadPayload,
|
||||||
LibtorrentStatus,
|
LibtorrentStatus,
|
||||||
LibtorrentPayload,
|
LibtorrentPayload,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
@ -67,6 +67,7 @@ export class TorrentDownloader {
|
||||||
bytesDownloaded,
|
bytesDownloaded,
|
||||||
fileSize,
|
fileSize,
|
||||||
progress,
|
progress,
|
||||||
|
status: "active",
|
||||||
};
|
};
|
||||||
|
|
||||||
await gameRepository.update(
|
await gameRepository.update(
|
||||||
|
|
Loading…
Reference in a new issue