Scan queue handler now returns a QueueLibraryScanResult enum (Queued|NotFound|SyncDisabled|AlreadyScanning) instead of a lying bool; LibrariesController.ScanLibrary maps them to 202/404/422/409 with ProblemDetails. Guard the lock->enqueue with the EnqueueWithTraktLock compensating-unlock pattern. ScannerService now releases every library/collection lock in a finally so a handler exception can't leak the lock. Plex "Shows" scheduler batch (one lock, two messages) now has only the trailing SynchronizePlexNetworks carry the single release (Unlock flag), mirroring the scheduler Trakt tail-token precedent. Guard the other lock->enqueue producers (Create/UpdateLocalLibrary, UpdateTraktList) with compensating unlock. SPA drops the PENDING_GRACE_TICKS heuristic now that the POST reports 202/409/404/422 directly: 202 -> pending+poll, 409 -> reconcile (no error toast), 404/422 -> surface error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
72 lines
3.3 KiB
C#
72 lines
3.3 KiB
C#
using ErsatzTV.Application.Libraries;
|
|
using ErsatzTV.Core.Api.Libraries;
|
|
using ErsatzTV.Core.Interfaces.Repositories;
|
|
using ErsatzTV.Extensions;
|
|
using MediatR;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace ErsatzTV.Controllers.Api;
|
|
|
|
[ApiController]
|
|
[EndpointGroupName("general")]
|
|
public class LibrariesController(ITelevisionRepository televisionRepository, IMediator mediator) : ControllerBase
|
|
{
|
|
[HttpGet("/api/libraries/scan-status", Name = "GetLibraryScanStatus")]
|
|
[Tags("Libraries")]
|
|
[EndpointSummary("Get active library scan status")]
|
|
[ProducesResponseType(typeof(List<LibraryScanStatusResponseModel>), StatusCodes.Status200OK)]
|
|
public async Task<List<LibraryScanStatusResponseModel>> GetScanStatus(CancellationToken cancellationToken) =>
|
|
await mediator.Send(new GetLibraryScanStatus(), cancellationToken);
|
|
|
|
[HttpPost("/api/libraries/{id:int}/scan")]
|
|
[Tags("Libraries")]
|
|
[EndpointSummary("Scan library")]
|
|
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
|
|
public async Task<IActionResult> ScanLibrary(int id, CancellationToken cancellationToken)
|
|
{
|
|
QueueLibraryScanResult result = await mediator.Send(new QueueLibraryScanByLibraryId(id), cancellationToken);
|
|
return result switch
|
|
{
|
|
QueueLibraryScanResult.Queued => new AcceptedResult(),
|
|
QueueLibraryScanResult.AlreadyScanning => ApiResults.ConflictProblem(
|
|
"Library scan in progress",
|
|
$"A scan for library {id} is already in progress."),
|
|
QueueLibraryScanResult.SyncDisabled => new UnprocessableEntityObjectResult(
|
|
new ProblemDetails
|
|
{
|
|
Status = StatusCodes.Status422UnprocessableEntity,
|
|
Title = "Library sync is disabled",
|
|
Detail = $"Item sync is disabled for library {id}."
|
|
}),
|
|
_ => ApiResults.NotFoundProblem($"Library {id} does not exist.")
|
|
};
|
|
}
|
|
|
|
[HttpPost("/api/libraries/{id:int}/scan-show")]
|
|
[Tags("Libraries")]
|
|
[EndpointSummary("Scan show")]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public async Task<IActionResult> ScanShow(int id, [FromBody] ScanShowRequest request)
|
|
{
|
|
Option<string> maybeTitle = await televisionRepository.GetShowTitle(id, request.ShowId);
|
|
foreach (string title in maybeTitle)
|
|
{
|
|
bool result = await mediator.Send(new QueueShowScanByLibraryId(id, request.ShowId, title, request.DeepScan));
|
|
|
|
return result
|
|
? new OkResult()
|
|
: new BadRequestObjectResult(new { error = "Unable to queue show scan. Library may not exist, may not support single show scanning, or may already be scanning." });
|
|
}
|
|
|
|
return ApiResults.NotFoundProblem($"Show {request.ShowId} does not exist in library {id}.");
|
|
}
|
|
}
|
|
|
|
public record ScanShowRequest(int ShowId, bool DeepScan = false);
|