Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/seed completed downloads #1223

Merged
merged 66 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
21ca26d
feat: update schema with seeding columns
Hachi-R Nov 8, 2024
9c9c0e6
feat: seed after doenload
Hachi-R Nov 8, 2024
c556a00
feat: get seed status
Hachi-R Nov 8, 2024
b32952f
feat: add ablity to pause and resume the seeding process
Hachi-R Nov 9, 2024
94b65c0
lint
Hachi-R Nov 9, 2024
7c039ea
feat: add seeding management logic
Hachi-R Nov 9, 2024
5078946
refactor: change logic to seed new downloads
Hachi-R Nov 9, 2024
c314c39
feat: add option to disable seeding after download completes
Hachi-R Nov 9, 2024
8ec52bf
temp
Hachi-R Nov 9, 2024
5668794
feat: display upload speed during seeding
Hachi-R Nov 9, 2024
9619578
lint
Hachi-R Nov 9, 2024
518d919
feat: add locale strings
Hachi-R Nov 9, 2024
f66bdd7
feat: pause seeding before deleting game
Hachi-R Nov 9, 2024
610b6e5
Merge branch 'main' into feature/seed-completed-downloads
thegrannychaseroperation Nov 9, 2024
1416cd4
feat: seed downloads from previous versions
Hachi-R Nov 9, 2024
a7b8018
"feat: pause seeding if game folder is deleted"
Hachi-R Nov 11, 2024
40ec773
lint
Hachi-R Nov 11, 2024
2c1c3e3
feat: add dropdown menu component
Hachi-R Nov 12, 2024
ca953de
lint
Hachi-R Nov 12, 2024
94ef167
feat: add menu with download options on download page
Hachi-R Nov 12, 2024
9d1c04d
refactor: export with component definition
Hachi-R Nov 12, 2024
3303413
lint
Hachi-R Nov 12, 2024
130c236
Merge branch 'main' into feature/seed-completed-downloads
Hachi-R Nov 12, 2024
f2cc20c
refactor: removed dropdown title because its ugly
Hachi-R Nov 16, 2024
f35c34f
fix: fixing bottom panel scss
thegrannychaseroperation Nov 28, 2024
6259cf4
Merge branch 'main' of github.com:hydralauncher/hydra into feature/se…
thegrannychaseroperation Nov 28, 2024
2d8b63c
Merge branch 'feature/seed-completed-downloads' of github.com:hydrala…
thegrannychaseroperation Nov 28, 2024
4060f7a
feat: adding aria2c
thegrannychaseroperation Nov 28, 2024
fe8f962
feat: adding aria2c
thegrannychaseroperation Nov 28, 2024
a847f93
feat: adding aria2c
thegrannychaseroperation Nov 28, 2024
c6d4b65
feat: adding aria2c
thegrannychaseroperation Nov 28, 2024
d7e06d6
feat: adding aria2c
thegrannychaseroperation Nov 28, 2024
5fbf0ba
feat: enhance seed status retrieval
Hachi-R Dec 21, 2024
09ea3b2
feat: improve seed status response structure by including game ID
Hachi-R Dec 21, 2024
5714c23
feat: implement seed process initiation in PythonRPC
Hachi-R Dec 21, 2024
4b8569b
refactor: streamline download completion handling
Hachi-R Dec 21, 2024
90b3209
lint
Hachi-R Dec 21, 2024
9884a39
refactor: replace resumeSeeding with resumeDownload in torrenting eve…
Hachi-R Dec 21, 2024
1a286df
Merge branch 'main' into feature/seed-completed-downloads
Hachi-R Dec 22, 2024
fd5b2e0
lint
Hachi-R Dec 22, 2024
93ef0c2
feat: updated pauseGameSeed and resumeGameSeed events
Hachi-R Dec 22, 2024
bd184fc
lint
Hachi-R Dec 22, 2024
abb3db4
feat: implement directory size check and seed abort
Hachi-R Dec 22, 2024
93712a7
refactor: remove commented-out code for torrent actions in main.py an…
Hachi-R Dec 22, 2024
855f0aa
refactor: remove commented-out code in download-manager.ts
Hachi-R Dec 22, 2024
859d849
fix: update Portuguese translations and enhance dropdown menu styling
Hachi-R Dec 22, 2024
db01980
refactor: clean up code
Hachi-R Dec 22, 2024
79bb12e
lint
Hachi-R Dec 22, 2024
65b1ec9
refactor: replace dataSource with gameRepository in pauseGameSeed and…
Hachi-R Dec 22, 2024
3aa8230
feat: restart downloads and move seed process initiation to main.ts
Hachi-R Dec 23, 2024
35d14b3
lint
Hachi-R Dec 23, 2024
e463ee5
feat: add initial download handling in Python RPC and update spawn me…
Hachi-R Dec 23, 2024
af2b422
feat: integrate DownloadManager for active game downloads and streaml…
Hachi-R Dec 23, 2024
c755aa3
lint
Hachi-R Dec 23, 2024
f9719c9
feat: delay in seed process
Hachi-R Dec 23, 2024
59846cf
fix: processProfileImage path and body
zamitto Dec 23, 2024
c9be6b6
feat: replace console with logger
zamitto Dec 23, 2024
56217fb
fix: bug after pause seed
zamitto Dec 23, 2024
a3caa62
feat: remove console log
zamitto Dec 23, 2024
b740359
feat: replace button with div on DropdownMenu
zamitto Dec 23, 2024
ea90b49
chore: sonar adjustments
zamitto Dec 23, 2024
843301c
feat: implement pause and resume functionality for game seeding in Do…
Hachi-R Dec 23, 2024
f853a2a
feat: move initial seeding for DownloadManager
Hachi-R Dec 23, 2024
230c24c
lint
Hachi-R Dec 23, 2024
17879f9
refactor: remove unused seed process functionality from main application
Hachi-R Dec 23, 2024
b8f5f90
feat: enhance download handling for magnet and HTTP links in action f…
Hachi-R Dec 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
MAIN_VITE_API_URL=API_URL
MAIN_VITE_AUTH_URL=AUTH_URL
MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist
out
.gitignore
migration.stub
hydra-python-rpc/
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
run: pip install -r requirements.txt

- name: Build with cx_Freeze
run: python torrent-client/setup.py build
run: python python_rpc/setup.py build

- name: Build Linux
if: matrix.os == 'ubuntu-latest'
Expand Down
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
.vscode/
node_modules/
hydra-download-manager/
fastlist.exe
__pycache__
dist
out
.DS_Store
*.log*
.env
.vite
ludusavi/
ludusavi/
hydra-python-rpc/
aria2/
3 changes: 2 additions & 1 deletion electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ productName: Hydra
directories:
buildResources: build
extraResources:
- aria2
- ludusavi
- hydra-download-manager
- hydra-python-rpc
- seeds
- from: node_modules/create-desktop-shortcuts/src/windows.vbs
- from: resources/achievement.wav
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@fontsource/noto-sans": "^5.1.0",
"@hookform/resolvers": "^3.9.1",
"@primer/octicons-react": "^19.9.0",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@reduxjs/toolkit": "^2.2.3",
"@vanilla-extract/css": "^1.14.2",
"@vanilla-extract/dynamic": "^2.1.2",
Expand Down
47 changes: 47 additions & 0 deletions python_rpc/http_downloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import aria2p

class HttpDownloader:
def __init__(self):
self.download = None
self.aria2 = aria2p.API(
aria2p.Client(
host="http://localhost",
port=6800,
secret=""
)
)

def start_download(self, url: str, save_path: str, header: str):
if self.download:
self.aria2.resume([self.download])
else:
downloads = self.aria2.add(url, options={"header": header, "dir": save_path})
self.download = downloads[0]

def pause_download(self):
if self.download:
self.aria2.pause([self.download])

def cancel_download(self):
if self.download:
self.aria2.remove([self.download])
self.download = None

def get_download_status(self):
if self.download == None:
return None

download = self.aria2.get_download(self.download.gid)

response = {
'folderName': str(download.dir) + "/" + download.name,
'fileSize': download.total_length,
'progress': download.completed_length / download.total_length if download.total_length else 0,
'downloadSpeed': download.download_speed,
'numPeers': 0,
'numSeeds': 0,
'status': download.status,
'bytesDownloaded': download.completed_length,
}

return response
176 changes: 176 additions & 0 deletions python_rpc/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
from flask import Flask, request, jsonify
import sys, json, urllib.parse, psutil
from torrent_downloader import TorrentDownloader
from http_downloader import HttpDownloader
from profile_image_processor import ProfileImageProcessor
import libtorrent as lt

app = Flask(__name__)

# Retrieve command line arguments
torrent_port = sys.argv[1]
http_port = sys.argv[2]
rpc_password = sys.argv[3]
start_download_payload = sys.argv[4]
start_seeding_payload = sys.argv[5]

downloads = {}
# This can be streamed down from Node
downloading_game_id = -1

torrent_session = lt.session({'listen_interfaces': '0.0.0.0:{port}'.format(port=torrent_port)})

if start_download_payload:
initial_download = json.loads(urllib.parse.unquote(start_download_payload))
downloading_game_id = initial_download['game_id']

if initial_download['url'].startswith('magnet'):
torrent_downloader = TorrentDownloader(torrent_session)
downloads[initial_download['game_id']] = torrent_downloader
torrent_downloader.start_download(initial_download['url'], initial_download['save_path'], "")
else:
http_downloader = HttpDownloader()
downloads[initial_download['game_id']] = http_downloader
http_downloader.start_download(initial_download['url'], initial_download['save_path'], initial_download.get('header'))

if start_seeding_payload:
initial_seeding = json.loads(urllib.parse.unquote(start_seeding_payload))
for seed in initial_seeding:
torrent_downloader = TorrentDownloader(torrent_session)
downloads[seed['game_id']] = torrent_downloader
torrent_downloader.start_download(seed['url'], seed['save_path'], "")

def validate_rpc_password():
"""Middleware to validate RPC password."""
header_password = request.headers.get('x-hydra-rpc-password')
if header_password != rpc_password:
return jsonify({"error": "Unauthorized"}), 401

@app.route("/status", methods=["GET"])
def status():
auth_error = validate_rpc_password()
if auth_error:
return auth_error

downloader = downloads.get(downloading_game_id)
if downloader:
status = downloads.get(downloading_game_id).get_download_status()
return jsonify(status), 200
else:
return jsonify(None)

@app.route("/seed-status", methods=["GET"])
def seed_status():
auth_error = validate_rpc_password()
if auth_error:
return auth_error

seed_status = []

for game_id, downloader in downloads.items():
if not downloader:
continue

response = downloader.get_download_status()
if response is None:
continue

if response.get('status') == 5:
seed_status.append({
'gameId': game_id,
**response,
})

return jsonify(seed_status), 200

@app.route("/healthcheck", methods=["GET"])
def healthcheck():
return "", 200

@app.route("/process-list", methods=["GET"])
def process_list():
auth_error = validate_rpc_password()
if auth_error:
return auth_error

process_list = [proc.info for proc in psutil.process_iter(['exe', 'pid', 'name'])]
return jsonify(process_list), 200

@app.route("/profile-image", methods=["POST"])
def profile_image():
auth_error = validate_rpc_password()
if auth_error:
return auth_error

data = request.get_json()
image_path = data.get('image_path')

try:
processed_image_path, mime_type = ProfileImageProcessor.process_image(image_path)
return jsonify({'imagePath': processed_image_path, 'mimeType': mime_type}), 200
except Exception as e:
return jsonify({"error": str(e)}), 400

@app.route("/action", methods=["POST"])
def action():
global torrent_session
global downloading_game_id

auth_error = validate_rpc_password()
if auth_error:
return auth_error

data = request.get_json()
action = data.get('action')
game_id = data.get('game_id')

print(data)

if action == 'start':
url = data.get('url')

existing_downloader = downloads.get(game_id)

if url.startswith('magnet'):
if existing_downloader and isinstance(existing_downloader, TorrentDownloader):
existing_downloader.start_download(url, data['save_path'], "")
else:
torrent_downloader = TorrentDownloader(torrent_session)
downloads[game_id] = torrent_downloader
torrent_downloader.start_download(url, data['save_path'], "")
else:
if existing_downloader and isinstance(existing_downloader, HttpDownloader):
existing_downloader.start_download(url, data['save_path'], data.get('header'))
else:
http_downloader = HttpDownloader()
downloads[game_id] = http_downloader
http_downloader.start_download(url, data['save_path'], data.get('header'))

downloading_game_id = game_id

elif action == 'pause':
downloader = downloads.get(game_id)
if downloader:
downloader.pause_download()
downloading_game_id = -1
elif action == 'cancel':
downloader = downloads.get(game_id)
if downloader:
downloader.cancel_download()
elif action == 'resume_seeding':
torrent_downloader = TorrentDownloader(torrent_session)
downloads[game_id] = torrent_downloader
torrent_downloader.start_download(data['url'], data['save_path'], "")
elif action == 'pause_seeding':
downloader = downloads.get(game_id)
if downloader:
downloader.cancel_download()

else:
return jsonify({"error": "Invalid action"}), 400

return "", 200

if __name__ == "__main__":
app.run(host="0.0.0.0", port=int(http_port))

Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def get_parsed_image_data(image_path):
mime_type = image.get_format_mimetype()
return image_path, mime_type
else:
newUUID = str(uuid.uuid4())
new_image_path = os.path.join(tempfile.gettempdir(), newUUID) + ".webp"
new_uuid = str(uuid.uuid4())
new_image_path = os.path.join(tempfile.gettempdir(), new_uuid) + ".webp"
image.save(new_image_path)

new_image = Image.open(new_image_path)
Expand Down
8 changes: 4 additions & 4 deletions torrent-client/setup.py → python_rpc/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
# Dependencies are automatically detected, but it might need fine tuning.
build_exe_options = {
"packages": ["libtorrent"],
"build_exe": "hydra-download-manager",
"build_exe": "hydra-python-rpc",
"include_msvcr": True
}

setup(
name="hydra-download-manager",
name="hydra-python-rpc",
version="0.1",
description="Hydra",
options={"build_exe": build_exe_options},
executables=[Executable(
"torrent-client/main.py",
target_name="hydra-download-manager",
"python_rpc/main.py",
target_name="hydra-python-rpc",
icon="build/icon.ico"
)]
)
Loading
Loading