diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 932b80e4..4a5175e3 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -32,6 +32,8 @@ import "./torrenting/cancel-game-download"; import "./torrenting/pause-game-download"; import "./torrenting/resume-game-download"; import "./torrenting/start-game-download"; +import "./torrenting/pause-game-seed"; +import "./torrenting/resume-game-seed"; import "./user-preferences/get-user-preferences"; import "./user-preferences/update-user-preferences"; import "./user-preferences/auto-launch"; diff --git a/src/main/events/torrenting/pause-game-seed.ts b/src/main/events/torrenting/pause-game-seed.ts new file mode 100644 index 00000000..3b00ddd5 --- /dev/null +++ b/src/main/events/torrenting/pause-game-seed.ts @@ -0,0 +1,21 @@ +import { registerEvent } from "../register-event"; + +import { DownloadManager } from "@main/services"; +import { dataSource } from "@main/data-source"; +import { Game } from "@main/entity"; + +const pauseGameSeed = async ( + _event: Electron.IpcMainInvokeEvent, + gameId: number +) => { + await dataSource.transaction(async (transactionalEntityManager) => { + + await transactionalEntityManager + .getRepository(Game) + .update({ id: gameId }, { status: "complete", shouldSeed: false }); + }); + + await DownloadManager.pauseSeeding(gameId); +}; + +registerEvent("pauseGameSeed", pauseGameSeed); diff --git a/src/main/events/torrenting/resume-game-seed.ts b/src/main/events/torrenting/resume-game-seed.ts new file mode 100644 index 00000000..133b26a0 --- /dev/null +++ b/src/main/events/torrenting/resume-game-seed.ts @@ -0,0 +1,31 @@ +import { registerEvent } from "../register-event"; +import { gameRepository } from "../../repository"; + +import { DownloadManager } from "@main/services"; +import { dataSource } from "@main/data-source"; +import { Game } from "@main/entity"; + +const resumeGameSeed = async ( + _event: Electron.IpcMainInvokeEvent, + gameId: number +) => { + const game = await gameRepository.findOne({ + where: { + id: gameId, + isDeleted: false, + }, + }); + + if (!game) return; + + await dataSource.transaction(async (transactionalEntityManager) => { + + await transactionalEntityManager + .getRepository(Game) + .update({ id: gameId }, { status: "seeding", shouldSeed: true }); + }); + + await DownloadManager.resumeSeeding(gameId, game.uri!, game.downloadPath!); +}; + +registerEvent("resumeGameSeed", resumeGameSeed); diff --git a/src/main/services/download/python-instance.ts b/src/main/services/download/python-instance.ts index 09ea10ed..95997c4c 100644 --- a/src/main/services/download/python-instance.ts +++ b/src/main/services/download/python-instance.ts @@ -18,6 +18,8 @@ import { LibtorrentStatus, LibtorrentPayload, ProcessPayload, + PauseSeedingPayload, + ResumeSeedingPayload, } from "./types"; import { pythonInstanceLogger as logger } from "../logger"; @@ -133,6 +135,26 @@ export class PythonInstance { return response.data; } + static async pauseSeeding(gameId: number) { + await this.rpc + .post("/action", { + action: "pause-seeding", + game_id: gameId, + } as PauseSeedingPayload) + .catch(() => {}); + } + + static async resumeSeeding(gameId: number, magnet: string, savePath: string) { + await this.rpc + .post("/action", { + action: "resume-seeding", + game_id: gameId, + magnet, + save_path: savePath, + } as ResumeSeedingPayload) + .catch(() => {}); + } + static async pauseDownload() { await this.rpc .post("/action", { diff --git a/src/main/services/download/types.ts b/src/main/services/download/types.ts index c09455c7..2d992735 100644 --- a/src/main/services/download/types.ts +++ b/src/main/services/download/types.ts @@ -37,3 +37,11 @@ export interface ProcessPayload { exe: string; pid: number; } + +export interface PauseSeedingPayload { + game_id: number; +} + +export interface ResumeSeedingPayload { + game_id: number; +} diff --git a/src/preload/index.ts b/src/preload/index.ts index d8d142ca..f1a26150 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -26,6 +26,10 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("pauseGameDownload", gameId), resumeGameDownload: (gameId: number) => ipcRenderer.invoke("resumeGameDownload", gameId), + pauseGameSeed: (gameId: number) => + ipcRenderer.invoke("pauseGameSeed", gameId), + resumeGameSeed: (gameId: number) => + ipcRenderer.invoke("resumeGameSeed", gameId), onDownloadProgress: (cb: (value: DownloadProgress) => void) => { const listener = ( _event: Electron.IpcRendererEvent, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 4065b64c..93e5b295 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -46,6 +46,8 @@ declare global { cancelGameDownload: (gameId: number) => Promise; pauseGameDownload: (gameId: number) => Promise; resumeGameDownload: (gameId: number) => Promise; + pauseGameSeed: (gameId: number) => Promise; + resumeGameSeed: (gameId: number) => Promise; onDownloadProgress: ( cb: (value: DownloadProgress) => void ) => () => Electron.IpcRenderer; diff --git a/src/renderer/src/hooks/use-download.ts b/src/renderer/src/hooks/use-download.ts index 31c3bf2f..4ea79b93 100644 --- a/src/renderer/src/hooks/use-download.ts +++ b/src/renderer/src/hooks/use-download.ts @@ -66,6 +66,16 @@ export function useDownload() { updateLibrary(); }); + const pauseSeeding = async (gameId: number) => { + await window.electron.pauseGameSeed(gameId); + await updateLibrary(); + }; + + const resumeSeeding = async (gameId: number) => { + await window.electron.resumeGameSeed(gameId); + await updateLibrary(); + }; + const calculateETA = () => { if (!lastPacket || lastPacket.timeRemaining < 0) return ""; @@ -96,6 +106,8 @@ export function useDownload() { removeGameFromLibrary, removeGameInstaller, isGameDeleting, + pauseSeeding, + resumeSeeding, clearDownload: () => dispatch(clearDownload()), setLastPacket: (packet: DownloadProgress) => dispatch(setLastPacket(packet)), diff --git a/src/types/index.ts b/src/types/index.ts index 9116a998..2e4894b3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,6 +7,7 @@ export type GameStatus = | "paused" | "error" | "complete" + | "seeding" | "removed"; export type GameShop = "steam" | "epic"; diff --git a/torrent-client/main.py b/torrent-client/main.py index c8ffd9e2..a37ffabc 100644 --- a/torrent-client/main.py +++ b/torrent-client/main.py @@ -121,6 +121,10 @@ class Handler(BaseHTTPRequestHandler): elif data['action'] == 'kill-torrent': torrent_downloader.abort_session() torrent_downloader = None + elif data['action'] == 'pause-seeding': + torrent_downloader.pause_seeding(data['game_id']) + elif data['action'] == 'resume-seeding': + torrent_downloader.resume_seeding(data['game_id'], data['magnet'], data['save_path']) self.send_response(200) self.end_headers() diff --git a/torrent-client/torrent_downloader.py b/torrent-client/torrent_downloader.py index a098f7db..41c45278 100644 --- a/torrent-client/torrent_downloader.py +++ b/torrent-client/torrent_downloader.py @@ -189,3 +189,16 @@ class TorrentDownloader: response.append(torrent_info) return response + + def pause_seeding(self, game_id: int): + torrent_handle = self.torrent_handles.get(game_id) + if torrent_handle: + torrent_handle.pause() + torrent_handle.unset_flags(lt.torrent_flags.auto_managed) + + def resume_seeding(self, game_id: int, magnet: str, save_path: str): + params = {'url': magnet, 'save_path': save_path, 'trackers': self.trackers} + torrent_handle = self.session.add_torrent(params) + self.torrent_handles[game_id] = torrent_handle + torrent_handle.set_flags(lt.torrent_flags.auto_managed) + torrent_handle.resume()