feat: adding real debrid downloads

This commit is contained in:
Chubby Granny Chaser 2024-06-27 21:52:04 +01:00
parent a1e41ea464
commit 13644c60e8
No known key found for this signature in database
3 changed files with 203 additions and 46 deletions

View file

@ -4,56 +4,58 @@ import { TorrentDownloader } from "./torrent-downloader";
import { WindowManager } from "../window-manager";
import { downloadQueueRepository, gameRepository } from "@main/repository";
import { publishDownloadCompleteNotification } from "../notifications";
import { RealDebridDownloader } from "./real-debrid-downloader";
import type { DownloadProgress } from "@types";
export class DownloadManager {
private static currentDownloader: Downloader | null = null;
public static async watchDownloads() {
let status: DownloadProgress | null = null;
if (this.currentDownloader === Downloader.RealDebrid) {
throw new Error();
status = await RealDebridDownloader.getStatus();
} else {
const status = await TorrentDownloader.getStatus();
status = await TorrentDownloader.getStatus();
}
if (status) {
const { gameId, progress } = status;
if (status) {
const { gameId, progress } = status;
const game = await gameRepository.findOne({
where: { id: gameId, isDeleted: false },
const game = await gameRepository.findOne({
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) {
WindowManager.mainWindow.setProgressBar(
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);
}
if (nextQueueItem) {
this.resumeDownload(nextQueueItem.game);
}
}
}
@ -61,7 +63,7 @@ export class DownloadManager {
static async pauseDownload() {
if (this.currentDownloader === Downloader.RealDebrid) {
throw new Error();
RealDebridDownloader.pauseDownload();
} else {
await TorrentDownloader.pauseDownload();
}
@ -72,7 +74,8 @@ export class DownloadManager {
static async resumeDownload(game: Game) {
if (game.downloader === Downloader.RealDebrid) {
throw new Error();
RealDebridDownloader.startDownload(game);
this.currentDownloader = Downloader.RealDebrid;
} else {
TorrentDownloader.startDownload(game);
this.currentDownloader = Downloader.Torrent;
@ -81,7 +84,7 @@ export class DownloadManager {
static async cancelDownload(gameId: number) {
if (this.currentDownloader === Downloader.RealDebrid) {
throw new Error();
RealDebridDownloader.cancelDownload();
} else {
TorrentDownloader.cancelDownload(gameId);
}
@ -92,7 +95,8 @@ export class DownloadManager {
static async startDownload(game: Game) {
if (game.downloader === Downloader.RealDebrid) {
throw new Error();
RealDebridDownloader.startDownload(game);
this.currentDownloader = Downloader.RealDebrid;
} else {
TorrentDownloader.startDownload(game);
this.currentDownloader = Downloader.Torrent;

View 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();
}
}

View file

@ -8,9 +8,9 @@ import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity
import { calculateETA } from "./helpers";
import axios from "axios";
import {
type CancelDownloadPayload,
type StartDownloadPayload,
type PauseDownloadPayload,
CancelDownloadPayload,
StartDownloadPayload,
PauseDownloadPayload,
LibtorrentStatus,
LibtorrentPayload,
} from "./types";
@ -67,6 +67,7 @@ export class TorrentDownloader {
bytesDownloaded,
fileSize,
progress,
status: "active",
};
await gameRepository.update(