diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 7ac7c1d2..61880019 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -198,7 +198,10 @@ "queued": "Queued", "no_downloads_title": "Such empty", "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": { "downloads_path": "Downloads path", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 41e60338..d731c9fe 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -194,7 +194,10 @@ "queued": "Na fila", "no_downloads_title": "Nada por aqui…", "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": { "downloads_path": "Diretório dos downloads", diff --git a/src/main/entity/game.entity.ts b/src/main/entity/game.entity.ts index 19905c32..c2f2e916 100644 --- a/src/main/entity/game.entity.ts +++ b/src/main/entity/game.entity.ts @@ -54,6 +54,9 @@ export class Game { @Column("int", { default: Downloader.Torrent }) downloader: Downloader; + @Column("boolean", { default: false }) + shouldSeed: boolean; + /** * Progress is a float between 0 and 1 */ diff --git a/src/main/knex-client.ts b/src/main/knex-client.ts index 3abbbaed..b41c11a6 100644 --- a/src/main/knex-client.ts +++ b/src/main/knex-client.ts @@ -14,6 +14,7 @@ import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column"; import { AddSeedAfterDownloadCompletesColumn } from "./migrations/20241101012727_add_seed_after_download_completes_column"; 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 }; class MigrationSource implements Knex.MigrationSource { @@ -32,6 +33,7 @@ class MigrationSource implements Knex.MigrationSource { AddStartMinimizedColumn, AddSeedAfterDownloadCompletesColumn, AddSeedListTable, + AddShouldSeedColumn, ]); } getMigrationName(migration: HydraMigration): string { diff --git a/src/main/migrations/20241107211345_add_should_seed_colum.ts b/src/main/migrations/20241107211345_add_should_seed_colum.ts new file mode 100644 index 00000000..13d9c1ed --- /dev/null +++ b/src/main/migrations/20241107211345_add_should_seed_colum.ts @@ -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"); + }); + }, +}; \ No newline at end of file diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 67b59389..99a0f536 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -5,7 +5,6 @@ import { WindowManager } from "../window-manager"; import { downloadQueueRepository, gameRepository, - seedListRepository, userPreferencesRepository, } from "@main/repository"; import { publishDownloadCompleteNotification } from "../notifications"; @@ -63,20 +62,8 @@ export class DownloadManager { userPreferences?.seedAfterDownloadCompletes && this.currentDownloader === Downloader.Torrent ) { - const existingSeed = await seedListRepository.findOne({ - where: { downloadUri: game.uri! }, - }); - - if (existingSeed) { - await seedListRepository.update( - { downloadUri: game.uri! }, - { shouldSeed: true } - ); - } else { - await seedListRepository.save({ - downloadUri: game.uri!, - shouldSeed: true, - }); + if (!game.shouldSeed) { + await gameRepository.update(game.id, { shouldSeed: true }); } } @@ -97,9 +84,13 @@ export class DownloadManager { } 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( "on-seeding-list", JSON.parse(JSON.stringify(seedingList)) diff --git a/src/main/services/download/python-instance.ts b/src/main/services/download/python-instance.ts index 7c35b9c2..5f60a234 100644 --- a/src/main/services/download/python-instance.ts +++ b/src/main/services/download/python-instance.ts @@ -66,6 +66,16 @@ export class PythonInstance { "/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; } diff --git a/src/main/services/download/types.ts b/src/main/services/download/types.ts index 73f204d5..1d2a3379 100644 --- a/src/main/services/download/types.ts +++ b/src/main/services/download/types.ts @@ -37,7 +37,6 @@ export interface LibtorrentSeedingPayload { numPeers: number; numSeeds: number; uploadSpeed: number; - // isCheckingFiles: boolean; fileSize: number; folderName: string; status: LibtorrentStatus; diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 7281079d..26d2227e 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -1,6 +1,7 @@ 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 { @@ -21,6 +22,7 @@ export interface DownloadGroupProps { title: string; openDeleteGameModal: (gameId: number) => void; openGameInstaller: (gameId: number) => void; + seedingList: SeedingList[]; } export function DownloadGroup({ @@ -28,6 +30,7 @@ export function DownloadGroup({ title, openDeleteGameModal, openGameInstaller, + seedingList = [], }: DownloadGroupProps) { const navigate = useNavigate(); @@ -46,6 +49,17 @@ export function DownloadGroup({ isGameDeleting, } = useDownload(); + const seedingMap = useMemo(() => { + if (!Array.isArray(seedingList) || seedingList.length === 0) { + return new Map(); + } + const map = new Map(); + seedingList.forEach((seed) => { + map.set(seed.gameId, seed); + }); + return map; + }, [seedingList]); + const getFinalDownloadSize = (game: LibraryGame) => { const isGameDownloading = lastPacket?.game.id === game.id; @@ -60,6 +74,7 @@ export function DownloadGroup({ const getGameInfo = (game: LibraryGame) => { const isGameDownloading = lastPacket?.game.id === game.id; const finalDownloadSize = getFinalDownloadSize(game); + const seed = seedingMap.get(game.id); if (isGameDeleting(game.id)) { return

{t("deleting")}

; @@ -98,7 +113,18 @@ export function DownloadGroup({ } if (game.progress === 1) { - return

{t("completed")}

; + return ( + <> + {seed ? ( + <> +

{t("seeding")}

+

{formatBytes(seed.uploadSpeed ?? 0)}/s

+ + ) : ( +

{t("completed")}

+ )} + + ); } if (game.status === "paused") { @@ -127,8 +153,8 @@ export function DownloadGroup({ const getGameActions = (game: LibraryGame) => { const isGameDownloading = lastPacket?.game.id === game.id; - const deleting = isGameDeleting(game.id); + const seed = seedingMap.get(game.id); if (game.progress === 1) { return ( @@ -144,6 +170,18 @@ export function DownloadGroup({ + + {seed && game.shouldSeed && ( + + )} + + {seed && !game.shouldSeed && ( + + )} ); } diff --git a/src/renderer/src/pages/downloads/downloads.tsx b/src/renderer/src/pages/downloads/downloads.tsx index 9cdb6f4c..1bd204ea 100644 --- a/src/renderer/src/pages/downloads/downloads.tsx +++ b/src/renderer/src/pages/downloads/downloads.tsx @@ -36,8 +36,6 @@ export default function Downloads() { window.electron.onSeedingList((value) => setSeedingList(value)); }, []); - console.log("sexo", seedingList); - const handleOpenGameInstaller = (gameId: number) => window.electron.openGameInstaller(gameId).then((isBinaryInPath) => { if (!isBinaryInPath) setShowBinaryNotFoundModal(true); @@ -130,6 +128,7 @@ export default function Downloads() { library={group.library} openDeleteGameModal={handleOpenDeleteGameModal} openGameInstaller={handleOpenGameInstaller} + seedingList={seedingList} /> ))} diff --git a/src/types/index.ts b/src/types/index.ts index 786516c9..9d617915 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,7 +7,8 @@ export type GameStatus = | "paused" | "error" | "complete" - | "removed"; + | "removed" + | "seeding"; export type GameShop = "steam" | "epic"; @@ -124,6 +125,7 @@ export interface Game { objectID: string; shop: GameShop; downloadQueue: DownloadQueue | null; + shouldSeed: boolean; createdAt: Date; updatedAt: Date; } @@ -156,6 +158,9 @@ export interface SeedingList { numPeers: number; numSeeds: number; uploadSpeed: number; + gameId: number; + folderName: string; + fileSize: number; } export interface UserPreferences { diff --git a/torrent-client/torrent_downloader.py b/torrent-client/torrent_downloader.py index c68eb6ec..eada2fd6 100644 --- a/torrent-client/torrent_downloader.py +++ b/torrent-client/torrent_downloader.py @@ -176,7 +176,7 @@ class TorrentDownloader: torrent_info = { 'folderName': info.name() if info else "", 'fileSize': info.total_size() if info else 0, - 'gameId': self.downloading_game_id, + 'gameId': game_id, 'progress': status.progress, 'downloadSpeed': status.download_rate, 'uploadSpeed': status.upload_rate, @@ -189,6 +189,4 @@ class TorrentDownloader: if status.state == 5: response.append(torrent_info) - # print(response) - return response - # return None \ No newline at end of file + return response \ No newline at end of file