From c8886d69edbaf3786f19ebf60cf84975f3b2fa23 Mon Sep 17 00:00:00 2001 From: sevi-kun Date: Tue, 18 Mar 2025 00:05:09 +0100 Subject: [PATCH] Adding sqlite and splitting into two files --- README.md | 3 +- src/main.py | 307 ---------------------------------------------------- 2 files changed, 2 insertions(+), 308 deletions(-) delete mode 100644 src/main.py diff --git a/README.md b/README.md index 06b65fa..79626da 100644 --- a/README.md +++ b/README.md @@ -15,5 +15,6 @@ There is a simple web UI to add the doujinshi IDs to the queue. - [ ] Add file name normalizer (for kavita) - [ ] Add file mover (organizer) - [ ] Add config file -- [ ] Add Database integration (sqlite from nhentail cli tool) +- [x] Add Database integration (sqlite from nhentail cli tool) - [ ] Add Dockerfile +- [ ] Better error handeling (Show in UI) diff --git a/src/main.py b/src/main.py deleted file mode 100644 index 4ec3327..0000000 --- a/src/main.py +++ /dev/null @@ -1,307 +0,0 @@ -from nicegui import ui, app -import time -import subprocess -import os -import threading -from collections import defaultdict - -ui.dark_mode().enable() - -# Configuration -DOWNLOAD_DIR = os.path.expanduser("~/Downloads/doujinshi") -OUTPUT_FORMAT = "[%i]%s" # Default format for folder naming -COOKIE = "" # For bypassing Cloudflare captcha -USER_AGENT = "" # For bypassing Cloudflare captcha -HTML_VIEWER = False # Generate HTML viewer -GENERATE_CBZ = True # Generate Comic Book Archive -GENERATE_PDF = False # Generate PDF file -THREAD_COUNT = 5 # Thread count for downloading -TIMEOUT = 30 # Timeout in seconds -RETRY_COUNT = 3 # Retry times when download fails - -# Global dictionary to track download statuses -download_statuses = {} - -def add_nhentai_id(): - id_value = id_input.value - if not id_value: - ui.notify('Please enter an ID', color='warning') - return - - # Clear the input field after submission - id_input.value = '' - - # Show notification - ui.notify(f'Adding nhentai ID: {id_value}') - - # Create a new history item - with history_container: - item_row = ui.row().classes('w-full items-center') - with item_row: - status = ui.icon('circle').classes('text-yellow-500') # Initial "processing" status - ui.label(f'ID: {id_value}').classes('text-lg ml-2') - timestamp = ui.label(f'Added: {time.strftime("%H:%M:%S")}').classes('text-sm text-gray-500 ml-auto') - - # Add buttons for actions - download_btn = ui.button('Download', - on_click=lambda: start_download(id_value, status), - color='primary').classes('ml-2').props('size=sm') - -def update_status(status_icon, id_value, success=True): - if success: - status_icon.name = 'check_circle' - status_icon.classes('text-green-500', remove='text-yellow-500 text-red-500 text-blue-500') - else: - status_icon.name = 'error' - status_icon.classes('text-red-500', remove='text-yellow-500 text-green-500 text-blue-500') - ui.notify(f'ID {id_value} marked as failed', color='negative') - -def download_doujinshi(id_value, status_icon): - # This function runs in a background thread - global download_statuses - - try: - # Try to check if nhentai CLI is installed - try: - subprocess.run(["nhentai", "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) - except (subprocess.SubprocessError, FileNotFoundError): - download_statuses[id_value] = { - 'status': 'error', - 'message': 'nhentai CLI tool is not installed' - } - return - - # Create download directory if it doesn't exist - os.makedirs(DOWNLOAD_DIR, exist_ok=True) - - # Build command with all options - cmd = ["nhentai"] - - # Add the ID - cmd.extend(["--id", str(id_value)]) - - # Add output directory - cmd.extend(["--output", DOWNLOAD_DIR]) - - # Add format option - cmd.extend(["--format", OUTPUT_FORMAT]) - - # Add thread count - if THREAD_COUNT > 0: - cmd.extend(["--threads", str(THREAD_COUNT)]) - - # Add timeout - if TIMEOUT > 0: - cmd.extend(["--timeout", str(TIMEOUT)]) - - # Add retry count - if RETRY_COUNT > 0: - cmd.extend(["--retry", str(RETRY_COUNT)]) - - # Add exit-on-fail option to prevent incomplete downloads - cmd.append("--exit-on-fail") - - # Add HTML viewer option - if HTML_VIEWER: - cmd.append("--html") - else: - cmd.append("--no-html") - - # Generate CBZ if selected - if GENERATE_CBZ: - cmd.append("--cbz") - - # Generate PDF if selected - if GENERATE_PDF: - cmd.append("--pdf") - - # Add cookie if provided - if COOKIE: - cmd.extend(["--cookie", COOKIE]) - - # Add user agent if provided - if USER_AGENT: - cmd.extend(["--useragent", USER_AGENT]) - - # Save download history - cmd.append("--save-download-history") - - # For debugging: print the command (excluding sensitive info) - debug_cmd = list(cmd) - if COOKIE: - cookie_index = debug_cmd.index("--cookie") - if len(debug_cmd) > cookie_index + 1: - debug_cmd[cookie_index + 1] = "***COOKIE***" - - if USER_AGENT: - ua_index = debug_cmd.index("--useragent") - if len(debug_cmd) > ua_index + 1: - debug_cmd[ua_index + 1] = "***USER-AGENT***" - - print(f"Executing: {' '.join(debug_cmd)}") - - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True - ) - stdout, stderr = process.communicate() - - # Use the global dict to track status - if process.returncode == 0: - # Success - download_statuses[id_value] = { - 'status': 'success', - 'message': 'Download completed' - } - print(f"Download successful for ID {id_value}") - print(f"STDOUT: {stdout}") - else: - # Error - download_statuses[id_value] = { - 'status': 'error', - 'message': f'Download failed: {stderr}' - } - print(f"Download failed for ID {id_value}") - print(f"STDERR: {stderr}") - except Exception as e: - # Error handling - download_statuses[id_value] = { - 'status': 'error', - 'message': f'Error: {str(e)}' - } - print(f"Exception during download for ID {id_value}: {str(e)}") - -def start_download(id_value, status_icon): - # Update status to "downloading" - status_icon.name = 'downloading' - status_icon.classes('text-blue-500', remove='text-yellow-500 text-green-500 text-red-500') - ui.notify(f'Starting download for ID: {id_value}', color='info') - - # Initialize download status - global download_statuses - download_statuses[id_value] = {'status': 'downloading'} - - # Start download in a background thread - threading.Thread(target=download_doujinshi, args=(id_value, status_icon), daemon=True).start() - - # Set up a periodic task to check for download completion - check_download_status(id_value, status_icon) - -def check_download_status(id_value, status_icon): - """Check if the download has completed and update the UI accordingly""" - global download_statuses - - if id_value in download_statuses: - result = download_statuses[id_value] - if result.get('status') == 'success': - ui.notify(f'Download completed for ID: {id_value}', color='positive') - update_status(status_icon, id_value, True) - # Remove from tracking to avoid duplicate notifications - del download_statuses[id_value] - elif result.get('status') == 'error': - ui.notify(f'Download failed for ID {id_value}: {result.get("message", "Unknown error")}', color='negative') - update_status(status_icon, id_value, False) - # Remove from tracking to avoid duplicate notifications - del download_statuses[id_value] - else: - # Still processing, check again in a second - ui.timer(1.0, lambda: check_download_status(id_value, status_icon), once=True) - else: - # No result yet, check again in a second - ui.timer(1.0, lambda: check_download_status(id_value, status_icon), once=True) - -# Add advanced settings dialog -def show_settings(): - with ui.dialog() as dialog, ui.card().classes('w-96'): - ui.label('Download Settings').classes('text-xl font-bold') - - global DOWNLOAD_DIR, OUTPUT_FORMAT, COOKIE, USER_AGENT, HTML_VIEWER, GENERATE_CBZ, GENERATE_PDF, THREAD_COUNT, TIMEOUT, RETRY_COUNT - - # Basic Settings - ui.label('Basic Settings').classes('text-lg font-semibold mt-4') - download_dir_input = ui.input('Download Directory', value=DOWNLOAD_DIR).classes('w-full') - output_format_input = ui.input('Output Format', value=OUTPUT_FORMAT, - placeholder='e.g. [%i]%s').classes('w-full') - ui.label('Supported: %i (ID), %s (subtitle), %t (title), %a (authors), %g (groups), %p (pretty name)').classes('text-xs text-gray-500') - - # Advanced Settings - ui.label('Advanced Settings').classes('text-lg font-semibold mt-4') - thread_count_input = ui.number('Thread Count', value=THREAD_COUNT, min=1, max=20) - timeout_input = ui.number('Timeout (seconds)', value=TIMEOUT, min=5, max=120) - retry_count_input = ui.number('Retry Count', value=RETRY_COUNT, min=0, max=10) - - # Output Options - ui.label('Output Options').classes('text-lg font-semibold mt-4') - html_viewer_toggle = ui.switch('Generate HTML Viewer', value=HTML_VIEWER) - cbz_toggle = ui.switch('Generate CBZ File', value=GENERATE_CBZ) - pdf_toggle = ui.switch('Generate PDF File', value=GENERATE_PDF) - - # Authentication (for bypassing Cloudflare) - ui.label('Authentication (to bypass Cloudflare)').classes('text-lg font-semibold mt-4') - cookie_input = ui.input('Cookie', value=COOKIE, - placeholder='csrftoken=TOKEN; sessionid=ID; cf_clearance=CLOUDFLARE').classes('w-full') - useragent_input = ui.input('User Agent', value=USER_AGENT, - placeholder='Mozilla/5.0 ...').classes('w-full') - - with ui.row(): - ui.button('Cancel', on_click=dialog.close).props('outline') - ui.button('Save', on_click=lambda: save_settings( - download_dir_input.value, - output_format_input.value, - cookie_input.value, - useragent_input.value, - html_viewer_toggle.value, - cbz_toggle.value, - pdf_toggle.value, - thread_count_input.value, - timeout_input.value, - retry_count_input.value, - dialog - )) - dialog.open() - -def save_settings(download_dir, output_format, cookie, useragent, - html_viewer, generate_cbz, generate_pdf, thread_count, timeout, retry_count, dialog): - global DOWNLOAD_DIR, OUTPUT_FORMAT, COOKIE, USER_AGENT, HTML_VIEWER, GENERATE_CBZ, GENERATE_PDF, THREAD_COUNT, TIMEOUT, RETRY_COUNT - - DOWNLOAD_DIR = download_dir - OUTPUT_FORMAT = output_format - COOKIE = cookie - USER_AGENT = useragent - HTML_VIEWER = html_viewer - GENERATE_CBZ = generate_cbz - GENERATE_PDF = generate_pdf - THREAD_COUNT = thread_count - TIMEOUT = timeout - RETRY_COUNT = retry_count - - ui.notify('Settings saved', color='positive') - dialog.close() - -# UI Layout -with ui.header().classes('bg-primary'): - ui.label('nhentai Downloader').classes('text-xl font-bold') - ui.space() - ui.button('Settings', on_click=show_settings).props('icon=settings flat') - -# Input and button row -with ui.row().classes('w-full items-center mt-4'): - id_input = ui.input(label='ID:').classes('mr-2') - ui.button('Add ID!', on_click=add_nhentai_id).props('icon=add') - -# History section -ui.label('History').classes('text-xl font-bold mt-4 mb-2') -history_container = ui.column().classes('w-full border rounded-lg p-2') - -# Add some instructions initially -with history_container: - ui.label('Enter an ID and click "Add ID!" to see the history').classes('text-gray-500 italic') - -# Add footer with help info -with ui.footer().classes('bg-gray-100 dark:bg-gray-800 text-sm'): - ui.label('Using nhentai CLI tool. Need help? Check the GitHub page:') - ui.link('https://github.com/RicterZ/nhentai', 'https://github.com/RicterZ/nhentai').classes('ml-1') - -ui.run()