dfdkflj
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Modal for displaying files/URLs to access in web mode."""
|
||||
"""Modal for displaying files/url to access in web mode."""
|
||||
|
||||
from textual.screen import ModalScreen
|
||||
from textual.containers import Container, Vertical, Horizontal
|
||||
@@ -93,7 +93,7 @@ class AccessModal(ModalScreen):
|
||||
yield Label("[bold cyan]File:[/bold cyan]", classes="access-label")
|
||||
|
||||
# Display as clickable link using HTML link element for web mode
|
||||
# Rich link markup `[link=URL]` has parsing issues with URLs containing special chars
|
||||
# Rich link markup `[link=URL]` has parsing issues with url containing special chars
|
||||
# Instead, use the HTML link markup that Textual-serve renders as <a> tag
|
||||
# Format: [link=URL "tooltip"]text[/link] - the quotes help with parsing
|
||||
link_text = f'[link="{self.item_content}"]Open in Browser[/link]'
|
||||
|
||||
@@ -233,8 +233,8 @@ class DownloadModal(ModalScreen):
|
||||
self.screenshot_checkbox.value = False
|
||||
self.playlist_merge_checkbox.value = False
|
||||
|
||||
# Initialize PDF playlist URLs (set by _handle_pdf_playlist)
|
||||
self.pdf_urls = []
|
||||
# Initialize PDF playlist url (set by _handle_pdf_playlist)
|
||||
self.pdf_url = []
|
||||
self.is_pdf_playlist = False
|
||||
|
||||
# Hide playlist by default (show format select)
|
||||
@@ -288,10 +288,10 @@ class DownloadModal(ModalScreen):
|
||||
|
||||
# Launch the background worker with PDF playlist info
|
||||
self._submit_worker(url, tags, source, download_enabled, playlist_selection, merge_enabled,
|
||||
is_pdf_playlist=self.is_pdf_playlist, pdf_urls=self.pdf_urls if self.is_pdf_playlist else [])
|
||||
is_pdf_playlist=self.is_pdf_playlist, pdf_url=self.pdf_url if self.is_pdf_playlist else [])
|
||||
|
||||
@work(thread=True)
|
||||
def _submit_worker(self, url: str, tags: list, source: str, download_enabled: bool, playlist_selection: str = "", merge_enabled: bool = False, is_pdf_playlist: bool = False, pdf_urls: Optional[list] = None) -> None:
|
||||
def _submit_worker(self, url: str, tags: list, source: str, download_enabled: bool, playlist_selection: str = "", merge_enabled: bool = False, is_pdf_playlist: bool = False, pdf_url: Optional[list] = None) -> None:
|
||||
"""Background worker to execute the cmdlet pipeline.
|
||||
|
||||
Args:
|
||||
@@ -302,10 +302,10 @@ class DownloadModal(ModalScreen):
|
||||
playlist_selection: Playlist track selection (e.g., "1-3", "all", "merge")
|
||||
merge_enabled: Whether to merge playlist files after download
|
||||
is_pdf_playlist: Whether this is a PDF pseudo-playlist
|
||||
pdf_urls: List of PDF URLs if is_pdf_playlist is True
|
||||
pdf_url: List of PDF url if is_pdf_playlist is True
|
||||
"""
|
||||
if pdf_urls is None:
|
||||
pdf_urls = []
|
||||
if pdf_url is None:
|
||||
pdf_url = []
|
||||
|
||||
# Initialize worker to None so outer exception handler can check it
|
||||
worker = None
|
||||
@@ -340,9 +340,9 @@ class DownloadModal(ModalScreen):
|
||||
worker.log_step("Download initiated")
|
||||
|
||||
# Handle PDF playlist specially
|
||||
if is_pdf_playlist and pdf_urls:
|
||||
logger.info(f"Processing PDF playlist with {len(pdf_urls)} PDFs")
|
||||
self._handle_pdf_playlist_download(pdf_urls, tags, playlist_selection, merge_enabled)
|
||||
if is_pdf_playlist and pdf_url:
|
||||
logger.info(f"Processing PDF playlist with {len(pdf_url)} PDFs")
|
||||
self._handle_pdf_playlist_download(pdf_url, tags, playlist_selection, merge_enabled)
|
||||
self.app.call_from_thread(self._hide_progress)
|
||||
self.app.call_from_thread(self.dismiss)
|
||||
return
|
||||
@@ -690,7 +690,7 @@ class DownloadModal(ModalScreen):
|
||||
'media_kind': 'audio',
|
||||
'hash_hex': None,
|
||||
'hash': None,
|
||||
'known_urls': [],
|
||||
'url': [],
|
||||
'title': filepath_obj.stem
|
||||
})()
|
||||
files_to_merge.append(file_result)
|
||||
@@ -934,8 +934,8 @@ class DownloadModal(ModalScreen):
|
||||
"""Scrape metadata from URL(s) in URL textarea - wipes tags and source.
|
||||
|
||||
This is triggered by Ctrl+T when URL textarea is focused.
|
||||
Supports single URL or multiple URLs (newline/comma-separated).
|
||||
For multiple PDF URLs, creates pseudo-playlist for merge workflow.
|
||||
Supports single URL or multiple url (newline/comma-separated).
|
||||
For multiple PDF url, creates pseudo-playlist for merge workflow.
|
||||
"""
|
||||
try:
|
||||
text = self.paragraph_textarea.text.strip()
|
||||
@@ -943,29 +943,29 @@ class DownloadModal(ModalScreen):
|
||||
logger.warning("No URL to scrape metadata from")
|
||||
return
|
||||
|
||||
# Parse multiple URLs (newline or comma-separated)
|
||||
urls = []
|
||||
# Parse multiple url (newline or comma-separated)
|
||||
url = []
|
||||
for line in text.split('\n'):
|
||||
line = line.strip()
|
||||
if line:
|
||||
# Handle comma-separated URLs within a line
|
||||
# Handle comma-separated url within a line
|
||||
for url in line.split(','):
|
||||
url = url.strip()
|
||||
if url:
|
||||
urls.append(url)
|
||||
url.append(url)
|
||||
|
||||
# Check if multiple URLs provided
|
||||
if len(urls) > 1:
|
||||
logger.info(f"Detected {len(urls)} URLs - checking for PDF pseudo-playlist")
|
||||
# Check if all URLs appear to be PDFs
|
||||
all_pdfs = all(url.endswith('.pdf') or 'pdf' in url.lower() for url in urls)
|
||||
# Check if multiple url provided
|
||||
if len(url) > 1:
|
||||
logger.info(f"Detected {len(url)} url - checking for PDF pseudo-playlist")
|
||||
# Check if all url appear to be PDFs
|
||||
all_pdfs = all(url.endswith('.pdf') or 'pdf' in url.lower() for url in url)
|
||||
if all_pdfs:
|
||||
logger.info(f"All URLs are PDFs - creating pseudo-playlist")
|
||||
self._handle_pdf_playlist(urls)
|
||||
logger.info(f"All url are PDFs - creating pseudo-playlist")
|
||||
self._handle_pdf_playlist(url)
|
||||
return
|
||||
|
||||
# Single URL - proceed with normal metadata scraping
|
||||
url = urls[0] if urls else text.strip()
|
||||
url = url[0] if url else text.strip()
|
||||
logger.info(f"Scraping fresh metadata from: {url}")
|
||||
|
||||
# Check if tags are already provided in textarea
|
||||
@@ -1044,21 +1044,21 @@ class DownloadModal(ModalScreen):
|
||||
)
|
||||
|
||||
|
||||
def _handle_pdf_playlist(self, pdf_urls: list) -> None:
|
||||
"""Handle multiple PDF URLs as a pseudo-playlist.
|
||||
def _handle_pdf_playlist(self, pdf_url: list) -> None:
|
||||
"""Handle multiple PDF url as a pseudo-playlist.
|
||||
|
||||
Creates a playlist-like structure with PDF metadata for merge workflow.
|
||||
Extracts title from URL or uses default naming.
|
||||
|
||||
Args:
|
||||
pdf_urls: List of PDF URLs to process
|
||||
pdf_url: List of PDF url to process
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Creating PDF pseudo-playlist with {len(pdf_urls)} items")
|
||||
logger.info(f"Creating PDF pseudo-playlist with {len(pdf_url)} items")
|
||||
|
||||
# Create playlist items from PDF URLs
|
||||
# Create playlist items from PDF url
|
||||
playlist_items = []
|
||||
for idx, url in enumerate(pdf_urls, 1):
|
||||
for idx, url in enumerate(pdf_url, 1):
|
||||
# Extract filename from URL for display
|
||||
try:
|
||||
# Get filename from URL path
|
||||
@@ -1083,15 +1083,15 @@ class DownloadModal(ModalScreen):
|
||||
|
||||
# Build minimal metadata structure for UI population
|
||||
metadata = {
|
||||
'title': f'{len(pdf_urls)} PDF Documents',
|
||||
'title': f'{len(pdf_url)} PDF Documents',
|
||||
'tags': [],
|
||||
'formats': [('pdf', 'pdf')], # Default format is PDF
|
||||
'playlist_items': playlist_items,
|
||||
'is_pdf_playlist': True # Mark as PDF pseudo-playlist
|
||||
}
|
||||
|
||||
# Store URLs for later use during merge
|
||||
self.pdf_urls = pdf_urls
|
||||
# Store url for later use during merge
|
||||
self.pdf_url = pdf_url
|
||||
self.is_pdf_playlist = True
|
||||
|
||||
# Populate the modal with metadata
|
||||
@@ -1099,7 +1099,7 @@ class DownloadModal(ModalScreen):
|
||||
self._populate_from_metadata(metadata, wipe_tags_and_source=True)
|
||||
|
||||
self.app.notify(
|
||||
f"Loaded {len(pdf_urls)} PDFs as playlist",
|
||||
f"Loaded {len(pdf_url)} PDFs as playlist",
|
||||
title="PDF Playlist",
|
||||
severity="information",
|
||||
timeout=3
|
||||
@@ -1115,11 +1115,11 @@ class DownloadModal(ModalScreen):
|
||||
)
|
||||
|
||||
|
||||
def _handle_pdf_playlist_download(self, pdf_urls: list, tags: list, selection: str, merge_enabled: bool) -> None:
|
||||
def _handle_pdf_playlist_download(self, pdf_url: list, tags: list, selection: str, merge_enabled: bool) -> None:
|
||||
"""Download and merge PDF playlist.
|
||||
|
||||
Args:
|
||||
pdf_urls: List of PDF URLs to download
|
||||
pdf_url: List of PDF url to download
|
||||
tags: Tags to apply to the merged PDF
|
||||
selection: Selection string like "1-3" or "1,3,5"
|
||||
merge_enabled: Whether to merge the PDFs
|
||||
@@ -1141,7 +1141,7 @@ class DownloadModal(ModalScreen):
|
||||
# Create temporary list of playlist items for selection parsing
|
||||
# We need this because _parse_playlist_selection uses self.playlist_items
|
||||
temp_items = []
|
||||
for url in pdf_urls:
|
||||
for url in pdf_url:
|
||||
temp_items.append({'title': url})
|
||||
self.playlist_items = temp_items
|
||||
|
||||
@@ -1149,20 +1149,20 @@ class DownloadModal(ModalScreen):
|
||||
selected_indices = self._parse_playlist_selection(selection)
|
||||
if not selected_indices:
|
||||
# No valid selection, use all
|
||||
selected_indices = list(range(len(pdf_urls)))
|
||||
selected_indices = list(range(len(pdf_url)))
|
||||
|
||||
selected_urls = [pdf_urls[i] for i in selected_indices]
|
||||
selected_url = [pdf_url[i] for i in selected_indices]
|
||||
|
||||
logger.info(f"Downloading {len(selected_urls)} selected PDFs for merge")
|
||||
logger.info(f"Downloading {len(selected_url)} selected PDFs for merge")
|
||||
|
||||
# Download PDFs to temporary directory
|
||||
temp_dir = Path.home() / ".downlow_temp_pdfs"
|
||||
temp_dir.mkdir(exist_ok=True)
|
||||
|
||||
downloaded_files = []
|
||||
for idx, url in enumerate(selected_urls, 1):
|
||||
for idx, url in enumerate(selected_url, 1):
|
||||
try:
|
||||
logger.info(f"Downloading PDF {idx}/{len(selected_urls)}: {url}")
|
||||
logger.info(f"Downloading PDF {idx}/{len(selected_url)}: {url}")
|
||||
|
||||
response = requests.get(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
@@ -1619,7 +1619,7 @@ class DownloadModal(ModalScreen):
|
||||
)
|
||||
return
|
||||
else:
|
||||
success_msg = "✅ download-data completed successfully"
|
||||
success_msg = "download-data completed successfully"
|
||||
logger.info(success_msg)
|
||||
if worker:
|
||||
worker.append_stdout(f"{success_msg}\n")
|
||||
@@ -1670,7 +1670,7 @@ class DownloadModal(ModalScreen):
|
||||
worker.append_stdout(f"{warning_msg}\n")
|
||||
else:
|
||||
if worker:
|
||||
worker.append_stdout("✅ Tags applied successfully\n")
|
||||
worker.append_stdout("Tags applied successfully\n")
|
||||
except Exception as e:
|
||||
error_msg = f"❌ Tagging error: {e}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
@@ -1684,7 +1684,7 @@ class DownloadModal(ModalScreen):
|
||||
worker.append_stdout(f"{warning_msg}\n")
|
||||
else:
|
||||
if worker:
|
||||
worker.append_stdout("✅ Download complete (no tags to apply)\n")
|
||||
worker.append_stdout("Download complete (no tags to apply)\n")
|
||||
|
||||
def _show_format_select(self) -> None:
|
||||
"""Show format select (always visible for single files)."""
|
||||
@@ -1770,9 +1770,9 @@ class DownloadModal(ModalScreen):
|
||||
# Namespaces to exclude (metadata-only, not user-facing)
|
||||
excluded_namespaces = {
|
||||
'hash', # Hash values (internal)
|
||||
'known_url', # URLs (internal)
|
||||
'url', # url (internal)
|
||||
'relationship', # Internal relationships
|
||||
'url', # URLs (internal)
|
||||
'url', # url (internal)
|
||||
}
|
||||
|
||||
# Add all other tags
|
||||
|
||||
@@ -350,9 +350,9 @@ class ExportModal(ModalScreen):
|
||||
if tag:
|
||||
export_tags.add(tag)
|
||||
|
||||
# For Hydrus export, filter out metadata-only tags (hash:, known_url:, relationship:)
|
||||
# For Hydrus export, filter out metadata-only tags (hash:, url:, relationship:)
|
||||
if export_to == "libraries" and library == "hydrus":
|
||||
metadata_prefixes = {'hash:', 'known_url:', 'relationship:'}
|
||||
metadata_prefixes = {'hash:', 'url:', 'relationship:'}
|
||||
export_tags = {tag for tag in export_tags if not any(tag.lower().startswith(prefix) for prefix in metadata_prefixes)}
|
||||
logger.info(f"Filtered tags for Hydrus - removed metadata tags, {len(export_tags)} tags remaining")
|
||||
|
||||
@@ -404,9 +404,9 @@ class ExportModal(ModalScreen):
|
||||
metadata = self.result_data.get('metadata', {})
|
||||
|
||||
# Extract file source info from result_data (passed by hub-ui)
|
||||
file_hash = self.result_data.get('file_hash')
|
||||
file_url = self.result_data.get('file_url')
|
||||
file_path = self.result_data.get('file_path') # For local files
|
||||
file_hash = self.result_data.get('hash') or self.result_data.get('file_hash')
|
||||
file_url = self.result_data.get('url') or self.result_data.get('file_url')
|
||||
file_path = self.result_data.get('path') or self.result_data.get('file_path') # For local files
|
||||
source = self.result_data.get('source', 'unknown')
|
||||
|
||||
# Prepare export data
|
||||
@@ -419,8 +419,11 @@ class ExportModal(ModalScreen):
|
||||
'format': file_format,
|
||||
'metadata': metadata,
|
||||
'original_data': self.result_data,
|
||||
'hash': file_hash,
|
||||
'file_hash': file_hash,
|
||||
'url': file_url,
|
||||
'file_url': file_url,
|
||||
'path': file_path,
|
||||
'file_path': file_path, # Pass file path for local files
|
||||
'source': source,
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import asyncio
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
from config import load_config
|
||||
from result_table import ResultTable
|
||||
from helper.search_provider import get_provider
|
||||
from helper.provider import get_provider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -183,7 +183,7 @@ class SearchModal(ModalScreen):
|
||||
else:
|
||||
# Fallback if no columns defined
|
||||
row.add_column("Title", res.title)
|
||||
row.add_column("Target", res.target)
|
||||
row.add_column("Target", getattr(res, 'path', None) or getattr(res, 'url', None) or getattr(res, 'target', None) or '')
|
||||
|
||||
self.current_result_table = table
|
||||
|
||||
|
||||
@@ -197,8 +197,6 @@ class PipelineExecutor:
|
||||
|
||||
pipeline_ctx = ctx.PipelineStageContext(stage_index=index, total_stages=total)
|
||||
ctx.set_stage_context(pipeline_ctx)
|
||||
ctx.set_active(True)
|
||||
ctx.set_last_stage(index == total - 1)
|
||||
|
||||
try:
|
||||
return_code = cmd_fn(piped_input, list(stage_args), self._config)
|
||||
@@ -210,7 +208,6 @@ class PipelineExecutor:
|
||||
return stage
|
||||
finally:
|
||||
ctx.set_stage_context(None)
|
||||
ctx.set_active(False)
|
||||
|
||||
emitted = list(getattr(pipeline_ctx, "emits", []) or [])
|
||||
stage.emitted = emitted
|
||||
|
||||
Reference in New Issue
Block a user