-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1223 from hydralauncher/feature/seed-completed-do…
…wnloads Feature/seed completed downloads
Showing
62 changed files
with
1,763 additions
and
829 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ dist | |
out | ||
.gitignore | ||
migration.stub | ||
hydra-python-rpc/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.