fix(lora): keep the save outcome, honour the umask and sweep stale downloads

Leaving Integrations drops the CivitAI key draft only when a key was
typed, so the last Save's outcome survives and Sources still marks a
refused source. The key status line is rewritten only when it changes.

Cached CivitAI images are opened like a plain save, so they keep the
umask's mode, and each import removes .pm-*.part files older than an
hour from the image cache. A legacy scan stopped because its page left
ends cancelled in Activity, and staging is awaited to the end even if
the handler is cancelled again.
This commit is contained in:
Vito Sansevero
2026-10-25 09:48:55 -07:00
parent 2ff74fda25
commit a073dc613e
9 changed files with 213 additions and 22 deletions
+1
View File
@@ -135,6 +135,7 @@ class LoraImportRoutesMixin:
from ..config import IntegrationConfig
cache_dir = await self._run_in_executor(lora_utils.get_lora_image_cache_dir)
await self._run_in_executor(lora_utils.sweep_stale_downloads, cache_dir)
loop = asyncio.get_running_loop()
def report(index, record, staged):
+26 -5
View File
@@ -73,6 +73,13 @@ def _closed(request):
return closed
def _stopped(error) -> bool:
"""Whether a legacy scan ended because its page closed the connection."""
from .. import lora_import
return isinstance(error, (ConnectionError, lora_import.ImportCancelled))
class LoraIntegrationMixin:
"""Mixin providing LoraManager detection, scanning, and trigger word endpoints."""
@@ -357,8 +364,13 @@ class LoraIntegrationMixin:
await self._lora_scan_stream(
allowed, send_progress, disconnected=_closed(request)
)
except asyncio.CancelledError:
# The server cancelled the handler when the page left.
job.finish("cancelled")
raise
except Exception as e:
job.finish("failed")
# A page that left stopped the scan; nothing failed (R2).
job.finish("cancelled" if _stopped(e) else "failed")
await self._send_scan_failure(e, send_progress)
with contextlib.suppress(ConnectionError, RuntimeError):
await response.write_eof()
@@ -375,14 +387,13 @@ class LoraIntegrationMixin:
import) are warnings in the admin page's own words; anything else is
logged with its traceback.
"""
from .. import lora_import
from .lora_import_routes import (
EmptyReplacement,
PairDependencyError,
PlanChanged,
)
if isinstance(error, (ConnectionError, lora_import.ImportCancelled)):
if _stopped(error):
self.logger.info("LoRA scan stopped: the page closed its connection")
return
if isinstance(error, PlanChanged):
@@ -498,8 +509,18 @@ class LoraIntegrationMixin:
await report(*events.get_nowait())
finally:
stop.set()
with contextlib.suppress(Exception):
await asyncio.shield(staging)
# Staging must have stopped before the records hold is released,
# even when the handler is cancelled again while it waits (R3).
interrupted = False
while not staging.done():
try:
await asyncio.shield(staging)
except asyncio.CancelledError:
interrupted = True
except Exception:
pass
if interrupted:
raise asyncio.CancelledError
staged = await staging
if not await self.lora_files_unchanged(lm_path, reviewed):
raise LoraFilesChanged(_FILES_CHANGED)