f
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user