mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-07 20:27:45 +02:00
164 lines
6.5 KiB
Python
164 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
"""Public API methods for ManualDownloadManager."""
|
|
|
|
import json
|
|
from typing import Optional
|
|
|
|
|
|
class ManualDownloadManagerApiMixin:
|
|
"""Mixin containing public manager API methods and status properties."""
|
|
def load_items(self, events: list[dict], loop_iteration: int = 1) -> None:
|
|
"""
|
|
Merge a new batch of engine events into the existing item list.
|
|
|
|
On loop_iteration > 1, engine only emits still-missing files. Items NOT
|
|
in the new batch that were pending are confirmed present by the engine
|
|
(they passed its rescan) and are marked complete. Genuinely new items
|
|
(edge case) are appended. active_tabs resets so the sliding window
|
|
opens fresh tabs for the remaining items.
|
|
"""
|
|
with self._lock:
|
|
existing_map = {item.file_name: item for item in self._items}
|
|
new_batch_names = {evt.get('file_name', '') for evt in events}
|
|
|
|
# Items the engine confirmed are now present (not in new batch, were pending)
|
|
for item in self._items:
|
|
if item.file_name not in new_batch_names and item.status not in ('complete', 'deferred', 'skipped', 'error'):
|
|
item.status = 'complete'
|
|
item.needs_user_retry = False
|
|
|
|
# Recheck loop: clear temporary defer state for still-missing files so they can
|
|
# re-enter active browser rotation in the new iteration.
|
|
if loop_iteration > 1:
|
|
for item in self._items:
|
|
if item.file_name in new_batch_names and item.status in ('deferred', 'skipped'):
|
|
item.status = 'pending'
|
|
item.needs_user_retry = False
|
|
item.error_message = None
|
|
|
|
# Add items genuinely not seen before (first iteration, or edge case)
|
|
for evt in events:
|
|
name = evt.get('file_name', '')
|
|
if name not in existing_map:
|
|
# Local import avoids module-load circular dependency with manager class.
|
|
from jackify.backend.services.manual_download_manager import DownloadItem
|
|
|
|
new_item = DownloadItem.from_event(evt, loop_iteration)
|
|
self._items.append(new_item)
|
|
if not new_item.nexus_url:
|
|
self._diag(
|
|
"MDL-9012",
|
|
"Engine manual-download event missing required nexus_url",
|
|
level="error",
|
|
file_name=new_item.file_name or "missing",
|
|
loop_iteration=loop_iteration,
|
|
mod_id=new_item.mod_id,
|
|
file_id=new_item.file_id,
|
|
)
|
|
|
|
self._active_tabs = 0
|
|
total = len(self._items)
|
|
pending = sum(1 for i in self._items if i.status == 'pending')
|
|
complete = sum(1 for i in self._items if i.status == 'complete')
|
|
skipped = sum(1 for i in self._items if i.status == 'skipped')
|
|
sample_pending = [i.file_name for i in self._items if i.status == 'pending'][:5]
|
|
self._refresh_watcher_pending_items()
|
|
self._diag(
|
|
"MDL-1001",
|
|
"Manual download batch loaded",
|
|
loop_iteration=loop_iteration,
|
|
batch_size=len(events),
|
|
total_items=total,
|
|
pending=pending,
|
|
complete=complete,
|
|
skipped=skipped,
|
|
pending_sample=json.dumps(sample_pending, ensure_ascii=True),
|
|
)
|
|
|
|
def start(self) -> None:
|
|
with self._lock:
|
|
if self._started:
|
|
return
|
|
self._started = True
|
|
self._diag(
|
|
"MDL-1002",
|
|
"Manual download watcher started",
|
|
watch_dir=str(self._watch_dir),
|
|
downloads_dir=str(self._dl_dir),
|
|
concurrent_limit=self._limit,
|
|
)
|
|
self._watcher.start()
|
|
matched = self._ingest_existing_files()
|
|
with self._lock:
|
|
self._startup_precheck_pending = matched
|
|
if matched:
|
|
self._diag("MDL-1003", "Pre-existing archives detected", matched=matched)
|
|
self._diag("MDL-1016", "Deferring tab opening until precheck validation completes", pending_precheck=matched)
|
|
else:
|
|
self._open_next_tabs()
|
|
|
|
def stop(self) -> None:
|
|
self._watcher.stop()
|
|
self._validator.shutdown()
|
|
with self._lock:
|
|
self._started = False
|
|
self._startup_precheck_pending = 0
|
|
self._diag("MDL-1009", "Manual download manager stopped")
|
|
|
|
def pause(self) -> None:
|
|
with self._lock:
|
|
self._paused = True
|
|
|
|
def resume(self) -> None:
|
|
with self._lock:
|
|
self._paused = False
|
|
self._diag("MDL-1008", "Manual download resumed")
|
|
# Explicit user start/resume must open tabs even if startup precheck
|
|
# bookkeeping is still in-flight.
|
|
self._open_next_tabs(force_user_start=True)
|
|
|
|
def skip_item(self, file_name: str) -> None:
|
|
item_to_notify: Optional[DownloadItem] = None
|
|
with self._lock:
|
|
for item in self._items:
|
|
if item.file_name == file_name and item.status not in ('complete',):
|
|
item.status = 'deferred'
|
|
if self._active_tabs > 0:
|
|
self._active_tabs -= 1
|
|
item_to_notify = item
|
|
break
|
|
if item_to_notify is not None:
|
|
self._notify(item_to_notify)
|
|
self._open_next_tabs()
|
|
self._check_all_done()
|
|
|
|
def set_concurrent_limit(self, limit: int) -> None:
|
|
with self._lock:
|
|
self._limit = max(1, min(5, limit))
|
|
applied = self._limit
|
|
started = self._started
|
|
self._diag("MDL-1006", "Manual download concurrency updated", concurrent_limit=applied)
|
|
if started:
|
|
self._open_next_tabs()
|
|
|
|
@property
|
|
def items(self) -> list[DownloadItem]:
|
|
with self._lock:
|
|
return list(self._items)
|
|
|
|
@property
|
|
def pending_count(self) -> int:
|
|
with self._lock:
|
|
return sum(1 for i in self._items if i.status == 'pending')
|
|
|
|
@property
|
|
def complete_count(self) -> int:
|
|
with self._lock:
|
|
return sum(1 for i in self._items if i.status == 'complete')
|
|
|
|
@property
|
|
def skipped_count(self) -> int:
|
|
with self._lock:
|
|
return sum(1 for i in self._items if i.status in ('deferred', 'skipped'))
|