mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 21:44:55 +03:00
feat: reducing http downloads duplicate
This commit is contained in:
parent
d098887c51
commit
bbe68a0aff
@ -20,8 +20,6 @@ autoUpdater.setFeedURL({
|
|||||||
|
|
||||||
autoUpdater.logger = logger;
|
autoUpdater.logger = logger;
|
||||||
|
|
||||||
logger.log("Init Hydra");
|
|
||||||
|
|
||||||
const gotTheLock = app.requestSingleInstanceLock();
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
if (!gotTheLock) app.quit();
|
if (!gotTheLock) app.quit();
|
||||||
|
|
||||||
@ -123,7 +121,6 @@ app.on("window-all-closed", () => {
|
|||||||
app.on("before-quit", () => {
|
app.on("before-quit", () => {
|
||||||
/* Disconnects libtorrent */
|
/* Disconnects libtorrent */
|
||||||
PythonInstance.kill();
|
PythonInstance.kill();
|
||||||
logger.log("Quit Hydra");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on("activate", () => {
|
app.on("activate", () => {
|
||||||
|
@ -7,7 +7,7 @@ import { publishDownloadCompleteNotification } from "../notifications";
|
|||||||
import { RealDebridDownloader } from "./real-debrid-downloader";
|
import { RealDebridDownloader } from "./real-debrid-downloader";
|
||||||
import type { DownloadProgress } from "@types";
|
import type { DownloadProgress } from "@types";
|
||||||
import { GofileApi } from "../hosters";
|
import { GofileApi } from "../hosters";
|
||||||
import { GenericHTTPDownloader } from "./generic-http-downloader";
|
import { GenericHttpDownloader } from "./generic-http-downloader";
|
||||||
|
|
||||||
export class DownloadManager {
|
export class DownloadManager {
|
||||||
private static currentDownloader: Downloader | null = null;
|
private static currentDownloader: Downloader | null = null;
|
||||||
@ -20,7 +20,7 @@ export class DownloadManager {
|
|||||||
} else if (this.currentDownloader === Downloader.RealDebrid) {
|
} else if (this.currentDownloader === Downloader.RealDebrid) {
|
||||||
status = await RealDebridDownloader.getStatus();
|
status = await RealDebridDownloader.getStatus();
|
||||||
} else {
|
} else {
|
||||||
status = await GenericHTTPDownloader.getStatus();
|
status = await GenericHttpDownloader.getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
@ -71,7 +71,7 @@ export class DownloadManager {
|
|||||||
} else if (this.currentDownloader === Downloader.RealDebrid) {
|
} else if (this.currentDownloader === Downloader.RealDebrid) {
|
||||||
await RealDebridDownloader.pauseDownload();
|
await RealDebridDownloader.pauseDownload();
|
||||||
} else {
|
} else {
|
||||||
await GenericHTTPDownloader.pauseDownload();
|
await GenericHttpDownloader.pauseDownload();
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowManager.mainWindow?.setProgressBar(-1);
|
WindowManager.mainWindow?.setProgressBar(-1);
|
||||||
@ -88,7 +88,7 @@ export class DownloadManager {
|
|||||||
} else if (this.currentDownloader === Downloader.RealDebrid) {
|
} else if (this.currentDownloader === Downloader.RealDebrid) {
|
||||||
RealDebridDownloader.cancelDownload(gameId);
|
RealDebridDownloader.cancelDownload(gameId);
|
||||||
} else {
|
} else {
|
||||||
GenericHTTPDownloader.cancelDownload(gameId);
|
GenericHttpDownloader.cancelDownload(gameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowManager.mainWindow?.setProgressBar(-1);
|
WindowManager.mainWindow?.setProgressBar(-1);
|
||||||
@ -102,13 +102,13 @@ export class DownloadManager {
|
|||||||
const token = await GofileApi.authorize();
|
const token = await GofileApi.authorize();
|
||||||
const downloadLink = await GofileApi.getDownloadLink(id!);
|
const downloadLink = await GofileApi.getDownloadLink(id!);
|
||||||
|
|
||||||
GenericHTTPDownloader.startDownload(game, downloadLink, {
|
GenericHttpDownloader.startDownload(game, downloadLink, {
|
||||||
Cookie: `accountToken=${token}`,
|
Cookie: `accountToken=${token}`,
|
||||||
});
|
});
|
||||||
} else if (game.downloader === Downloader.PixelDrain) {
|
} else if (game.downloader === Downloader.PixelDrain) {
|
||||||
const id = game!.uri!.split("/").pop();
|
const id = game!.uri!.split("/").pop();
|
||||||
|
|
||||||
await GenericHTTPDownloader.startDownload(
|
await GenericHttpDownloader.startDownload(
|
||||||
game,
|
game,
|
||||||
`https://pixeldrain.com/api/file/${id}?download`
|
`https://pixeldrain.com/api/file/${id}?download`
|
||||||
);
|
);
|
||||||
|
@ -4,14 +4,14 @@ import { calculateETA } from "./helpers";
|
|||||||
import { DownloadProgress } from "@types";
|
import { DownloadProgress } from "@types";
|
||||||
import { HttpDownload } from "./http-download";
|
import { HttpDownload } from "./http-download";
|
||||||
|
|
||||||
export class GenericHTTPDownloader {
|
export class GenericHttpDownloader {
|
||||||
private static downloads = new Map<number, string>();
|
public static downloads = new Map<number, HttpDownload>();
|
||||||
private static downloadingGame: Game | null = null;
|
public static downloadingGame: Game | null = null;
|
||||||
|
|
||||||
public static async getStatus() {
|
public static async getStatus() {
|
||||||
if (this.downloadingGame) {
|
if (this.downloadingGame) {
|
||||||
const gid = this.downloads.get(this.downloadingGame.id)!;
|
const download = this.downloads.get(this.downloadingGame.id)!;
|
||||||
const status = HttpDownload.getStatus(gid);
|
const status = download.getStatus();
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
const progress =
|
const progress =
|
||||||
@ -57,10 +57,10 @@ export class GenericHTTPDownloader {
|
|||||||
|
|
||||||
static async pauseDownload() {
|
static async pauseDownload() {
|
||||||
if (this.downloadingGame) {
|
if (this.downloadingGame) {
|
||||||
const gid = this.downloads.get(this.downloadingGame!.id!);
|
const httpDownload = this.downloads.get(this.downloadingGame!.id!);
|
||||||
|
|
||||||
if (gid) {
|
if (httpDownload) {
|
||||||
await HttpDownload.pauseDownload(gid);
|
await httpDownload.pauseDownload();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.downloadingGame = null;
|
this.downloadingGame = null;
|
||||||
@ -79,29 +79,31 @@ export class GenericHTTPDownloader {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gid = await HttpDownload.startDownload(
|
const httpDownload = new HttpDownload(
|
||||||
game.downloadPath!,
|
game.downloadPath!,
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
headers
|
headers
|
||||||
);
|
);
|
||||||
|
|
||||||
this.downloads.set(game.id!, gid);
|
httpDownload.startDownload();
|
||||||
|
|
||||||
|
this.downloads.set(game.id!, httpDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async cancelDownload(gameId: number) {
|
static async cancelDownload(gameId: number) {
|
||||||
const gid = this.downloads.get(gameId);
|
const httpDownload = this.downloads.get(gameId);
|
||||||
|
|
||||||
if (gid) {
|
if (httpDownload) {
|
||||||
await HttpDownload.cancelDownload(gid);
|
await httpDownload.cancelDownload();
|
||||||
this.downloads.delete(gameId);
|
this.downloads.delete(gameId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async resumeDownload(gameId: number) {
|
static async resumeDownload(gameId: number) {
|
||||||
const gid = this.downloads.get(gameId);
|
const httpDownload = this.downloads.get(gameId);
|
||||||
|
|
||||||
if (gid) {
|
if (httpDownload) {
|
||||||
await HttpDownload.resumeDownload(gid);
|
await httpDownload.resumeDownload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,67 +1,54 @@
|
|||||||
import { DownloadItem } from "electron";
|
|
||||||
import { WindowManager } from "../window-manager";
|
import { WindowManager } from "../window-manager";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
export class HttpDownload {
|
export class HttpDownload {
|
||||||
private static id = 0;
|
private downloadItem: Electron.DownloadItem;
|
||||||
|
|
||||||
private static downloads: Record<string, DownloadItem> = {};
|
constructor(
|
||||||
|
private downloadPath: string,
|
||||||
|
private downloadUrl: string,
|
||||||
|
private headers?: Record<string, string>
|
||||||
|
) {}
|
||||||
|
|
||||||
public static getStatus(gid: string): {
|
public getStatus() {
|
||||||
completedLength: number;
|
return {
|
||||||
totalLength: number;
|
completedLength: this.downloadItem.getReceivedBytes(),
|
||||||
downloadSpeed: number;
|
totalLength: this.downloadItem.getTotalBytes(),
|
||||||
folderName: string;
|
downloadSpeed: this.downloadItem.getCurrentBytesPerSecond(),
|
||||||
} | null {
|
folderName: this.downloadItem.getFilename(),
|
||||||
const downloadItem = this.downloads[gid];
|
};
|
||||||
if (downloadItem) {
|
|
||||||
return {
|
|
||||||
completedLength: downloadItem.getReceivedBytes(),
|
|
||||||
totalLength: downloadItem.getTotalBytes(),
|
|
||||||
downloadSpeed: downloadItem.getCurrentBytesPerSecond(),
|
|
||||||
folderName: downloadItem.getFilename(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async cancelDownload(gid: string) {
|
async cancelDownload() {
|
||||||
const downloadItem = this.downloads[gid];
|
this.downloadItem.cancel();
|
||||||
downloadItem?.cancel();
|
|
||||||
delete this.downloads[gid];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async pauseDownload(gid: string) {
|
async pauseDownload() {
|
||||||
const downloadItem = this.downloads[gid];
|
this.downloadItem.pause();
|
||||||
downloadItem?.pause();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async resumeDownload(gid: string) {
|
async resumeDownload() {
|
||||||
const downloadItem = this.downloads[gid];
|
this.downloadItem.resume();
|
||||||
downloadItem?.resume();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async startDownload(
|
async startDownload() {
|
||||||
downloadPath: string,
|
return new Promise((resolve) => {
|
||||||
downloadUrl: string,
|
const options = this.headers ? { headers: this.headers } : {};
|
||||||
headers?: Record<string, string>
|
WindowManager.mainWindow?.webContents.downloadURL(
|
||||||
) {
|
this.downloadUrl,
|
||||||
return new Promise<string>((resolve) => {
|
options
|
||||||
const options = headers ? { headers } : {};
|
);
|
||||||
WindowManager.mainWindow?.webContents.downloadURL(downloadUrl, options);
|
|
||||||
|
|
||||||
const gid = ++this.id;
|
WindowManager.mainWindow?.webContents.session.once(
|
||||||
|
|
||||||
WindowManager.mainWindow?.webContents.session.on(
|
|
||||||
"will-download",
|
"will-download",
|
||||||
(_event, item, _webContents) => {
|
(_event, item, _webContents) => {
|
||||||
this.downloads[gid.toString()] = item;
|
console.log(_event);
|
||||||
|
|
||||||
// Set the save path, making Electron not to prompt a save dialog.
|
this.downloadItem = item;
|
||||||
item.setSavePath(path.join(downloadPath, item.getFilename()));
|
|
||||||
|
|
||||||
resolve(gid.toString());
|
item.setSavePath(path.join(this.downloadPath, item.getFilename()));
|
||||||
|
|
||||||
|
resolve(null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
import { Game } from "@main/entity";
|
import { Game } from "@main/entity";
|
||||||
import { RealDebridClient } from "../real-debrid";
|
import { RealDebridClient } from "../real-debrid";
|
||||||
import { gameRepository } from "@main/repository";
|
|
||||||
import { calculateETA } from "./helpers";
|
|
||||||
import { DownloadProgress } from "@types";
|
|
||||||
import { HttpDownload } from "./http-download";
|
import { HttpDownload } from "./http-download";
|
||||||
|
import { GenericHttpDownloader } from "./generic-http-downloader";
|
||||||
|
|
||||||
export class RealDebridDownloader {
|
export class RealDebridDownloader extends GenericHttpDownloader {
|
||||||
private static downloads = new Map<number, string>();
|
|
||||||
private static downloadingGame: Game | null = null;
|
|
||||||
|
|
||||||
private static realDebridTorrentId: string | null = null;
|
private static realDebridTorrentId: string | null = null;
|
||||||
|
|
||||||
private static async getRealDebridDownloadUrl() {
|
private static async getRealDebridDownloadUrl() {
|
||||||
@ -48,66 +43,6 @@ export class RealDebridDownloader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getStatus() {
|
|
||||||
if (this.downloadingGame) {
|
|
||||||
const gid = this.downloads.get(this.downloadingGame.id)!;
|
|
||||||
const status = HttpDownload.getStatus(gid);
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
const progress =
|
|
||||||
Number(status.completedLength) / Number(status.totalLength);
|
|
||||||
|
|
||||||
await gameRepository.update(
|
|
||||||
{ id: this.downloadingGame!.id },
|
|
||||||
{
|
|
||||||
bytesDownloaded: Number(status.completedLength),
|
|
||||||
fileSize: Number(status.totalLength),
|
|
||||||
progress,
|
|
||||||
status: "active",
|
|
||||||
folderName: status.folderName,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
numPeers: 0,
|
|
||||||
numSeeds: 0,
|
|
||||||
downloadSpeed: Number(status.downloadSpeed),
|
|
||||||
timeRemaining: calculateETA(
|
|
||||||
Number(status.totalLength),
|
|
||||||
Number(status.completedLength),
|
|
||||||
Number(status.downloadSpeed)
|
|
||||||
),
|
|
||||||
isDownloadingMetadata: false,
|
|
||||||
isCheckingFiles: false,
|
|
||||||
progress,
|
|
||||||
gameId: this.downloadingGame!.id,
|
|
||||||
} as DownloadProgress;
|
|
||||||
|
|
||||||
if (progress === 1) {
|
|
||||||
this.downloads.delete(this.downloadingGame.id);
|
|
||||||
this.realDebridTorrentId = null;
|
|
||||||
this.downloadingGame = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async pauseDownload() {
|
|
||||||
if (this.downloadingGame) {
|
|
||||||
const gid = this.downloads.get(this.downloadingGame.id);
|
|
||||||
if (gid) {
|
|
||||||
await HttpDownload.pauseDownload(gid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.realDebridTorrentId = null;
|
|
||||||
this.downloadingGame = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async startDownload(game: Game) {
|
static async startDownload(game: Game) {
|
||||||
if (this.downloads.has(game.id)) {
|
if (this.downloads.has(game.id)) {
|
||||||
await this.resumeDownload(game.id!);
|
await this.resumeDownload(game.id!);
|
||||||
@ -128,32 +63,10 @@ export class RealDebridDownloader {
|
|||||||
if (downloadUrl) {
|
if (downloadUrl) {
|
||||||
this.realDebridTorrentId = null;
|
this.realDebridTorrentId = null;
|
||||||
|
|
||||||
const gid = await HttpDownload.startDownload(
|
const httpDownload = new HttpDownload(game.downloadPath!, downloadUrl);
|
||||||
game.downloadPath!,
|
httpDownload.startDownload();
|
||||||
downloadUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
this.downloads.set(game.id!, gid);
|
this.downloads.set(game.id!, httpDownload);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async cancelDownload(gameId: number) {
|
|
||||||
const gid = this.downloads.get(gameId);
|
|
||||||
|
|
||||||
if (gid) {
|
|
||||||
await HttpDownload.cancelDownload(gid);
|
|
||||||
this.downloads.delete(gameId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.realDebridTorrentId = null;
|
|
||||||
this.downloadingGame = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async resumeDownload(gameId: number) {
|
|
||||||
const gid = this.downloads.get(gameId);
|
|
||||||
|
|
||||||
if (gid) {
|
|
||||||
await HttpDownload.resumeDownload(gid);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ export interface GofileContentsResponse {
|
|||||||
children: Record<string, GofileContentChild>;
|
children: Record<string, GofileContentChild>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const WT = "4fd6sg89d7s6";
|
||||||
|
|
||||||
export class GofileApi {
|
export class GofileApi {
|
||||||
private static token: string;
|
private static token: string;
|
||||||
|
|
||||||
@ -35,7 +37,7 @@ export class GofileApi {
|
|||||||
|
|
||||||
public static async getDownloadLink(id: string) {
|
public static async getDownloadLink(id: string) {
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
wt: "4fd6sg89d7s6",
|
wt: WT,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await axios.get<{
|
const response = await axios.get<{
|
||||||
|
Loading…
Reference in New Issue
Block a user