Treat manual storage as Importarr-owned
Use SAB storage under the configured download root as ownership even when SAB reports category '*'.
This commit is contained in:
+3
-3
@@ -317,9 +317,9 @@ async def sync_queue() -> None:
|
||||
continue
|
||||
if readiness.ready and readiness.storage:
|
||||
for video in scan_videos(readiness.storage):
|
||||
state.upsert_queue_item(source_type="sab", source_id=str(video.path), source_path=video.path, name=video.path.name, state="ready", reason=readiness.reason, relative_path=str(video.relative_path), size=video.size, job_id=job_id)
|
||||
state.upsert_queue_item(source_type="sab", source_id=str(video.path), source_path=video.path, name=video.path.name, state="ready", reason=readiness.reason, relative_path=str(video.relative_path), size=video.size, job_id=job_id, sab_category=str(item.get("category") or item.get("cat") or ""))
|
||||
else:
|
||||
state.upsert_queue_item(source_type="sab", source_id=job_id, source_path=readiness.storage, name=str(item.get("name") or job_id), state=readiness.state, reason=readiness.reason, job_id=job_id)
|
||||
state.upsert_queue_item(source_type="sab", source_id=job_id, source_path=readiness.storage, name=str(item.get("name") or job_id), state=readiness.state, reason=readiness.reason, job_id=job_id, sab_category=str(item.get("category") or item.get("cat") or ""))
|
||||
|
||||
|
||||
def queue_jobs() -> list[dict[str, object]]:
|
||||
@@ -346,7 +346,7 @@ def serialize_queue_item(item: dict[str, object]) -> dict[str, object]:
|
||||
"updated_at": item["updated_at"],
|
||||
"completed_at": item["completed_at"],
|
||||
"sab_status": state_name if source_type == "sab" else None,
|
||||
"sab_category": settings.sab_category if source_type == "sab" else None,
|
||||
"sab_category": item.get("sab_category") if source_type == "sab" else None,
|
||||
"can_run_now": state_name in {"ready", "manual_batch", "failed"},
|
||||
"can_retry": state_name in {"failed", "skipped"},
|
||||
"can_ignore": state_name not in {"imported", "skipped"},
|
||||
|
||||
+14
-8
@@ -31,21 +31,27 @@ def classify_history_item(item: dict[str, Any], active_nzo_ids: set[str], catego
|
||||
if not force_status and nzo_id and nzo_id in active_nzo_ids:
|
||||
return Readiness("processing", "SAB job is still present in queue")
|
||||
item_category = str(item.get("category") or item.get("cat") or "")
|
||||
if item_category != category:
|
||||
return Readiness("ignored", "SAB category is not owned by Importarr")
|
||||
status = str(item.get("status") or "")
|
||||
storage_value = str(item.get("storage") or "")
|
||||
storage = Path(storage_value).resolve() if storage_value else None
|
||||
root = download_root.resolve()
|
||||
storage_in_root = bool(storage and (storage == root or root in storage.parents))
|
||||
if item_category != category and not storage_in_root:
|
||||
return Readiness("ignored", "SAB category/storage is not owned by Importarr", storage)
|
||||
if not force_status and status == "Failed":
|
||||
return Readiness("failed", "SAB history reports failure")
|
||||
if not force_status and (status in NOT_READY_STATUSES or status != "Completed"):
|
||||
return Readiness("processing", f"SAB status is {status or 'unknown'}")
|
||||
storage_value = str(item.get("storage") or "")
|
||||
if not storage_value:
|
||||
if storage is None:
|
||||
return Readiness("unknown", "SAB completed item has no final storage")
|
||||
storage = Path(storage_value).resolve()
|
||||
root = download_root.resolve()
|
||||
if storage != root and root not in storage.parents:
|
||||
if not storage_in_root:
|
||||
return Readiness("ignored", "SAB storage is outside configured download root", storage)
|
||||
if has_transient_part(storage):
|
||||
return Readiness("processing", "SAB storage path contains transient unpack/admin marker", storage)
|
||||
reason = "forced despite SAB status" if force_status and status != "Completed" else "SAB completed in owned category with final storage"
|
||||
if force_status and status != "Completed":
|
||||
reason = "forced despite SAB status"
|
||||
elif item_category != category:
|
||||
reason = "SAB completed inside Importarr download root"
|
||||
else:
|
||||
reason = "SAB completed in owned category with final storage"
|
||||
return Readiness("ready", reason, storage)
|
||||
|
||||
+9
-3
@@ -46,6 +46,7 @@ class State:
|
||||
size integer not null default 0,
|
||||
batch_id integer,
|
||||
job_id text,
|
||||
sab_category text,
|
||||
first_seen_at text not null default current_timestamp,
|
||||
updated_at text not null default current_timestamp,
|
||||
completed_at text,
|
||||
@@ -53,6 +54,9 @@ class State:
|
||||
);
|
||||
"""
|
||||
)
|
||||
columns = {row["name"] for row in self.conn.execute("pragma table_info(import_queue_items)")}
|
||||
if "sab_category" not in columns:
|
||||
self.conn.execute("alter table import_queue_items add column sab_category text")
|
||||
self.conn.commit()
|
||||
|
||||
def get_app_state(self, key: str, default: str | None = None) -> str | None:
|
||||
@@ -111,11 +115,12 @@ class State:
|
||||
size: int = 0,
|
||||
batch_id: int | None = None,
|
||||
job_id: str | None = None,
|
||||
sab_category: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
self.conn.execute(
|
||||
"""
|
||||
insert into import_queue_items(source_type, source_id, source_path, name, state, reason, relative_path, size, batch_id, job_id)
|
||||
values (?,?,?,?,?,?,?,?,?,?)
|
||||
insert into import_queue_items(source_type, source_id, source_path, name, state, reason, relative_path, size, batch_id, job_id, sab_category)
|
||||
values (?,?,?,?,?,?,?,?,?,?,?)
|
||||
on conflict(source_type, source_id) do update set
|
||||
source_path=excluded.source_path,
|
||||
name=excluded.name,
|
||||
@@ -125,10 +130,11 @@ class State:
|
||||
size=excluded.size,
|
||||
batch_id=excluded.batch_id,
|
||||
job_id=excluded.job_id,
|
||||
sab_category=excluded.sab_category,
|
||||
updated_at=current_timestamp,
|
||||
completed_at=case when excluded.state in ('imported','failed','skipped') then current_timestamp else null end
|
||||
""",
|
||||
(source_type, source_id, str(source_path) if source_path else None, name, state, reason, relative_path, size, batch_id, job_id),
|
||||
(source_type, source_id, str(source_path) if source_path else None, name, state, reason, relative_path, size, batch_id, job_id, sab_category),
|
||||
)
|
||||
self.conn.commit()
|
||||
row = self.conn.execute("select * from import_queue_items where source_type = ? and source_id = ?", (source_type, source_id)).fetchone()
|
||||
|
||||
@@ -19,6 +19,12 @@ def test_completed_manual_is_ready():
|
||||
|
||||
def test_wrong_category_ignored():
|
||||
result = classify_history_item(item(category="*"), set(), "manual", ROOT)
|
||||
assert result.ready
|
||||
assert result.reason == "SAB completed inside Importarr download root"
|
||||
|
||||
|
||||
def test_wrong_category_outside_root_ignored():
|
||||
result = classify_history_item(item(category="*", storage="/tmp/other/Movie"), set(), "manual", ROOT)
|
||||
assert result.state == "ignored"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user