This commit is contained in:
2026-01-19 06:24:09 -08:00
parent a961ac3ce7
commit 7ddf0065d1
45 changed files with 627 additions and 411 deletions

View File

@@ -10,6 +10,7 @@ from typing import Any, Dict, List, Optional, Tuple
from SYS.logger import debug, log
from SYS.utils import sha256_file, expand_path
from SYS.config import get_local_storage_path
from Store._base import Store
@@ -56,7 +57,7 @@ class Folder(Store):
""""""
# Track which locations have already been migrated to avoid repeated migrations
_migrated_locations = set()
_migrated_locations: set[str] = set()
# Cache scan results to avoid repeated full scans across repeated instantiations
_scan_cache: Dict[str,
Tuple[bool,
@@ -65,7 +66,7 @@ class Folder(Store):
int]]] = {}
@classmethod
def config(cls) -> List[Dict[str, Any]]:
def config_schema(cls) -> List[Dict[str, Any]]:
return [
{
"key": "NAME",
@@ -1498,11 +1499,12 @@ class Folder(Store):
debug(f"Failed to get file for hash {file_hash}: {exc}")
return None
def get_metadata(self, file_hash: str) -> Optional[Dict[str, Any]]:
def get_metadata(self, file_hash: str, **kwargs: Any) -> Optional[Dict[str, Any]]:
"""Get metadata for a file from the database by hash.
Args:
file_hash: SHA256 hash of the file (64-char hex string)
**kwargs: Additional options
Returns:
Dict with metadata fields (ext, size, hash, duration, etc.) or None if not found
@@ -1613,7 +1615,7 @@ class Folder(Store):
debug(f"get_tags failed for local file: {exc}")
return [], "unknown"
def add_tag(self, hash: str, tag: List[str], **kwargs: Any) -> bool:
def add_tag(self, file_identifier: str, tags: List[str], **kwargs: Any) -> bool:
"""Add tags to a local file by hash (via API_folder_store).
Handles namespace collapsing: when adding namespace:value, removes existing namespace:* tags.
@@ -1628,14 +1630,14 @@ class Folder(Store):
try:
with API_folder_store(Path(self._location)) as db:
existing_tags = [
t for t in (db.get_tags(hash) or [])
t for t in (db.get_tags(file_identifier) or [])
if isinstance(t, str) and t.strip()
]
from SYS.metadata import compute_namespaced_tag_overwrite
_to_remove, _to_add, merged = compute_namespaced_tag_overwrite(
existing_tags, tag or []
existing_tags, tags or []
)
if not _to_remove and not _to_add:
return True
@@ -1644,7 +1646,7 @@ class Folder(Store):
# To enforce lowercase-only tags and namespace overwrites, rewrite the full tag set.
cursor = db.connection.cursor()
cursor.execute("DELETE FROM tag WHERE hash = ?",
(hash,
(file_identifier,
))
for t in merged:
t = str(t).strip().lower()

View File

@@ -30,7 +30,7 @@ class HydrusNetwork(Store):
"""
@classmethod
def config(cls) -> List[Dict[str, Any]]:
def config_schema(cls) -> List[Dict[str, Any]]:
return [
{
"key": "NAME",
@@ -723,6 +723,10 @@ class HydrusNetwork(Store):
if text:
pattern_hints.append(text)
pattern_hint = pattern_hints[0] if pattern_hints else ""
hashes: list[str] = []
file_ids: list[int] = []
if ":" in query_lower and not query_lower.startswith(":"):
namespace, pattern = query_lower.split(":", 1)
namespace = namespace.strip().lower()
@@ -765,8 +769,8 @@ class HydrusNetwork(Store):
response = client._perform_request(
spec
) # type: ignore[attr-defined]
hashes: list[str] = []
file_ids: list[int] = []
hashes = []
file_ids = []
if isinstance(response, dict):
raw_hashes = response.get("hashes") or response.get(
"file_hashes"
@@ -870,11 +874,11 @@ class HydrusNetwork(Store):
freeform_predicates = [f"{query_lower}*"]
# Search files with the tags (unless url: search already produced metadata)
results = []
results: list[dict[str, Any]] = []
if metadata_list is None:
file_ids: list[int] = []
hashes: list[str] = []
file_ids = []
hashes = []
if freeform_union_search:
if not title_predicates and not freeform_predicates:
@@ -929,7 +933,7 @@ class HydrusNetwork(Store):
# Fast path: ext-only search. Avoid fetching metadata for an unbounded
# system:everything result set; fetch in chunks until we have enough.
if ext_only and ext_filter:
results: list[dict[str, Any]] = []
results = []
if not file_ids and not hashes:
debug(f"{prefix} 0 result(s)")
return []
@@ -1930,7 +1934,7 @@ class HydrusNetwork(Store):
try:
if service_key:
# Mutate tags for many hashes in a single request
client.mutate_tags_by_key(hashes=hashes, service_key=service_key, add_tags=list(tag_tuple))
client.mutate_tags_by_key(hash=hashes, service_key=service_key, add_tags=list(tag_tuple))
any_success = True
continue
except Exception as exc:

View File

@@ -30,7 +30,7 @@ from Store._base import Store
class ZeroTier(Store):
@classmethod
def config(cls) -> List[Dict[str, Any]]:
def config_schema(cls) -> List[Dict[str, Any]]:
return [
{"key": "NAME", "label": "Store Name", "default": "", "required": True},
{"key": "NETWORK_ID", "label": "ZeroTier Network ID", "default": "", "required": True},

View File

@@ -13,7 +13,7 @@ from typing import Any, Dict, List, Optional, Tuple
class Store(ABC):
@classmethod
def config(cls) -> List[Dict[str, Any]]:
def config_schema(cls) -> List[Dict[str, Any]]:
"""Return configuration schema for this store.
Returns a list of dicts:

View File

@@ -91,10 +91,10 @@ def _discover_store_classes() -> Dict[str, Type[BaseStore]]:
def _required_keys_for(store_cls: Type[BaseStore]) -> list[str]:
# Support new config() schema
if hasattr(store_cls, "config") and callable(store_cls.config):
# Support new config_schema() schema
if hasattr(store_cls, "config_schema") and callable(store_cls.config_schema):
try:
schema = store_cls.config()
schema = store_cls.config_schema()
keys = []
if isinstance(schema, list):
for field in schema: