feat: pass seeding list from downloader.py to download page

This commit is contained in:
Hachi-R 2024-11-07 20:35:17 -03:00
parent 452532e18b
commit a9085ec2ed
12 changed files with 98 additions and 30 deletions

View File

@ -198,7 +198,10 @@
"queued": "Queued", "queued": "Queued",
"no_downloads_title": "Such empty", "no_downloads_title": "Such empty",
"no_downloads_description": "You haven't downloaded anything with Hydra yet, but it's never too late to start.", "no_downloads_description": "You haven't downloaded anything with Hydra yet, but it's never too late to start.",
"checking_files": "Checking files…" "checking_files": "Checking files…",
"seeding": "Seeding",
"stop_seed": "Stop seed",
"resume_seed": "Resume seed"
}, },
"settings": { "settings": {
"downloads_path": "Downloads path", "downloads_path": "Downloads path",

View File

@ -194,7 +194,10 @@
"queued": "Na fila", "queued": "Na fila",
"no_downloads_title": "Nada por aqui…", "no_downloads_title": "Nada por aqui…",
"no_downloads_description": "Você ainda não baixou nada pelo Hydra, mas nunca é tarde para começar.", "no_downloads_description": "Você ainda não baixou nada pelo Hydra, mas nunca é tarde para começar.",
"checking_files": "Verificando arquivos…" "checking_files": "Verificando arquivos…",
"seeding": "Semeando",
"stop_seed": "Parar seed",
"resume_seed": "Retomar seed"
}, },
"settings": { "settings": {
"downloads_path": "Diretório dos downloads", "downloads_path": "Diretório dos downloads",

View File

@ -54,6 +54,9 @@ export class Game {
@Column("int", { default: Downloader.Torrent }) @Column("int", { default: Downloader.Torrent })
downloader: Downloader; downloader: Downloader;
@Column("boolean", { default: false })
shouldSeed: boolean;
/** /**
* Progress is a float between 0 and 1 * Progress is a float between 0 and 1
*/ */

View File

@ -14,6 +14,7 @@ import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix
import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column"; import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column";
import { AddSeedAfterDownloadCompletesColumn } from "./migrations/20241101012727_add_seed_after_download_completes_column"; import { AddSeedAfterDownloadCompletesColumn } from "./migrations/20241101012727_add_seed_after_download_completes_column";
import { AddSeedListTable } from "./migrations/20241103231555_add_seed_list_table"; import { AddSeedListTable } from "./migrations/20241103231555_add_seed_list_table";
import { AddShouldSeedColumn } from "./migrations/20241107211345_add_should_seed_colum";
export type HydraMigration = Knex.Migration & { name: string }; export type HydraMigration = Knex.Migration & { name: string };
class MigrationSource implements Knex.MigrationSource<HydraMigration> { class MigrationSource implements Knex.MigrationSource<HydraMigration> {
@ -32,6 +33,7 @@ class MigrationSource implements Knex.MigrationSource<HydraMigration> {
AddStartMinimizedColumn, AddStartMinimizedColumn,
AddSeedAfterDownloadCompletesColumn, AddSeedAfterDownloadCompletesColumn,
AddSeedListTable, AddSeedListTable,
AddShouldSeedColumn,
]); ]);
} }
getMigrationName(migration: HydraMigration): string { getMigrationName(migration: HydraMigration): string {

View File

@ -0,0 +1,17 @@
import type { HydraMigration } from "@main/knex-client";
import type { Knex } from "knex";
export const AddShouldSeedColumn: HydraMigration = {
name: "AddShouldSeedColumn",
up: (knex: Knex) => {
return knex.schema.alterTable("game", (table) => {
return table.boolean("shouldSeed").notNullable().defaultTo(false);
});
},
down: async (knex: Knex) => {
return knex.schema.alterTable("game", (table) => {
return table.dropColumn("shouldSeed");
});
},
};

View File

@ -5,7 +5,6 @@ import { WindowManager } from "../window-manager";
import { import {
downloadQueueRepository, downloadQueueRepository,
gameRepository, gameRepository,
seedListRepository,
userPreferencesRepository, userPreferencesRepository,
} from "@main/repository"; } from "@main/repository";
import { publishDownloadCompleteNotification } from "../notifications"; import { publishDownloadCompleteNotification } from "../notifications";
@ -63,20 +62,8 @@ export class DownloadManager {
userPreferences?.seedAfterDownloadCompletes && userPreferences?.seedAfterDownloadCompletes &&
this.currentDownloader === Downloader.Torrent this.currentDownloader === Downloader.Torrent
) { ) {
const existingSeed = await seedListRepository.findOne({ if (!game.shouldSeed) {
where: { downloadUri: game.uri! }, await gameRepository.update(game.id, { shouldSeed: true });
});
if (existingSeed) {
await seedListRepository.update(
{ downloadUri: game.uri! },
{ shouldSeed: true }
);
} else {
await seedListRepository.save({
downloadUri: game.uri!,
shouldSeed: true,
});
} }
} }
@ -97,9 +84,13 @@ export class DownloadManager {
} }
public static async watchSeedingList() { public static async watchSeedingList() {
const seedingList = await PythonInstance.getSeedingList(); const shouldSeedGames = await gameRepository.findOne({
where: { shouldSeed: true },
});
if (shouldSeedGames) {
const seedingList = await PythonInstance.getSeedingList();
if (seedingList) {
WindowManager.mainWindow?.webContents.send( WindowManager.mainWindow?.webContents.send(
"on-seeding-list", "on-seeding-list",
JSON.parse(JSON.stringify(seedingList)) JSON.parse(JSON.stringify(seedingList))

View File

@ -66,6 +66,16 @@ export class PythonInstance {
"/seed-list" "/seed-list"
); );
if (response.data && response.data.length > 0) {
for (const seed of response.data) {
await gameRepository.update(
{ id: seed.gameId },
{ status: "seeding" }
);
}
}
return response.data; return response.data;
} }

View File

@ -37,7 +37,6 @@ export interface LibtorrentSeedingPayload {
numPeers: number; numPeers: number;
numSeeds: number; numSeeds: number;
uploadSpeed: number; uploadSpeed: number;
// isCheckingFiles: boolean;
fileSize: number; fileSize: number;
folderName: string; folderName: string;
status: LibtorrentStatus; status: LibtorrentStatus;

View File

@ -1,6 +1,7 @@
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useMemo } from "react";
import type { LibraryGame } from "@types"; import type { LibraryGame, SeedingList } from "@types";
import { Badge, Button } from "@renderer/components"; import { Badge, Button } from "@renderer/components";
import { import {
@ -21,6 +22,7 @@ export interface DownloadGroupProps {
title: string; title: string;
openDeleteGameModal: (gameId: number) => void; openDeleteGameModal: (gameId: number) => void;
openGameInstaller: (gameId: number) => void; openGameInstaller: (gameId: number) => void;
seedingList: SeedingList[];
} }
export function DownloadGroup({ export function DownloadGroup({
@ -28,6 +30,7 @@ export function DownloadGroup({
title, title,
openDeleteGameModal, openDeleteGameModal,
openGameInstaller, openGameInstaller,
seedingList = [],
}: DownloadGroupProps) { }: DownloadGroupProps) {
const navigate = useNavigate(); const navigate = useNavigate();
@ -46,6 +49,17 @@ export function DownloadGroup({
isGameDeleting, isGameDeleting,
} = useDownload(); } = useDownload();
const seedingMap = useMemo(() => {
if (!Array.isArray(seedingList) || seedingList.length === 0) {
return new Map<number, SeedingList>();
}
const map = new Map<number, SeedingList>();
seedingList.forEach((seed) => {
map.set(seed.gameId, seed);
});
return map;
}, [seedingList]);
const getFinalDownloadSize = (game: LibraryGame) => { const getFinalDownloadSize = (game: LibraryGame) => {
const isGameDownloading = lastPacket?.game.id === game.id; const isGameDownloading = lastPacket?.game.id === game.id;
@ -60,6 +74,7 @@ export function DownloadGroup({
const getGameInfo = (game: LibraryGame) => { const getGameInfo = (game: LibraryGame) => {
const isGameDownloading = lastPacket?.game.id === game.id; const isGameDownloading = lastPacket?.game.id === game.id;
const finalDownloadSize = getFinalDownloadSize(game); const finalDownloadSize = getFinalDownloadSize(game);
const seed = seedingMap.get(game.id);
if (isGameDeleting(game.id)) { if (isGameDeleting(game.id)) {
return <p>{t("deleting")}</p>; return <p>{t("deleting")}</p>;
@ -98,7 +113,18 @@ export function DownloadGroup({
} }
if (game.progress === 1) { if (game.progress === 1) {
return <p>{t("completed")}</p>; return (
<>
{seed ? (
<>
<p>{t("seeding")}</p>
<p>{formatBytes(seed.uploadSpeed ?? 0)}/s</p>
</>
) : (
<p>{t("completed")}</p>
)}
</>
);
} }
if (game.status === "paused") { if (game.status === "paused") {
@ -127,8 +153,8 @@ export function DownloadGroup({
const getGameActions = (game: LibraryGame) => { const getGameActions = (game: LibraryGame) => {
const isGameDownloading = lastPacket?.game.id === game.id; const isGameDownloading = lastPacket?.game.id === game.id;
const deleting = isGameDeleting(game.id); const deleting = isGameDeleting(game.id);
const seed = seedingMap.get(game.id);
if (game.progress === 1) { if (game.progress === 1) {
return ( return (
@ -144,6 +170,18 @@ export function DownloadGroup({
<Button onClick={() => openDeleteGameModal(game.id)} theme="outline"> <Button onClick={() => openDeleteGameModal(game.id)} theme="outline">
{t("delete")} {t("delete")}
</Button> </Button>
{seed && game.shouldSeed && (
<Button theme="outline">
{t("stop_seed")}
</Button>
)}
{seed && !game.shouldSeed && (
<Button theme="outline">
{t("resume_seed")}
</Button>
)}
</> </>
); );
} }

View File

@ -36,8 +36,6 @@ export default function Downloads() {
window.electron.onSeedingList((value) => setSeedingList(value)); window.electron.onSeedingList((value) => setSeedingList(value));
}, []); }, []);
console.log("sexo", seedingList);
const handleOpenGameInstaller = (gameId: number) => const handleOpenGameInstaller = (gameId: number) =>
window.electron.openGameInstaller(gameId).then((isBinaryInPath) => { window.electron.openGameInstaller(gameId).then((isBinaryInPath) => {
if (!isBinaryInPath) setShowBinaryNotFoundModal(true); if (!isBinaryInPath) setShowBinaryNotFoundModal(true);
@ -130,6 +128,7 @@ export default function Downloads() {
library={group.library} library={group.library}
openDeleteGameModal={handleOpenDeleteGameModal} openDeleteGameModal={handleOpenDeleteGameModal}
openGameInstaller={handleOpenGameInstaller} openGameInstaller={handleOpenGameInstaller}
seedingList={seedingList}
/> />
))} ))}
</div> </div>

View File

@ -7,7 +7,8 @@ export type GameStatus =
| "paused" | "paused"
| "error" | "error"
| "complete" | "complete"
| "removed"; | "removed"
| "seeding";
export type GameShop = "steam" | "epic"; export type GameShop = "steam" | "epic";
@ -124,6 +125,7 @@ export interface Game {
objectID: string; objectID: string;
shop: GameShop; shop: GameShop;
downloadQueue: DownloadQueue | null; downloadQueue: DownloadQueue | null;
shouldSeed: boolean;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
@ -156,6 +158,9 @@ export interface SeedingList {
numPeers: number; numPeers: number;
numSeeds: number; numSeeds: number;
uploadSpeed: number; uploadSpeed: number;
gameId: number;
folderName: string;
fileSize: number;
} }
export interface UserPreferences { export interface UserPreferences {

View File

@ -176,7 +176,7 @@ class TorrentDownloader:
torrent_info = { torrent_info = {
'folderName': info.name() if info else "", 'folderName': info.name() if info else "",
'fileSize': info.total_size() if info else 0, 'fileSize': info.total_size() if info else 0,
'gameId': self.downloading_game_id, 'gameId': game_id,
'progress': status.progress, 'progress': status.progress,
'downloadSpeed': status.download_rate, 'downloadSpeed': status.download_rate,
'uploadSpeed': status.upload_rate, 'uploadSpeed': status.upload_rate,
@ -189,6 +189,4 @@ class TorrentDownloader:
if status.state == 5: if status.state == 5:
response.append(torrent_info) response.append(torrent_info)
# print(response) return response
return response
# return None