diff --git a/downloader_utils.py b/downloader_utils.py index 383e66d..9060db8 100644 --- a/downloader_utils.py +++ b/downloader_utils.py @@ -13,6 +13,16 @@ from PyQt5 .QtCore import QObject ,pyqtSignal ,QThread ,QMutex ,QMutexLocker from urllib .parse import urlparse try : from mega import Mega + # Import download functions from drive.py + # Assuming drive.py is in the same directory + try: + from drive import download_mega_file as drive_download_mega_file, \ + download_gdrive_file, download_dropbox_file + # To avoid confusion, we'll use drive_download_mega_file internally when calling from main + # and ensure this module exports it as download_mega_file for compatibility if needed, + # or main.py can be updated to call drive_download_mega_file. + except ImportError as drive_import_err: + print(f"ERROR importing from drive.py: {drive_import_err}. External drive downloads will fail.") except ImportError : print ("ERROR: mega.py library not found. Please install it: pip install mega.py") try : diff --git a/drive.py b/drive.py new file mode 100644 index 0000000..5e84e0d --- /dev/null +++ b/drive.py @@ -0,0 +1,228 @@ +from mega import Mega # Correctly import the Mega class +import os +import requests # Added import for requests.exceptions +import traceback +from urllib.parse import urlparse, urlunparse, parse_qs, urlencode + +try: + import gdown + GDOWN_AVAILABLE = True +except ImportError: + GDOWN_AVAILABLE = False + +def download_mega_file(mega_link, download_path=".", logger_func=print): + """ + Downloads a file from a public Mega.nz link. + + Args: + mega_link (str): The public Mega.nz link to the file. + download_path (str, optional): The directory to save the downloaded file. + Defaults to the current directory. + logger_func (callable, optional): Function to use for logging. Defaults to print. + """ + logger_func("Initializing Mega client...") + mega_client = Mega() # Instantiate the imported Mega class + + # For public links, login is usually not required. + # If you needed to log in for private files: + # m = mega.login("your_email@example.com", "your_password") + # Or anonymous login for some operations: + m = mega_client.login() # Call login on the instance + + logger_func(f"Attempting to download from: {mega_link}") + + try: + # Ensure the download path exists + if not os.path.exists(download_path): + logger_func(f"Download path '{download_path}' does not exist. Creating it...") + os.makedirs(download_path, exist_ok=True) + + # It's often better to let mega.py handle finding the file from the URL + # and then downloading it. + # The `download_url` method is convenient for public links. + logger_func(f"Starting download to '{download_path}'...") + + # mega.py's download_url will download the file to the specified path + # and name it appropriately. + file_handle = m.download_url(mega_link, dest_path=download_path) # m is the logged-in client + + if file_handle: + # The download_url method itself handles the file saving. + # The return value 'file_handle' might be the filename or a handle, + # depending on the library version and specifics. + # For modern mega.py, it often returns the local path to the downloaded file. + logger_func(f"File downloaded successfully! (Handle: {file_handle})") + # If you need the exact filename it was saved as: + # You might need to inspect what `download_url` returns or list dir contents + # if the filename isn't directly returned in a usable way. + # Often, the library names it based on the Mega file name. + # For this example, we'll assume it's handled and saved in download_path. + + # To get the actual filename (as mega.py might rename it) + # We can list the directory and find the newest file if only one download happened + # Or, more robustly, use the information from `m.get_public_url_info(mega_link)` + # to get the expected filename. + try: + info = m.get_public_url_info(mega_link) + filename = info.get('name', 'downloaded_file') # Default if name not found + full_download_path = os.path.join(download_path, filename) + logger_func(f"Downloaded file should be at: {full_download_path}") + if not os.path.exists(full_download_path): + logger_func(f"Warning: Expected file '{full_download_path}' not found. The library might have saved it with a different name or failed silently after appearing to succeed.") + except Exception as e_info: + logger_func(f"Could not retrieve file info to confirm filename: {e_info}") + + else: + logger_func("Download failed. The download_url method did not return a file handle.") + + except PermissionError: + logger_func(f"Error: Permission denied to write to '{download_path}'. Please check permissions.") + except FileNotFoundError: + logger_func(f"Error: The specified download path '{download_path}' is invalid or a component was not found.") + except ConnectionError as e: # More specific requests exception + logger_func(f"Error: Connection problem. {e}") + except requests.exceptions.RequestException as e: # Catch other requests-related errors + logger_func(f"Error during request to Mega: {e}") + except Exception as e: + logger_func(f"An error occurred during Mega download: {e}") + traceback.print_exc() + +def download_gdrive_file(gdrive_link, download_path=".", logger_func=print): + """ + Downloads a file from a public Google Drive link. + + Args: + gdrive_link (str): The public Google Drive link to the file. + download_path (str, optional): The directory to save the downloaded file. + Defaults to the current directory. + logger_func (callable, optional): Function to use for logging. Defaults to print. + """ + if not GDOWN_AVAILABLE: + logger_func("❌ Error: gdown library is not installed. Cannot download from Google Drive.") + logger_func("Please install it: pip install gdown") + raise ImportError("gdown library not found. Please install it: pip install gdown") + + logger_func(f"Attempting to download from Google Drive: {gdrive_link}") + try: + if not os.path.exists(download_path): + logger_func(f"Download path '{download_path}' does not exist. Creating it...") + os.makedirs(download_path, exist_ok=True) + + logger_func(f"Starting Google Drive download to '{download_path}'...") + # gdown.download returns the path to the downloaded file + output_file_path = gdown.download(gdrive_link, output=download_path, quiet=False, fuzzy=True) + + if output_file_path and os.path.exists(os.path.join(download_path, os.path.basename(output_file_path))): + logger_func(f"✅ Google Drive file downloaded successfully: {output_file_path}") + elif output_file_path: # gdown might return a path but if download_path was a dir, it might be just the filename + full_path_check = os.path.join(download_path, output_file_path) + if os.path.exists(full_path_check): + logger_func(f"✅ Google Drive file downloaded successfully: {full_path_check}") + else: + logger_func(f"⚠️ Google Drive download finished, gdown returned '{output_file_path}', but file not found at expected location.") + logger_func(f" Please check '{download_path}' for the downloaded file, it might have a different name than expected by gdown's return.") + # As a fallback, list files in dir if only one was expected + files_in_dest = [f for f in os.listdir(download_path) if os.path.isfile(os.path.join(download_path, f))] + if len(files_in_dest) == 1: + logger_func(f" Found one file in destination: {os.path.join(download_path, files_in_dest[0])}. Assuming this is it.") + elif len(files_in_dest) > 1 and output_file_path in files_in_dest: # if gdown returned just filename + logger_func(f" Confirmed file '{output_file_path}' exists in '{download_path}'.") + else: + raise Exception(f"gdown download failed or file not found. Returned: {output_file_path}") + else: + logger_func("❌ Google Drive download failed. gdown did not return an output path.") + raise Exception("gdown download failed.") + + except PermissionError: + logger_func(f"❌ Error: Permission denied to write to '{download_path}'. Please check permissions.") + raise + except Exception as e: + logger_func(f"❌ An error occurred during Google Drive download: {e}") + traceback.print_exc() + raise + +def _get_filename_from_headers(headers): + cd = headers.get('content-disposition') + if not cd: + return None + fname_match = re.findall('filename="?([^"]+)"?', cd) + if fname_match: + return fname_match[0].strip() + return None + +def download_dropbox_file(dropbox_link, download_path=".", logger_func=print): + """ + Downloads a file from a public Dropbox link. + + Args: + dropbox_link (str): The public Dropbox link to the file. + download_path (str, optional): The directory to save the downloaded file. + Defaults to the current directory. + logger_func (callable, optional): Function to use for logging. Defaults to print. + """ + logger_func(f"Attempting to download from Dropbox: {dropbox_link}") + + # Modify URL for direct download + parsed_url = urlparse(dropbox_link) + query_params = parse_qs(parsed_url.query) + query_params['dl'] = ['1'] # Set dl=1 for direct download + new_query = urlencode(query_params, doseq=True) + direct_download_url = urlunparse(parsed_url._replace(query=new_query)) + + logger_func(f" Using direct download URL: {direct_download_url}") + + try: + if not os.path.exists(download_path): + logger_func(f"Download path '{download_path}' does not exist. Creating it...") + os.makedirs(download_path, exist_ok=True) + + with requests.get(direct_download_url, stream=True, allow_redirects=True, timeout=(10,300)) as r: + r.raise_for_status() + filename = _get_filename_from_headers(r.headers) or os.path.basename(urlparse(dropbox_link).path) or "dropbox_downloaded_file" + # Sanitize filename (basic) + filename = re.sub(r'[<>:"/\\|?*]', '_', filename) + full_save_path = os.path.join(download_path, filename) + logger_func(f"Starting Dropbox download of '{filename}' to '{full_save_path}'...") + with open(full_save_path, 'wb') as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) + logger_func(f"✅ Dropbox file downloaded successfully: {full_save_path}") + except Exception as e: + logger_func(f"❌ An error occurred during Dropbox download: {e}") + traceback.print_exc() + raise + +if __name__ == "__main__": + # --- IMPORTANT --- + # Replace this with an ACTUAL PUBLIC MEGA.NZ FILE LINK to test. + # Using the specific link you provided: + mega_file_link = "https://mega.nz/file/03oRjBQT#Tcbp5sQVIyPbdmv8sLgbb9Lf9AZvZLdKRSQiuXkNW0k" + + if not mega_file_link.startswith("https://mega.nz/file/"): + print("Invalid Mega file link format. It should start with 'https://mega.nz/file/'.") # Or use logger_func if testing outside main + else: + # Specify your desired download directory + # If you want to download to a specific folder, e.g., "MegaDownloads" in your user's Downloads folder: + # user_downloads_path = os.path.expanduser("~/Downloads") + # download_directory = os.path.join(user_downloads_path, "MegaDownloads") + + # For this example, we'll download to a "mega_downloads" subfolder in the script's directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + download_directory = os.path.join(script_dir, "mega_downloads") + + print(f"Files will be downloaded to: {download_directory}") # Or use logger_func + download_mega_file(mega_file_link, download_directory, logger_func=print) + + # Test Google Drive + # gdrive_test_link = "YOUR_PUBLIC_GOOGLE_DRIVE_FILE_LINK_HERE" # Replace with a real link + # if gdrive_test_link and gdrive_test_link != "YOUR_PUBLIC_GOOGLE_DRIVE_FILE_LINK_HERE": + # gdrive_download_dir = os.path.join(script_dir, "gdrive_downloads") + # print(f"\nGoogle Drive files will be downloaded to: {gdrive_download_dir}") + # download_gdrive_file(gdrive_test_link, gdrive_download_dir, logger_func=print) + # + # # Test Dropbox + # dropbox_test_link = "YOUR_PUBLIC_DROPBOX_FILE_LINK_HERE" # Replace with a real link + # if dropbox_test_link and dropbox_test_link != "YOUR_PUBLIC_DROPBOX_FILE_LINK_HERE": + # dropbox_download_dir = os.path.join(script_dir, "dropbox_downloads") + # print(f"\nDropbox files will be downloaded to: {dropbox_download_dir}") + # download_dropbox_file(dropbox_test_link, dropbox_download_dir, logger_func=print) diff --git a/main.py b/main.py index 70dc84a..33594e3 100644 --- a/main.py +++ b/main.py @@ -61,8 +61,9 @@ try : STYLE_DATE_BASED , STYLE_POST_TITLE_GLOBAL_NUMBERING , CREATOR_DOWNLOAD_DEFAULT_FOLDER_IGNORE_WORDS , - download_mega_file - + drive_download_mega_file, # Use the aliased import from drive.py + download_gdrive_file, + download_dropbox_file ) print ("Successfully imported names from downloader_utils.") except ImportError as e : @@ -96,7 +97,9 @@ except ImportError as e : STYLE_DATE_BASED ="date_based" STYLE_POST_TITLE_GLOBAL_NUMBERING ="post_title_global_numbering" CREATOR_DOWNLOAD_DEFAULT_FOLDER_IGNORE_WORDS =set () - def download_mega_file (*args ,**kwargs ):pass + def drive_download_mega_file(*args, **kwargs): print("drive_download_mega_file (stub)"); pass + def download_gdrive_file(*args, **kwargs): print("download_gdrive_file (stub)"); pass + def download_dropbox_file(*args, **kwargs): print("download_dropbox_file (stub)"); pass except Exception as e : print (f"--- UNEXPECTED IMPORT ERROR ---") @@ -141,29 +144,30 @@ CONFIRM_ADD_ALL_CANCEL_DOWNLOAD =3 LOG_DISPLAY_LINKS = "links" LOG_DISPLAY_DOWNLOAD_PROGRESS = "download_progress" -class DownloadMegaLinksDialog (QDialog ): - """A dialog to select and initiate download for extracted Mega links.""" +class DownloadExtractedLinksDialog(QDialog): + """A dialog to select and initiate download for extracted supported links.""" download_requested =pyqtSignal (list ) - def __init__ (self ,mega_links_data ,parent =None ): + def __init__ (self ,links_data ,parent =None ): super ().__init__ (parent ) - self .mega_links_data =mega_links_data - self .setWindowTitle ("Download Selected Mega Links") + self .links_data = links_data + self .setWindowTitle ("Download Selected External Links") self .setMinimumSize (500 ,400 ) layout =QVBoxLayout (self ) - label =QLabel (f"Found {len (self .mega_links_data )} Mega link(s). Select which ones to download:") + label =QLabel (f"Found {len (self .links_data )} supported link(s) (Mega, GDrive, Dropbox). Select to download:") label .setAlignment (Qt .AlignCenter ) label .setWordWrap (True ) layout .addWidget (label ) self .links_list_widget =QListWidget () self .links_list_widget .setSelectionMode (QAbstractItemView .NoSelection ) - for link_info in self .mega_links_data : - display_text =f"{link_info ['title']} - {link_info ['link_text']} ({link_info ['url']})" + for link_info in self .links_data : + platform_display = link_info.get('platform', 'unknown').upper() + display_text =f"[{platform_display}] {link_info ['title']} - {link_info ['link_text']} ({link_info ['url']})" item =QListWidgetItem (display_text ) item .setData (Qt .UserRole ,link_info ) item .setFlags (item .flags ()|Qt .ItemIsUserCheckable ) @@ -209,7 +213,7 @@ class DownloadMegaLinksDialog (QDialog ): self .download_requested .emit (selected_links ) self .accept () else : - QMessageBox .information (self ,"No Selection","Please select at least one Mega link to download.") + QMessageBox .information (self ,"No Selection","Please select at least one link to download.") class ConfirmAddAllDialog (QDialog ): """A dialog to confirm adding multiple new names to Known.txt.""" @@ -2280,36 +2284,59 @@ class TourDialog (QDialog ): print (f"[Tour] CRITICAL ERROR in run_tour_if_needed: {e }") return QDialog .Rejected -class MegaDownloadThread (QThread ): - """A QThread to handle downloading multiple Mega links sequentially.""" +class ExternalLinkDownloadThread(QThread): + """A QThread to handle downloading multiple external links sequentially.""" progress_signal =pyqtSignal (str ) file_complete_signal =pyqtSignal (str ,bool ) finished_signal =pyqtSignal () - def __init__ (self ,tasks_to_download ,download_base_path ,parent_logger_func ,parent =None ): + def __init__ (self ,tasks_to_download ,download_base_path ,parent_logger_func ,parent =None): super ().__init__ (parent ) self .tasks =tasks_to_download self .download_base_path =download_base_path self .parent_logger_func =parent_logger_func self .is_cancelled =False - def run (self ): - self .progress_signal .emit (f"ℹ️ Starting Mega download thread for {len (self .tasks )} link(s).") + def run(self): + self.progress_signal.emit(f"ℹ️ Starting external link download thread for {len(self.tasks)} link(s).") for i ,task_info in enumerate (self .tasks ): if self .is_cancelled : - self .progress_signal .emit ("Mega download cancelled by user.") + self.progress_signal.emit("External link download cancelled by user.") break + platform = task_info.get('platform', 'unknown').lower() full_mega_url =task_info ['url'] post_title =task_info ['title'] - self .progress_signal .emit (f"Mega Download ({i +1 }/{len (self .tasks )}): Starting '{post_title }' from {full_mega_url }") - try : + key = task_info.get('key', '') # Primarily for Mega - download_mega_file (full_mega_url ,self .download_base_path ,logger_func =self .parent_logger_func ) - self .file_complete_signal .emit (full_mega_url ,True ) + self.progress_signal.emit(f"Download ({i + 1}/{len(self.tasks)}): Starting '{post_title}' ({platform.upper()}) from {full_mega_url}") + + try : + if platform == 'mega': + # Ensure key is part of the URL for mega.py + if key: + parsed_original_url = urlparse(full_mega_url) + if key not in parsed_original_url.fragment: + base_url_no_fragment = full_mega_url.split('#')[0] + full_mega_url_with_key = f"{base_url_no_fragment}#{key}" + self.progress_signal.emit(f" Adjusted Mega URL with key: {full_mega_url_with_key}") + else: + full_mega_url_with_key = full_mega_url + else: # No key provided, use URL as is (might fail if key is required and not in fragment) + full_mega_url_with_key = full_mega_url + drive_download_mega_file(full_mega_url_with_key, self.download_base_path, logger_func=self.parent_logger_func) + elif platform == 'google drive': + download_gdrive_file(full_mega_url, self.download_base_path, logger_func=self.parent_logger_func) + elif platform == 'dropbox': + download_dropbox_file(full_mega_url, self.download_base_path, logger_func=self.parent_logger_func) + else: + self.progress_signal.emit(f"⚠️ Unsupported platform '{platform}' for link: {full_mega_url}") + self.file_complete_signal.emit(full_mega_url, False) + continue + self.file_complete_signal.emit(full_mega_url, True) except Exception as e : - self .progress_signal .emit (f"❌ Error downloading Mega link '{full_mega_url }' (from post '{post_title }'): {e }") - self .file_complete_signal .emit (full_mega_url ,False ) + self.progress_signal.emit(f"❌ Error downloading ({platform.upper()}) link '{full_mega_url}' (from post '{post_title}'): {e}") + self.file_complete_signal.emit(full_mega_url, False) self .finished_signal .emit () def cancel (self ): @@ -2350,7 +2377,7 @@ class DownloaderApp (QWidget ): self .download_thread =None self .thread_pool =None self .cancellation_event =threading .Event () - self .mega_download_thread =None + self .external_link_download_thread =None self .pause_event =threading .Event () self .active_futures =[] self .total_posts_to_process =0 @@ -3435,31 +3462,33 @@ class DownloaderApp (QWidget ): self .log_signal .emit ("ℹ️ Download extracted links button clicked, but not in 'Only Links' mode.") return - mega_links_to_show =[] + supported_platforms = {'mega', 'google drive', 'dropbox'} + links_to_show_in_dialog =[] for link_data_tuple in self .extracted_links_cache : - - if link_data_tuple [3 ]=='mega': - mega_links_to_show .append ({ + platform = link_data_tuple[3].lower() + if platform in supported_platforms: + links_to_show_in_dialog.append ({ 'title':link_data_tuple [0 ], 'link_text':link_data_tuple [1 ], 'url':link_data_tuple [2 ], - 'platform':link_data_tuple [3 ], + 'platform':platform, 'key':link_data_tuple [4 ] }) - if not mega_links_to_show : - QMessageBox .information (self ,"No Mega Links","No Mega links were found in the extracted links.") + if not links_to_show_in_dialog : + QMessageBox .information (self ,"No Supported Links","No Mega, Google Drive, or Dropbox links were found in the extracted links.") return - dialog =DownloadMegaLinksDialog (mega_links_to_show ,self ) - dialog .download_requested .connect (self ._handle_mega_links_download_request ) + dialog = DownloadExtractedLinksDialog(links_to_show_in_dialog, self) + dialog .download_requested .connect (self ._handle_extracted_links_download_request ) dialog .exec_ () - def _handle_mega_links_download_request (self ,selected_links_info ): + def _handle_extracted_links_download_request(self, selected_links_info): if not selected_links_info : - self .log_signal .emit ("ℹ️ No Mega links selected for download from dialog.") + self.log_signal.emit("ℹ️ No links selected for download from dialog.") return + # Preserve log logic (might need adjustment if GDrive/Dropbox have different log styles) if self.radio_only_links and self.radio_only_links.isChecked() and \ self.only_links_log_display_mode == LOG_DISPLAY_DOWNLOAD_PROGRESS: self.main_log_output.clear() @@ -3470,15 +3499,14 @@ class DownloaderApp (QWidget ): download_dir_for_mega ="" if current_main_dir and os .path .isdir (current_main_dir ): - download_dir_for_mega =current_main_dir - self .log_signal .emit (f"ℹ️ Using existing main download location for Mega links: {download_dir_for_mega }") + self.log_signal.emit(f"ℹ️ Using existing main download location for external links: {download_dir_for_mega}") else : - if not current_main_dir : - self .log_signal .emit ("ℹ️ Main download location is empty. Prompting for Mega download folder.") + self.log_signal.emit("ℹ️ Main download location is empty. Prompting for download folder.") else : - self .log_signal .emit (f"⚠️ Main download location '{current_main_dir }' is not a valid directory. Prompting for Mega download folder.") + self.log_signal.emit( + f"⚠️ Main download location '{current_main_dir}' is not a valid directory. Prompting for download folder.") suggestion_path =current_main_dir if current_main_dir else QStandardPaths .writableLocation (QStandardPaths .DownloadLocation ) @@ -3486,53 +3514,48 @@ class DownloaderApp (QWidget ): chosen_dir =QFileDialog .getExistingDirectory ( self , "Select Download Folder for Mega Links", - suggestion_path + suggestion_path, + options=QFileDialog.ShowDirsOnly | QFileDialog.DontUseNativeDialog # Added DontUseNativeDialog for consistency ) if not chosen_dir : - self .log_signal .emit ("ℹ️ Mega links download cancelled - no download directory selected from prompt.") + self.log_signal.emit("ℹ️ External links download cancelled - no download directory selected from prompt.") return download_dir_for_mega =chosen_dir - - self .log_signal .emit (f"ℹ️ Preparing to download {len (selected_links_info )} selected Mega link(s) to: {download_dir_for_mega }") + self.log_signal.emit(f"ℹ️ Preparing to download {len(selected_links_info)} selected external link(s) to: {download_dir_for_mega}") if not os .path .exists (download_dir_for_mega ): - self .log_signal .emit (f"❌ Critical Error: Selected Mega download directory '{download_dir_for_mega }' does not exist.") + self.log_signal.emit(f"❌ Critical Error: Selected download directory '{download_dir_for_mega}' does not exist.") return - tasks_for_thread =[] - for item in selected_links_info : - full_url =item ['url'] - key =item ['key'] + # selected_links_info already contains dicts with 'url', 'title', 'platform', 'key' + tasks_for_thread = selected_links_info - if key : - url_parts =urlparse (full_url ) - if key not in url_parts .fragment : - if url_parts .fragment : - base_url_no_fragment =full_url .split ('#')[0 ] - full_url =f"{base_url_no_fragment }#{key }" - else : - full_url =f"{full_url }#{key }" + if self.external_link_download_thread and self.external_link_download_thread.isRunning(): + QMessageBox.warning(self, "Busy", "Another external link download is already in progress.") + return - tasks_for_thread .append ({'url':full_url ,'title':item ['title']}) - - if self .mega_download_thread and self .mega_download_thread .isRunning (): - QMessageBox .warning (self ,"Busy","Another Mega download is already in progress.") - return - - self .mega_download_thread =MegaDownloadThread (tasks_for_thread ,download_dir_for_mega ,self .log_signal .emit ,self ) - self .mega_download_thread .finished .connect (self ._on_mega_download_thread_finished ) + self.external_link_download_thread = ExternalLinkDownloadThread( + tasks_for_thread, + download_dir_for_mega, + self.log_signal.emit, + self + ) + self.external_link_download_thread.finished.connect(self._on_external_link_download_thread_finished) + # Connect progress_signal if ExternalLinkDownloadThread has it (it does) + self.external_link_download_thread.progress_signal.connect(self.handle_main_log) + self.external_link_download_thread.file_complete_signal.connect(self._on_single_external_file_complete) self .set_ui_enabled (False ) - self .progress_label .setText (f"Downloading Mega Links (0/{len (tasks_for_thread )})...") - self .mega_download_thread .start () + self.progress_label.setText(f"Downloading External Links (0/{len(tasks_for_thread)})...") + self.external_link_download_thread.start() - def _on_mega_download_thread_finished (self ): - self .log_signal .emit ("✅ Mega download thread finished.") - self .progress_label .setText ("Mega downloads complete. Ready for new task.") + def _on_external_link_download_thread_finished(self): + self.log_signal.emit("✅ External link download thread finished.") + self.progress_label.setText("External link downloads complete. Ready for new task.") self.mega_download_log_preserved_once = True # Mark that a mega download just finished self.log_signal.emit("INTERNAL: mega_download_log_preserved_once SET to True.") # Debug @@ -3550,10 +3573,14 @@ class DownloaderApp (QWidget ): self.mega_download_log_preserved_once = False self.log_signal.emit("INTERNAL: mega_download_log_preserved_once RESET to False.") # Debug - if self .mega_download_thread : - self .mega_download_thread .deleteLater () - self .mega_download_thread =None + if self.external_link_download_thread: + self.external_link_download_thread.deleteLater() + self.external_link_download_thread = None + def _on_single_external_file_complete(self, url, success): + # This is a new slot to potentially update progress if needed per file + # For now, the thread's own progress_signal handles detailed logging. + pass def _show_future_settings_dialog (self ): """Shows the placeholder dialog for future settings.""" dialog =FutureSettingsDialog (self ) @@ -5660,7 +5687,7 @@ class DownloaderApp (QWidget ): if self .cancellation_event .is_set (): self .log_signal .emit (" Cancellation detected after/during task submission loop.") - if self .mega_download_thread and self .mega_download_thread .isRunning (): + if self .external_link_download_thread and self .external_link_download_thread .isRunning (): self .mega_download_thread .cancel () @@ -5750,9 +5777,9 @@ class DownloaderApp (QWidget ): if self .bottom_action_buttons_stack : self .bottom_action_buttons_stack .setCurrentIndex (0 ) - if self .mega_download_thread and self .mega_download_thread .isRunning (): + if self .external_link_download_thread and self .external_link_download_thread .isRunning (): self .log_signal .emit ("ℹ️ Cancelling active Mega download due to UI state change.") - self .mega_download_thread .cancel () + self .external_link_download_thread .cancel () else : pass @@ -5938,9 +5965,9 @@ class DownloaderApp (QWidget ): self .log_signal .emit ("⚠️ Requesting cancellation of download process (soft reset)...") if self .mega_download_thread and self .mega_download_thread .isRunning (): - self .log_signal .emit (" Cancelling active Mega download thread...") - self .mega_download_thread .cancel () - + self .log_signal .emit (" Cancelling active External Link download thread...") + self .external_link_download_thread .cancel () + current_url =self .link_input .text () current_dir =self .dir_input .text () @@ -6220,9 +6247,9 @@ class DownloaderApp (QWidget ): self .retry_thread_pool .shutdown (wait =True ) self .retry_thread_pool =None - if self .mega_download_thread and not self .mega_download_thread .isRunning (): - self .mega_download_thread .deleteLater () - self .mega_download_thread =None + if self.external_link_download_thread and not self.external_link_download_thread.isRunning(): + self.external_link_download_thread.deleteLater() + self.external_link_download_thread = None self .active_retry_futures .clear () self .active_retry_futures_map .clear ()