continuing refactor

This commit is contained in:
2026-05-03 21:20:05 -07:00
parent 77cab1bd27
commit 5534812426
50 changed files with 1004 additions and 428 deletions
+90 -5
View File
@@ -161,6 +161,17 @@ class Provider(ABC):
# generic "file host" plugins via `add-file -plugin ...`.
EXPOSE_AS_FILE_PROVIDER: bool = True
# Set to True for plugins that support multiple named instances in config.
# When True, config is expected at config["plugin"][<PLUGIN_NAME>][<instance_name>]
# rather than config["plugin"][<PLUGIN_NAME>] directly.
# Examples: hydrusnetwork (home/work), matrix (personal/work), ftp.
MULTI_INSTANCE: bool = False
# Declare which top-level cmdlet names this plugin handles.
# Cmdlet dispatch and capability discovery use this to route operations.
# Example: frozenset({"add-file", "get-file", "get-tag", "search-file"})
SUPPORTED_CMDLETS: frozenset = frozenset()
def __init__(self, config: Optional[Dict[str, Any]] = None):
self.config = config or {}
self.name = str(
@@ -312,11 +323,21 @@ class Provider(ABC):
def plugin_config_root(self) -> Dict[str, Any]:
if not isinstance(self.config, dict):
return {}
provider_cfg = self.config.get("provider")
if not isinstance(provider_cfg, dict):
return {}
entry = provider_cfg.get(self.plugin_config_key())
return dict(entry) if isinstance(entry, dict) else {}
# Check plugin/provider section first (preferred new format)
for section in ("plugin", "provider"):
section_cfg = self.config.get(section)
if isinstance(section_cfg, dict):
entry = section_cfg.get(self.plugin_config_key())
if isinstance(entry, dict):
return dict(entry)
# Backward compat: fall back to store section.
# store config uses {type: {instance: {key: val}}} — one level deeper.
store_cfg = self.config.get("store")
if isinstance(store_cfg, dict):
store_entries = store_cfg.get(self.plugin_config_key())
if isinstance(store_entries, dict):
return dict(store_entries)
return {}
def plugin_instance_configs(self) -> Dict[str, Dict[str, Any]]:
entry = self.plugin_config_root()
@@ -599,6 +620,70 @@ class Provider(ABC):
"""Upload a file and return a URL or identifier."""
raise NotImplementedError(f"Plugin '{self.name}' does not support upload")
# -----------------------------------------------------------------------
# Storage interface — mirrors Store._base.Store.
# Plugins that act as file repositories override these methods.
# All raise NotImplementedError by default; override selectively.
# -----------------------------------------------------------------------
@property
def is_remote(self) -> bool:
"""True if this plugin stores files on a remote service."""
return False
@property
def prefer_defer_tags(self) -> bool:
"""True if tag writes should be deferred until after file ingest."""
return False
def add_file(self, file_path: Path, **kwargs: Any) -> str:
"""Ingest a file and return its canonical hash."""
raise NotImplementedError(f"Plugin '{self.name}' does not support add_file")
def get_file(self, file_hash: str, **kwargs: Any) -> Optional[Path]:
"""Retrieve a stored file by hash, returning a local Path or None."""
raise NotImplementedError(f"Plugin '{self.name}' does not support get_file")
def get_metadata(self, file_hash: str, **kwargs: Any) -> Optional[Dict[str, Any]]:
"""Return metadata dict for a stored file."""
raise NotImplementedError(f"Plugin '{self.name}' does not support get_metadata")
def get_tag(self, file_identifier: str, **kwargs: Any) -> Tuple[List[str], str]:
"""Return (tags, hash) for a stored file identifier."""
raise NotImplementedError(f"Plugin '{self.name}' does not support get_tag")
def add_tag(self, file_identifier: str, tags: List[str], **kwargs: Any) -> bool:
"""Add tags to a stored file. Returns True on success."""
raise NotImplementedError(f"Plugin '{self.name}' does not support add_tag")
def delete_tag(self, file_identifier: str, tags: List[str], **kwargs: Any) -> bool:
"""Remove tags from a stored file. Returns True on success."""
raise NotImplementedError(f"Plugin '{self.name}' does not support delete_tag")
def get_url(self, file_identifier: str, **kwargs: Any) -> List[str]:
"""Return associated URLs for a stored file."""
raise NotImplementedError(f"Plugin '{self.name}' does not support get_url")
def add_url(self, file_identifier: str, urls: List[str], **kwargs: Any) -> bool:
"""Associate URLs with a stored file. Returns True on success."""
raise NotImplementedError(f"Plugin '{self.name}' does not support add_url")
def delete_url(self, file_identifier: str, urls: List[str], **kwargs: Any) -> bool:
"""Remove URL associations from a stored file. Returns True on success."""
raise NotImplementedError(f"Plugin '{self.name}' does not support delete_url")
def get_note(self, file_identifier: str, **kwargs: Any) -> Dict[str, str]:
"""Return notes dict (name -> text) for a stored file."""
raise NotImplementedError(f"Plugin '{self.name}' does not support get_note")
def set_note(self, file_identifier: str, name: str, text: str, **kwargs: Any) -> bool:
"""Write a named note on a stored file. Returns True on success."""
raise NotImplementedError(f"Plugin '{self.name}' does not support set_note")
def delete_note(self, file_identifier: str, name: str, **kwargs: Any) -> bool:
"""Delete a named note from a stored file. Returns True on success."""
raise NotImplementedError(f"Plugin '{self.name}' does not support delete_note")
def validate(self) -> bool:
"""Check if the plugin is available and properly configured."""