feat: delete orphaned future snapshots after xml sync

This commit is contained in:
Björn Fromme
2026-03-25 15:35:12 +01:00
parent facd91375c
commit bca3610cff
4 changed files with 84 additions and 0 deletions
+9
View File
@@ -7,6 +7,7 @@ namespace App\Command;
use App\BusProNet\Model\XmlExportInfo;
use App\BusProNet\XmlLoader\TravelLoader;
use App\Service\TravelDataService;
use App\Service\TravelSnapshotService;
use League\Flysystem\FilesystemException;
use League\Flysystem\FilesystemOperator;
use League\Flysystem\StorageAttributes;
@@ -41,6 +42,7 @@ class BpnXmlSyncCommand extends Command
private readonly LoggerInterface $logger,
private readonly TravelLoader $travelLoader,
private readonly TravelDataService $travelDataService,
private readonly TravelSnapshotService $snapshotService,
) {
parent::__construct();
}
@@ -124,6 +126,7 @@ class BpnXmlSyncCommand extends Command
$xmlFileMap = null;
$snapshotResult = null;
$orphanedDeleted = null;
try {
$xmlFileMap = $this->travelLoader->generateFilesMap();
} catch (\Throwable $e) {
@@ -142,18 +145,24 @@ class BpnXmlSyncCommand extends Command
);
$io->progressFinish();
$orphanedDeleted = $this->snapshotService->purgeOrphanedFutureSnapshots(array_keys($xmlFileMap));
}
$successMessage = sprintf('Sync complete: %d files updated, %d files deleted', $syncResult['updated'], $syncResult['deleted']);
if (null !== $snapshotResult) {
$successMessage .= sprintf(', snapshots: %d processed, %d failed', $snapshotResult['processed'], $snapshotResult['failed']);
}
if (null !== $orphanedDeleted) {
$successMessage .= sprintf(', orphaned deleted: %d', $orphanedDeleted);
}
$io->success($successMessage);
$this->logger->info('XML sync completed', [
'files_updated' => $syncResult['updated'],
'files_deleted' => $syncResult['deleted'],
'snapshots_processed' => $snapshotResult['processed'] ?? null,
'snapshots_failed' => $snapshotResult['failed'] ?? null,
'orphaned_deleted' => $orphanedDeleted,
'remote_timestamp' => $remoteInfo->lastTransfer->format('c'),
]);
@@ -126,4 +126,31 @@ class TravelSnapshotRepository extends ServiceEntityRepository
->getQuery()
->execute();
}
/**
* Deletes future snapshots whose dateId is no longer present in the XML export.
*
* Only snapshots with dateFrom > today are removed; in-progress and past travels
* are intentionally left intact so the extended-availability fallback can still serve them.
*
* @param array<int> $activeXmlDateIds dateIds currently present in the XML file map
*/
public function deleteOrphanedFutureSnapshots(array $activeXmlDateIds, \DateTimeImmutable $today): int
{
// An empty list means the XML export is unavailable or empty — refuse to purge
// anything rather than deleting all future snapshots due to NOT IN ([]) = 1=1.
if ([] === $activeXmlDateIds) {
return 0;
}
return (int) $this->createQueryBuilder('s')
->delete()
->where('s.dateFrom IS NOT NULL')
->andWhere('s.dateFrom > :today')
->andWhere('s.dateId NOT IN (:activeXmlDateIds)')
->setParameter('today', $today)
->setParameter('activeXmlDateIds', $activeXmlDateIds)
->getQuery()
->execute();
}
}
+13
View File
@@ -272,6 +272,19 @@ class TravelSnapshotService
return $this->snapshotRepository->deleteExpiredSnapshots($beforeDate);
}
/**
* Purges future snapshots whose dateId is no longer present in the XML export.
*
* @param array<int> $activeXmlDateIds dateIds currently present in the XML file map
*/
public function purgeOrphanedFutureSnapshots(array $activeXmlDateIds): int
{
return $this->snapshotRepository->deleteOrphanedFutureSnapshots(
$activeXmlDateIds,
new \DateTimeImmutable('today'),
);
}
/**
* Resolves snapshot by exact date/hotel or by date fallback.
*/