mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-07 12:05:35 +00:00
core: Rewrite all classes of torrent download
This commit is contained in:
parent
64e382c32e
commit
48e433d2fb
@ -165,13 +165,10 @@ from StreamingCommunity.Download import TOR_downloader
|
|||||||
client = TOR_downloader()
|
client = TOR_downloader()
|
||||||
|
|
||||||
# Add magnet link
|
# Add magnet link
|
||||||
client.add_magnet_link("magnet:?xt=urn:btih:example_hash&dn=example_name")
|
client.add_magnet_link("magnet:?xt=urn:btih:example_hash&dn=example_name", save_path=".")
|
||||||
|
|
||||||
# Start download
|
# Start download
|
||||||
client.start_download()
|
client.start_download()
|
||||||
|
|
||||||
# Move downloaded files to specific location
|
|
||||||
client.move_downloaded_files("/downloads/torrents/")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Torrent example](./Test/Download/TOR.py) for complete usage.
|
See [Torrent example](./Test/Download/TOR.py) for complete usage.
|
||||||
|
@ -58,6 +58,5 @@ def download_title(select_title: MediaItem):
|
|||||||
|
|
||||||
# Tor manager
|
# Tor manager
|
||||||
manager = TOR_downloader()
|
manager = TOR_downloader()
|
||||||
manager.add_magnet_link(final_url)
|
manager.add_magnet_link(final_url, save_path=mp4_path)
|
||||||
manager.start_download()
|
manager.start_download()
|
||||||
manager.move_downloaded_files(mp4_path)
|
|
@ -4,13 +4,15 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import shutil
|
|
||||||
import psutil
|
import psutil
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
# External library
|
# External libraries
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from tqdm import tqdm
|
||||||
|
import qbittorrentapi
|
||||||
|
|
||||||
|
|
||||||
# Internal utilities
|
# Internal utilities
|
||||||
@ -19,19 +21,12 @@ from StreamingCommunity.Util.os import internet_manager
|
|||||||
from StreamingCommunity.Util.config_json import config_manager, get_use_large_bar
|
from StreamingCommunity.Util.config_json import config_manager, get_use_large_bar
|
||||||
|
|
||||||
|
|
||||||
# External libraries
|
# Configuration
|
||||||
from tqdm import tqdm
|
HOST = config_manager.get('QBIT_CONFIG', 'host')
|
||||||
import qbittorrentapi
|
PORT = config_manager.get('QBIT_CONFIG', 'port')
|
||||||
|
USERNAME = config_manager.get('QBIT_CONFIG', 'user')
|
||||||
|
PASSWORD = config_manager.get('QBIT_CONFIG', 'pass')
|
||||||
|
|
||||||
|
|
||||||
# Tor config
|
|
||||||
HOST = config_manager.get_dict('QBIT_CONFIG', 'host')
|
|
||||||
PORT = config_manager.get_dict('QBIT_CONFIG', 'port')
|
|
||||||
USERNAME = config_manager.get_dict('QBIT_CONFIG', 'user')
|
|
||||||
PASSWORD = config_manager.get_dict('QBIT_CONFIG', 'pass')
|
|
||||||
|
|
||||||
|
|
||||||
# Variable
|
|
||||||
REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
|
REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
@ -39,136 +34,295 @@ console = Console()
|
|||||||
class TOR_downloader:
|
class TOR_downloader:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
Initializes the TorrentManager instance.
|
Initializes the TorrentDownloader instance and connects to qBittorrent.
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- host (str): IP address or hostname of the qBittorrent Web UI.
|
|
||||||
- port (int): Port of the qBittorrent Web UI.
|
|
||||||
- username (str): Username for accessing qBittorrent.
|
|
||||||
- password (str): Password for accessing qBittorrent.
|
|
||||||
"""
|
"""
|
||||||
|
self.console = Console()
|
||||||
|
self.latest_torrent_hash = None
|
||||||
|
self.output_file = None
|
||||||
|
self.file_name = None
|
||||||
|
self.save_path = None
|
||||||
|
self.torrent_name = None
|
||||||
|
|
||||||
|
self._connect_to_client()
|
||||||
|
|
||||||
|
def _connect_to_client(self):
|
||||||
|
"""
|
||||||
|
Establishes connection to qBittorrent client using configuration parameters.
|
||||||
|
"""
|
||||||
|
self.console.print(f"[cyan]Connecting to qBittorrent: [green]{HOST}:{PORT}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
console.print(f"[cyan]Connect to: [green]{HOST}:{PORT}")
|
# Create client with connection settings and timeouts
|
||||||
self.qb = qbittorrentapi.Client(
|
self.qb = qbittorrentapi.Client(
|
||||||
host=HOST,
|
host=HOST,
|
||||||
port=PORT,
|
port=PORT,
|
||||||
username=USERNAME,
|
username=USERNAME,
|
||||||
password=PASSWORD
|
password=PASSWORD,
|
||||||
|
VERIFY_WEBUI_CERTIFICATE=False,
|
||||||
|
REQUESTS_ARGS={'timeout': REQUEST_TIMEOUT}
|
||||||
)
|
)
|
||||||
|
|
||||||
except:
|
# Test connection and login
|
||||||
logging.error("Start qbittorrent first.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
self.username = USERNAME
|
|
||||||
self.password = PASSWORD
|
|
||||||
self.latest_torrent_hash = None
|
|
||||||
self.output_file = None
|
|
||||||
self.file_name = None
|
|
||||||
|
|
||||||
self.login()
|
|
||||||
|
|
||||||
def login(self):
|
|
||||||
"""
|
|
||||||
Logs into the qBittorrent Web UI.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.qb.auth_log_in()
|
self.qb.auth_log_in()
|
||||||
self.logged_in = True
|
qb_version = self.qb.app.version
|
||||||
logging.info("Successfully logged in to qBittorrent.")
|
self.console.print(f"[green]Successfully connected to qBittorrent v{qb_version}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Failed to log in: {str(e)}")
|
logging.error(f"Unexpected error: {str(e)}")
|
||||||
self.logged_in = False
|
self.console.print(f"[bold red]Error initializing qBittorrent client: {str(e)}[/bold red]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def delete_magnet(self, torrent_info):
|
def add_magnet_link(self, magnet_link, save_path=None):
|
||||||
"""
|
"""
|
||||||
Deletes a torrent if it is not downloadable (no seeds/peers).
|
Adds a magnet link to qBittorrent and retrieves torrent information.
|
||||||
|
|
||||||
Parameters:
|
Args:
|
||||||
- torrent_info: Object containing torrent information obtained from the qBittorrent API.
|
magnet_link (str): Magnet link to add to qBittorrent
|
||||||
"""
|
save_path (str, optional): Directory where to save the downloaded files
|
||||||
if (int(torrent_info.dlspeed) == 0 and
|
|
||||||
int(torrent_info.num_leechs) == 0 and
|
|
||||||
int(torrent_info.num_seeds) == 0):
|
|
||||||
|
|
||||||
console.print(f"[bold red]Torrent not downloadable. Removing...[/bold red]")
|
|
||||||
try:
|
|
||||||
self.qb.torrents_delete(delete_files=True, torrent_hashes=torrent_info.hash)
|
|
||||||
except Exception as delete_error:
|
|
||||||
logging.error(f"Error while removing torrent: {delete_error}")
|
|
||||||
|
|
||||||
self.latest_torrent_hash = None
|
|
||||||
|
|
||||||
def add_magnet_link(self, magnet_link):
|
|
||||||
"""
|
|
||||||
Adds a magnet link and retrieves detailed torrent information.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
magnet_link (str): Magnet link to add.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Information about the added torrent, or None in case of error.
|
TorrentDictionary: Information about the added torrent
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If magnet link is invalid or torrent can't be added
|
||||||
"""
|
"""
|
||||||
|
# Extract hash from magnet link
|
||||||
magnet_hash_match = re.search(r'urn:btih:([0-9a-fA-F]+)', magnet_link)
|
magnet_hash_match = re.search(r'urn:btih:([0-9a-fA-F]+)', magnet_link)
|
||||||
if not magnet_hash_match:
|
if not magnet_hash_match:
|
||||||
raise ValueError("Magnet link hash not found")
|
raise ValueError("Invalid magnet link: hash not found")
|
||||||
|
|
||||||
magnet_hash = magnet_hash_match.group(1).lower()
|
magnet_hash = magnet_hash_match.group(1).lower()
|
||||||
|
|
||||||
# Extract the torrent name, if available
|
# Extract torrent name from magnet link if available
|
||||||
name_match = re.search(r'dn=([^&]+)', magnet_link)
|
name_match = re.search(r'dn=([^&]+)', magnet_link)
|
||||||
torrent_name = name_match.group(1).replace('+', ' ') if name_match else "Name not available"
|
torrent_name = name_match.group(1).replace('+', ' ') if name_match else "Unknown"
|
||||||
|
|
||||||
# Save the timestamp before adding the torrent
|
# Record timestamp before adding torrent for identification
|
||||||
before_add_time = time.time()
|
before_add_time = time.time()
|
||||||
|
|
||||||
console.print(f"[cyan]Adding magnet link ...")
|
self.console.print(f"[cyan]Adding magnet link for: [yellow]{torrent_name}")
|
||||||
self.qb.torrents_add(urls=magnet_link)
|
|
||||||
|
|
||||||
time.sleep(1)
|
# Prepare save path
|
||||||
|
if save_path:
|
||||||
|
self.console.print(f"[cyan]Setting save location to: [green]{save_path}")
|
||||||
|
|
||||||
torrents = self.qb.torrents_info()
|
# Ensure save path exists
|
||||||
matching_torrents = [
|
os.makedirs(save_path, exist_ok=True)
|
||||||
t for t in torrents
|
|
||||||
if (t.hash.lower() == magnet_hash) or (getattr(t, 'added_on', 0) > before_add_time)
|
# Add the torrent with save options
|
||||||
]
|
add_options = {
|
||||||
|
"urls": magnet_link,
|
||||||
|
"use_auto_torrent_management": False, # Don't use automatic management
|
||||||
|
"is_paused": False, # Start download immediately
|
||||||
|
"tags": ["StreamingCommunity"] # Add tag for easy identification
|
||||||
|
}
|
||||||
|
|
||||||
|
# If save_path is provided, add it to options
|
||||||
|
if save_path:
|
||||||
|
add_options["save_path"] = save_path
|
||||||
|
|
||||||
|
add_result = self.qb.torrents_add(**add_options)
|
||||||
|
|
||||||
|
if not add_result == "Ok.":
|
||||||
|
raise ValueError(f"Failed to add torrent: {add_result}")
|
||||||
|
|
||||||
|
# Wait for torrent to be recognized by the client
|
||||||
|
time.sleep(1.5)
|
||||||
|
|
||||||
|
# Find the newly added torrent
|
||||||
|
matching_torrents = self._find_torrent(magnet_hash, before_add_time)
|
||||||
|
|
||||||
if not matching_torrents:
|
if not matching_torrents:
|
||||||
raise ValueError("No matching torrent found")
|
raise ValueError("Torrent was added but couldn't be found in client")
|
||||||
|
|
||||||
torrent_info = matching_torrents[0]
|
torrent_info = matching_torrents[0]
|
||||||
|
|
||||||
console.print("\n[bold green]Added Torrent Details:[/bold green]")
|
# Store relevant information
|
||||||
console.print(f"[yellow]Name:[/yellow] {torrent_info.name or torrent_name}")
|
|
||||||
console.print(f"[yellow]Hash:[/yellow] {torrent_info.hash}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
self.latest_torrent_hash = torrent_info.hash
|
self.latest_torrent_hash = torrent_info.hash
|
||||||
self.output_file = torrent_info.content_path
|
self.output_file = torrent_info.content_path
|
||||||
self.file_name = torrent_info.name
|
self.file_name = torrent_info.name
|
||||||
|
self.save_path = torrent_info.save_path
|
||||||
|
|
||||||
# Wait and verify if the download is possible
|
# Display torrent information
|
||||||
time.sleep(5)
|
self._display_torrent_info(torrent_info)
|
||||||
self.delete_magnet(self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0])
|
|
||||||
|
# Check download viability after a short delay
|
||||||
|
time.sleep(3)
|
||||||
|
self._check_torrent_viability()
|
||||||
|
|
||||||
return torrent_info
|
return torrent_info
|
||||||
|
|
||||||
|
def _find_torrent(self, magnet_hash=None, timestamp=None):
|
||||||
|
"""
|
||||||
|
Find a torrent by hash or added timestamp.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
magnet_hash (str, optional): Hash of the torrent to find
|
||||||
|
timestamp (float, optional): Timestamp to compare against torrent added_on time
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List of matching torrent objects
|
||||||
|
"""
|
||||||
|
# Get list of all torrents with detailed information
|
||||||
|
torrents = self.qb.torrents_info()
|
||||||
|
|
||||||
|
if magnet_hash:
|
||||||
|
# First try to find by hash (most reliable)
|
||||||
|
hash_matches = [t for t in torrents if t.hash.lower() == magnet_hash]
|
||||||
|
if hash_matches:
|
||||||
|
return hash_matches
|
||||||
|
|
||||||
|
if timestamp:
|
||||||
|
# Fallback to finding by timestamp (least recently added torrent after timestamp)
|
||||||
|
time_matches = [t for t in torrents if getattr(t, 'added_on', 0) > timestamp]
|
||||||
|
if time_matches:
|
||||||
|
# Sort by added_on to get the most recently added
|
||||||
|
return sorted(time_matches, key=lambda t: getattr(t, 'added_on', 0), reverse=True)
|
||||||
|
|
||||||
|
# If we're just looking for the latest torrent
|
||||||
|
if not magnet_hash and not timestamp:
|
||||||
|
if torrents:
|
||||||
|
return [sorted(torrents, key=lambda t: getattr(t, 'added_on', 0), reverse=True)[0]]
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _display_torrent_info(self, torrent_info):
|
||||||
|
"""
|
||||||
|
Display detailed information about a torrent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
torrent_info: Torrent object from qBittorrent API
|
||||||
|
"""
|
||||||
|
self.console.print("\n[bold green]Torrent Details:[/bold green]")
|
||||||
|
self.console.print(f"[yellow]Name:[/yellow] {torrent_info.name}")
|
||||||
|
self.console.print(f"[yellow]Hash:[/yellow] {torrent_info.hash}")
|
||||||
|
#self.console.print(f"[yellow]Size:[/yellow] {internet_manager.format_file_size(torrent_info.size)}")
|
||||||
|
self.console.print(f"[yellow]Save Path:[/yellow] {torrent_info.save_path}")
|
||||||
|
|
||||||
|
# Show additional metadata if available
|
||||||
|
if hasattr(torrent_info, 'category') and torrent_info.category:
|
||||||
|
self.console.print(f"[yellow]Category:[/yellow] {torrent_info.category}")
|
||||||
|
|
||||||
|
if hasattr(torrent_info, 'tags') and torrent_info.tags:
|
||||||
|
self.console.print(f"[yellow]Tags:[/yellow] {torrent_info.tags}")
|
||||||
|
|
||||||
|
# Show connection info
|
||||||
|
self.console.print(f"[yellow]Seeds:[/yellow] {torrent_info.num_seeds} complete, {torrent_info.num_complete} connected")
|
||||||
|
self.console.print(f"[yellow]Peers:[/yellow] {torrent_info.num_leechs} incomplete, {torrent_info.num_incomplete} connected")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def _check_torrent_viability(self):
|
||||||
|
"""
|
||||||
|
Check if the torrent is viable for downloading (has seeds/peers).
|
||||||
|
Removes the torrent if it doesn't appear to be downloadable.
|
||||||
|
"""
|
||||||
|
if not self.latest_torrent_hash:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get updated torrent info
|
||||||
|
torrent_info = self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0]
|
||||||
|
|
||||||
|
# Check if torrent has no activity and no source (seeders or peers)
|
||||||
|
if (torrent_info.dlspeed == 0 and
|
||||||
|
torrent_info.num_leechs == 0 and
|
||||||
|
torrent_info.num_seeds == 0 and
|
||||||
|
torrent_info.state in ('stalledDL', 'missingFiles', 'error')):
|
||||||
|
|
||||||
|
self.console.print(f"[bold red]Torrent not downloadable. No seeds or peers available. Removing...[/bold red]")
|
||||||
|
self._remove_torrent(self.latest_torrent_hash)
|
||||||
|
self.latest_torrent_hash = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error checking torrent viability: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _remove_torrent(self, torrent_hash, delete_files=True):
|
||||||
|
"""
|
||||||
|
Remove a torrent from qBittorrent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
torrent_hash (str): Hash of the torrent to remove
|
||||||
|
delete_files (bool): Whether to delete associated files
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.qb.torrents_delete(delete_files=delete_files, torrent_hashes=torrent_hash)
|
||||||
|
self.console.print(f"[yellow]Torrent removed from client[/yellow]")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error removing torrent: {str(e)}")
|
||||||
|
|
||||||
|
def move_completed_torrent(self, destination):
|
||||||
|
"""
|
||||||
|
Move a completed torrent to a new destination using qBittorrent's API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
destination (str): New destination path
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
if not self.latest_torrent_hash:
|
||||||
|
self.console.print("[yellow]No active torrent to move[/yellow]")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Make sure destination exists
|
||||||
|
os.makedirs(destination, exist_ok=True)
|
||||||
|
|
||||||
|
# Get current state of the torrent
|
||||||
|
torrent_info = self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0]
|
||||||
|
|
||||||
|
if torrent_info.progress < 1.0:
|
||||||
|
self.console.print("[yellow]Torrent not yet completed. Cannot move.[/yellow]")
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.console.print(f"[cyan]Moving torrent to: [green]{destination}")
|
||||||
|
|
||||||
|
# Use qBittorrent API to set location
|
||||||
|
self.qb.torrents_set_location(location=destination, torrent_hashes=self.latest_torrent_hash)
|
||||||
|
|
||||||
|
# Wait a bit for the move operation to complete
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# Verify move was successful
|
||||||
|
updated_info = self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0]
|
||||||
|
|
||||||
|
if Path(updated_info.save_path) == Path(destination):
|
||||||
|
self.console.print(f"[bold green]Successfully moved torrent to {destination}[/bold green]")
|
||||||
|
self.save_path = updated_info.save_path
|
||||||
|
self.output_file = updated_info.content_path
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.console.print(f"[bold red]Failed to move torrent. Current path: {updated_info.save_path}[/bold red]")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error moving torrent: {str(e)}")
|
||||||
|
self.console.print(f"[bold red]Error moving torrent: {str(e)}[/bold red]")
|
||||||
|
return False
|
||||||
|
|
||||||
def start_download(self):
|
def start_download(self):
|
||||||
"""
|
"""
|
||||||
Starts downloading the added torrent and monitors its progress.
|
Start downloading the torrent and monitor its progress with a progress bar.
|
||||||
"""
|
"""
|
||||||
if self.latest_torrent_hash is not None:
|
if not self.latest_torrent_hash:
|
||||||
try:
|
self.console.print("[yellow]No active torrent to download[/yellow]")
|
||||||
|
return False
|
||||||
|
|
||||||
# Custom progress bar for mobile and PC
|
try:
|
||||||
|
# Ensure the torrent is started
|
||||||
|
self.qb.torrents_resume(torrent_hashes=self.latest_torrent_hash)
|
||||||
|
|
||||||
|
# Configure progress bar display format based on device
|
||||||
if get_use_large_bar():
|
if get_use_large_bar():
|
||||||
bar_format = (
|
bar_format = (
|
||||||
f"{Colors.YELLOW}[TOR] {Colors.WHITE}({Colors.CYAN}video{Colors.WHITE}): "
|
f"{Colors.YELLOW}[TOR] {Colors.WHITE}({Colors.CYAN}video{Colors.WHITE}): "
|
||||||
f"{Colors.RED}{{percentage:.2f}}% {Colors.MAGENTA}{{bar}} {Colors.WHITE}[ "
|
f"{Colors.RED}{{percentage:.2f}}% {Colors.MAGENTA}{{bar}} {Colors.WHITE}[ "
|
||||||
f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
|
f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
bar_format = (
|
bar_format = (
|
||||||
f"{Colors.YELLOW}Proc{Colors.WHITE}: "
|
f"{Colors.YELLOW}Proc{Colors.WHITE}: "
|
||||||
@ -176,131 +330,147 @@ class TOR_downloader:
|
|||||||
f"{Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
|
f"{Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
|
||||||
)
|
)
|
||||||
|
|
||||||
progress_bar = tqdm(
|
# Initialize progress bar
|
||||||
|
with tqdm(
|
||||||
total=100,
|
total=100,
|
||||||
ascii='░▒█',
|
ascii='░▒█',
|
||||||
bar_format=bar_format,
|
bar_format=bar_format,
|
||||||
unit_scale=True,
|
unit_scale=True,
|
||||||
unit_divisor=1024,
|
unit_divisor=1024,
|
||||||
mininterval=0.05
|
mininterval=0.1
|
||||||
)
|
) as pbar:
|
||||||
|
|
||||||
|
was_downloading = True
|
||||||
|
stalled_count = 0
|
||||||
|
|
||||||
with progress_bar as pbar:
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
|
# Get updated torrent information
|
||||||
|
try:
|
||||||
torrent_info = self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0]
|
torrent_info = self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0]
|
||||||
|
except (IndexError, qbittorrentapi.exceptions.NotFound404Error):
|
||||||
|
self.console.print("[bold red]Torrent no longer exists in client[/bold red]")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Store the latest path and name
|
||||||
self.save_path = torrent_info.save_path
|
self.save_path = torrent_info.save_path
|
||||||
self.torrent_name = torrent_info.name
|
self.torrent_name = torrent_info.name
|
||||||
|
self.output_file = torrent_info.content_path
|
||||||
|
|
||||||
|
# Update progress
|
||||||
progress = torrent_info.progress * 100
|
progress = torrent_info.progress * 100
|
||||||
pbar.n = progress
|
pbar.n = progress
|
||||||
|
|
||||||
|
# Get download statistics
|
||||||
download_speed = torrent_info.dlspeed
|
download_speed = torrent_info.dlspeed
|
||||||
|
upload_speed = torrent_info.upspeed
|
||||||
total_size = torrent_info.size
|
total_size = torrent_info.size
|
||||||
downloaded_size = torrent_info.downloaded
|
downloaded_size = torrent_info.downloaded
|
||||||
|
eta = torrent_info.eta # eta in seconds
|
||||||
|
|
||||||
# Format the downloaded size
|
# Format sizes and speeds using the existing functions without modification
|
||||||
downloaded_size_str = internet_manager.format_file_size(downloaded_size)
|
downloaded_size_str = internet_manager.format_file_size(downloaded_size)
|
||||||
downloaded_size = downloaded_size_str.split(' ')[0]
|
|
||||||
|
|
||||||
# Safely format the total size
|
|
||||||
total_size_str = internet_manager.format_file_size(total_size)
|
total_size_str = internet_manager.format_file_size(total_size)
|
||||||
total_size_parts = total_size_str.split(' ')
|
download_speed_str = internet_manager.format_transfer_speed(download_speed)
|
||||||
if len(total_size_parts) >= 2:
|
|
||||||
total_size = total_size_parts[0]
|
|
||||||
total_size_unit = total_size_parts[1]
|
|
||||||
else:
|
|
||||||
total_size = total_size_str
|
|
||||||
total_size_unit = ""
|
|
||||||
|
|
||||||
# Safely format the average download speed
|
# Parse the formatted strings to extract numbers and units
|
||||||
average_internet_str = internet_manager.format_transfer_speed(download_speed)
|
# The format is "X.XX Unit" from the format_file_size and format_transfer_speed functions
|
||||||
average_internet_parts = average_internet_str.split(' ')
|
dl_parts = downloaded_size_str.split(' ')
|
||||||
if len(average_internet_parts) >= 2:
|
dl_size_num = dl_parts[0] if len(dl_parts) > 0 else "0"
|
||||||
average_internet = average_internet_parts[0]
|
dl_size_unit = dl_parts[1] if len(dl_parts) > 1 else "B"
|
||||||
average_internet_unit = average_internet_parts[1]
|
|
||||||
else:
|
|
||||||
average_internet = average_internet_str
|
|
||||||
average_internet_unit = ""
|
|
||||||
|
|
||||||
if get_use_large_bar():
|
total_parts = total_size_str.split(' ')
|
||||||
|
total_size_num = total_parts[0] if len(total_parts) > 0 else "0"
|
||||||
|
total_size_unit = total_parts[1] if len(total_parts) > 1 else "B"
|
||||||
|
|
||||||
|
speed_parts = download_speed_str.split(' ')
|
||||||
|
speed_num = speed_parts[0] if len(speed_parts) > 0 else "0"
|
||||||
|
speed_unit = ' '.join(speed_parts[1:]) if len(speed_parts) > 1 else "B/s"
|
||||||
|
|
||||||
|
# Check if download is active
|
||||||
|
currently_downloading = download_speed > 0
|
||||||
|
|
||||||
|
# Handle stalled downloads
|
||||||
|
if was_downloading and not currently_downloading and progress < 100:
|
||||||
|
stalled_count += 1
|
||||||
|
if stalled_count >= 15: # 3 seconds (15 * 0.2)
|
||||||
|
pbar.set_description(f"{Colors.RED}Stalled")
|
||||||
|
else:
|
||||||
|
stalled_count = 0
|
||||||
|
pbar.set_description(f"{Colors.GREEN}Active")
|
||||||
|
|
||||||
|
was_downloading = currently_downloading
|
||||||
|
|
||||||
|
# Update progress bar display with formatted statistics
|
||||||
pbar.set_postfix_str(
|
pbar.set_postfix_str(
|
||||||
f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_size} {Colors.WHITE}< {Colors.GREEN}{total_size} {Colors.RED}{total_size_unit} "
|
f"{Colors.GREEN}{dl_size_num} {Colors.RED}{dl_size_unit} {Colors.WHITE}< "
|
||||||
f"{Colors.WHITE}| {Colors.CYAN}{average_internet} {Colors.RED}{average_internet_unit}"
|
f"{Colors.GREEN}{total_size_num} {Colors.RED}{total_size_unit}{Colors.WHITE}, "
|
||||||
|
f"{Colors.CYAN}{speed_num} {Colors.RED}{speed_unit}"
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
pbar.set_postfix_str(
|
|
||||||
f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_size}{Colors.RED} {total_size} "
|
|
||||||
f"{Colors.WHITE}| {Colors.CYAN}{average_internet} {Colors.RED}{average_internet_unit}"
|
|
||||||
)
|
|
||||||
|
|
||||||
pbar.refresh()
|
pbar.refresh()
|
||||||
time.sleep(0.2)
|
|
||||||
|
|
||||||
|
# Check for completion
|
||||||
if int(progress) == 100:
|
if int(progress) == 100:
|
||||||
|
pbar.n = 100
|
||||||
|
pbar.refresh()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Check torrent state for errors
|
||||||
|
if torrent_info.state in ('error', 'missingFiles', 'unknown'):
|
||||||
|
self.console.print(f"[bold red]Error in torrent: {torrent_info.state}[/bold red]")
|
||||||
|
return False
|
||||||
|
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
self.console.print(f"[bold green]Download complete: {self.torrent_name}[/bold green]")
|
||||||
|
return True
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logging.info("Download process interrupted.")
|
self.console.print("[yellow]Download process interrupted[/yellow]")
|
||||||
|
return False
|
||||||
|
|
||||||
def is_file_in_use(self, file_path: str) -> bool:
|
except Exception as e:
|
||||||
|
logging.error(f"Error monitoring download: {str(e)}")
|
||||||
|
self.console.print(f"[bold red]Error monitoring download: {str(e)}[/bold red]")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_file_in_use(self, file_path):
|
||||||
"""
|
"""
|
||||||
Checks if a file is being used by any process.
|
Check if a file is currently being used by any process.
|
||||||
|
|
||||||
Parameters:
|
Args:
|
||||||
- file_path (str): The file path to check.
|
file_path (str): Path to the file to check
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- bool: True if the file is in use, False otherwise.
|
bool: True if file is in use, False otherwise
|
||||||
"""
|
"""
|
||||||
for proc in psutil.process_iter(['open_files']):
|
# Convert to absolute path for consistency
|
||||||
|
file_path = str(Path(file_path).resolve())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if any(file_path == f.path for f in proc.info['open_files'] or []):
|
for proc in psutil.process_iter(['open_files', 'name']):
|
||||||
|
try:
|
||||||
|
proc_info = proc.info
|
||||||
|
if 'open_files' in proc_info and proc_info['open_files']:
|
||||||
|
for file_info in proc_info['open_files']:
|
||||||
|
if file_path == file_info.path:
|
||||||
return True
|
return True
|
||||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def move_downloaded_files(self, destination: str):
|
|
||||||
"""
|
|
||||||
Moves the downloaded files of the most recent torrent to a new location.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- destination (str): Destination folder.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- bool: True if the move was successful, False otherwise.
|
|
||||||
"""
|
|
||||||
console.print(f"[cyan]Destination folder: [red]{destination}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
timeout = 5
|
|
||||||
elapsed = 0
|
|
||||||
|
|
||||||
while self.is_file_in_use(self.output_file) and elapsed < timeout:
|
|
||||||
time.sleep(1)
|
|
||||||
elapsed += 1
|
|
||||||
|
|
||||||
if elapsed == timeout:
|
|
||||||
raise Exception(f"File '{self.output_file}' is in use and could not be moved.")
|
|
||||||
|
|
||||||
os.makedirs(destination, exist_ok=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
shutil.move(self.output_file, destination)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == 17: # Error when moving between different disks
|
|
||||||
shutil.copy2(self.output_file, destination)
|
|
||||||
os.remove(self.output_file)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
time.sleep(5)
|
|
||||||
last_torrent = self.qb.torrents_info()[-1]
|
|
||||||
self.qb.torrents_delete(delete_files=True, torrent_hashes=last_torrent.hash)
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error moving file: {e}")
|
logging.error(f"Error checking if file is in use: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""
|
||||||
|
Clean up resources and perform final operations before shutting down.
|
||||||
|
"""
|
||||||
|
if self.latest_torrent_hash:
|
||||||
|
self._remove_torrent(self.latest_torrent_hash)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.qb.auth_log_out()
|
||||||
|
except:
|
||||||
|
pass
|
@ -19,9 +19,6 @@ start_message()
|
|||||||
logger = Logger()
|
logger = Logger()
|
||||||
manager = TOR_downloader()
|
manager = TOR_downloader()
|
||||||
|
|
||||||
magnet_link = """magnet:?xt=urn:btih:d1257567d7a76d2e40734f561fd175769285ed33&dn=Alien.Romulus.2024.1080p.BluRay.x264-FHC.mkv&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftorrentclub.space%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.bt4g.com%3A2095%2Fannounce&tr=https%3A%2F%2Ftr.burnabyhighstar.com%3A443%2Fannoun
|
magnet_link = """magnet:?xt=urn:btih:0E0CDB5387B4C71C740BD21E8144F3735C3F899E&dn=Krapopolis.S02E14.720p.x265-TiPEX&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexplodie.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.tiny-vps.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.dler.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.darkness.services%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.openbittorrent.com%3A80%2Fannounce&tr=udp%3A%2F%2Fopentracker.i2p.rocks%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce"""
|
||||||
ce&tr=udp%3A%2F%2Ftracker.monitorit4.me%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.0x.tf%3A6969%2Fannounce&tr=http%3A%2F%2Fbt.okmp3.ru%3A2710%2Fannounce&tr=https%3A%2F%2Ftr.doogh.club%3A443%2Fannounce&tr=https%3A%2F%2Ft.btcland.xyz%3A443%2Fannounce&tr=https%3A%2F%2Ftr.fuckbitcoin.xyz%3A443%2Fannounce&tr=http%3A%2F%2Ftrack
|
manager.add_magnet_link(magnet_link, save_path=os.path.join(src_path, "Video"))
|
||||||
er.files.fm%3A6969%2Fannounce"""
|
|
||||||
manager.add_magnet_link(magnet_link)
|
|
||||||
manager.start_download()
|
manager.start_download()
|
||||||
manager.move_downloaded_files(".")
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user