huge refactor of the entire codebase, with the goal of improving maintainability, readability, and extensibility. This commit includes changes to almost every file in the project, including:

This commit is contained in:
2026-04-19 00:41:09 -07:00
parent d9e736172a
commit bafd37fdfb
50 changed files with 3258 additions and 4177 deletions
+79 -62
View File
@@ -176,14 +176,14 @@ class Add_File(Cmdlet):
super().__init__(
name="add-file",
summary=
"Ingest a local media file to a store backend, file provider, or local directory.",
"Ingest a local media file to a store backend, upload plugin, or local directory.",
usage=
"add-file (-path <filepath> | <piped>) (-storage <location> | -provider <fileprovider>) [-delete]",
"add-file (-path <filepath> | <piped>) (-storage <location> | -plugin <upload-plugin>) [-delete]",
arg=[
SharedArgs.PATH,
SharedArgs.STORE,
SharedArgs.URL,
SharedArgs.PROVIDER,
SharedArgs.PLUGIN,
CmdletArg(
name="delete",
type="flag",
@@ -198,7 +198,7 @@ class Add_File(Cmdlet):
" hydrus: Upload to Hydrus database with metadata tagging",
" local: Copy file to local directory",
" <path>: Copy file to specified directory",
"- File provider options (use -provider):",
"- Upload plugin options (use -plugin):",
" 0x0: Upload to 0x0.st for temporary hosting",
" file.io: Upload to file.io for temporary hosting",
" internetarchive: Upload to archive.org (optional tag: ia:<identifier> to upload into an existing item)",
@@ -224,13 +224,13 @@ class Add_File(Cmdlet):
path_arg = parsed.get("path")
location = parsed.get("store")
source_url_arg = parsed.get("url")
provider_name = parsed.get("provider")
plugin_name = parsed.get("plugin")
delete_after = parsed.get("delete", False)
# Convenience: when piping a file into add-file, allow `-path <existing dir>`
# to act as the destination export directory.
# Example: screen-shot "https://..." | add-file -path "C:\Users\Admin\Desktop"
if path_arg and not location and not provider_name:
if path_arg and not location and not plugin_name:
try:
candidate_dir = Path(str(path_arg))
if candidate_dir.exists() and candidate_dir.is_dir():
@@ -263,7 +263,7 @@ class Add_File(Cmdlet):
dir_scan_results: Optional[List[Dict[str, Any]]] = None
explicit_path_list_results: Optional[List[Dict[str, Any]]] = None
if path_arg and location and not provider_name:
if path_arg and location and not plugin_name:
# Support comma-separated path lists: -path "file1,file2,file3"
# This is the mechanism used by @N expansion for directory tables.
try:
@@ -403,7 +403,7 @@ class Add_File(Cmdlet):
("result_type", type(result).__name__),
("items", total_items),
("location", location),
("provider", provider_name),
("plugin", plugin_name),
("delete", delete_after),
],
border_style="cyan",
@@ -599,8 +599,8 @@ class Add_File(Cmdlet):
export_destination=(Path(location) if location and not is_storage_backend_location else None),
store_instance=storage_registry,
)
if not media_path and provider_name:
media_path, file_hash, temp_dir_to_cleanup = Add_File._download_provider_source(
if not media_path and plugin_name:
media_path, file_hash, temp_dir_to_cleanup = Add_File._download_piped_source(
pipe_obj, config, storage_registry
)
if media_path:
@@ -610,7 +610,7 @@ class Add_File(Cmdlet):
[
("path", media_path),
("hash", file_hash or "N/A"),
("provider", provider_name or "local"),
("plugin", plugin_name or "local"),
],
border_style="green",
)
@@ -635,10 +635,10 @@ class Add_File(Cmdlet):
progress.step("hashing file")
progress.step("ingesting file")
if provider_name:
code = self._handle_provider_upload(
if plugin_name:
code = self._handle_plugin_upload(
media_path,
provider_name,
plugin_name,
pipe_obj,
config,
delete_after_item
@@ -1365,7 +1365,7 @@ class Add_File(Cmdlet):
hash_hint = get_field(result, "hash") or get_field(result, "file_hash") or getattr(pipe_obj, "hash", None)
return candidate, hash_hint, None
downloaded_path, hash_hint, tmp_dir = Add_File._maybe_download_provider_result(
downloaded_path, hash_hint, tmp_dir = Add_File._maybe_download_plugin_result(
result,
pipe_obj,
config,
@@ -1393,45 +1393,41 @@ class Add_File(Cmdlet):
return normalized
@staticmethod
def _maybe_download_provider_result(
def _maybe_download_plugin_result(
result: Any,
pipe_obj: models.PipeObject,
config: Dict[str, Any],
) -> Tuple[Optional[Path], Optional[str], Optional[Path]]:
provider_key = None
plugin_key = None
for source in (
pipe_obj.provider,
get_field(result, "plugin"),
get_field(result, "provider"),
get_field(result, "table"),
):
candidate = Add_File._normalize_provider_key(source)
if candidate:
provider_key = candidate
plugin_key = candidate
break
if not provider_key:
if not plugin_key:
return None, None, None
provider = get_search_provider(provider_key, config)
if provider is None:
from ProviderCore.registry import get_search_plugin
plugin = get_search_plugin(plugin_key, config)
if plugin is None:
return None, None, None
# Check for specialized download helper (used by AllDebrid and potentially others)
handler = getattr(provider, "download_for_pipe_result", None)
if not callable(handler):
# Fallback: check class if it's a classmethod and instance didn't have it (unlikely but safe)
handler = getattr(type(provider), "download_for_pipe_result", None)
if callable(handler):
try:
return handler(result, pipe_obj, config)
except Exception as exc:
debug(f"[add-file] Provider '{provider_key}' download helper failed: {exc}")
try:
return plugin.resolve_pipe_result_download(result, pipe_obj)
except Exception as exc:
debug(f"[add-file] Plugin '{plugin_key}' download helper failed: {exc}")
return None, None, None
@staticmethod
def _download_provider_source(
def _download_piped_source(
pipe_obj: models.PipeObject,
config: Dict[str, Any],
store_instance: Optional[Any],
@@ -2152,23 +2148,23 @@ class Add_File(Cmdlet):
return 0
@staticmethod
def _handle_provider_upload(
def _handle_plugin_upload(
media_path: Path,
provider_name: str,
plugin_name: str,
pipe_obj: models.PipeObject,
config: Dict[str,
Any],
delete_after: bool,
) -> int:
"""Handle uploading to a file provider (e.g. 0x0)."""
from ProviderCore.registry import get_file_provider
"""Handle uploading via an upload plugin (e.g. 0x0)."""
from ProviderCore.registry import get_upload_plugin
log(f"Uploading via {provider_name}: {media_path.name}", file=sys.stderr)
log(f"Uploading via {plugin_name}: {media_path.name}", file=sys.stderr)
try:
file_provider = get_file_provider(provider_name, config)
file_provider = get_upload_plugin(plugin_name, config)
if not file_provider:
log(f"File provider '{provider_name}' not available", file=sys.stderr)
log(f"Upload plugin '{plugin_name}' not available", file=sys.stderr)
return 1
hoster_url = file_provider.upload(str(media_path), pipe_obj=pipe_obj)
@@ -2183,8 +2179,8 @@ class Add_File(Cmdlet):
# Update PipeObject and emit
extra_updates: Dict[str,
Any] = {
"provider": provider_name,
"provider_url": hoster_url,
"plugin": plugin_name,
"plugin_url": hoster_url,
}
if isinstance(pipe_obj.extra, dict):
# Also track hoster URL as a url for downstream steps
@@ -2197,7 +2193,7 @@ class Add_File(Cmdlet):
Add_File._update_pipe_object_destination(
pipe_obj,
hash_value=f_hash or "unknown",
store=provider_name or "provider",
store=plugin_name or "plugin",
path=file_path,
tag=pipe_obj.tag,
title=pipe_obj.title or (media_path.name if media_path else None),
@@ -2445,9 +2441,6 @@ class Add_File(Cmdlet):
try:
adder = getattr(backend, "add_tag", None)
if callable(adder):
debug(
f"[add-file] Applying {len(tags)} tag(s) post-upload to {backend_name}"
)
adder(resolved_hash, list(tags))
except Exception as exc:
log(f"[add-file] Post-upload tagging failed for {backend_name}: {exc}", file=sys.stderr)
@@ -2479,48 +2472,72 @@ class Add_File(Cmdlet):
try:
setter = getattr(backend, "set_note", None)
if callable(setter):
debug(
f"[add-file] Writing sub note (len={len(str(sub_note))}) to {backend_name}:{resolved_hash}"
)
setter(resolved_hash, "sub", sub_note)
except Exception as exc:
debug(f"[add-file] sub note write failed: {exc}")
debug_panel(
"add-file note write failed",
[
("store", backend_name),
("hash", resolved_hash),
("note", "sub"),
("error", exc),
],
border_style="yellow",
)
lyric_note = Add_File._get_note_text(result, pipe_obj, "lyric")
if lyric_note:
try:
setter = getattr(backend, "set_note", None)
if callable(setter):
debug(
f"[add-file] Writing lyric note (len={len(str(lyric_note))}) to {backend_name}:{resolved_hash}"
)
setter(resolved_hash, "lyric", lyric_note)
except Exception as exc:
debug(f"[add-file] lyric note write failed: {exc}")
debug_panel(
"add-file note write failed",
[
("store", backend_name),
("hash", resolved_hash),
("note", "lyric"),
("error", exc),
],
border_style="yellow",
)
chapters_note = Add_File._get_note_text(result, pipe_obj, "chapters")
if chapters_note:
try:
setter = getattr(backend, "set_note", None)
if callable(setter):
debug(
f"[add-file] Writing chapters note (len={len(str(chapters_note))}) to {backend_name}:{resolved_hash}"
)
setter(resolved_hash, "chapters", chapters_note)
except Exception as exc:
debug(f"[add-file] chapters note write failed: {exc}")
debug_panel(
"add-file note write failed",
[
("store", backend_name),
("hash", resolved_hash),
("note", "chapters"),
("error", exc),
],
border_style="yellow",
)
caption_note = Add_File._get_note_text(result, pipe_obj, "caption")
if caption_note:
try:
setter = getattr(backend, "set_note", None)
if callable(setter):
debug(
f"[add-file] Writing caption note (len={len(str(caption_note))}) to {backend_name}:{resolved_hash}"
)
setter(resolved_hash, "caption", caption_note)
except Exception as exc:
debug(f"[add-file] caption note write failed: {exc}")
debug_panel(
"add-file note write failed",
[
("store", backend_name),
("hash", resolved_hash),
("note", "caption"),
("error", exc),
],
border_style="yellow",
)
meta: Dict[str,
Any] = {}