Preserve concurrent article mappings during static sync
This commit is contained in:
parent
25e0f87d97
commit
9d2cd9a895
@ -20,6 +20,7 @@ public function handle(LocalIllustrations $local): int
|
||||
return self::FAILURE;
|
||||
}
|
||||
try {
|
||||
$baseline = $local->selectionBaseline();
|
||||
$snapshots = [];
|
||||
$expected = [];
|
||||
foreach (config('illustrations.categories') as $category => $pool) {
|
||||
@ -33,7 +34,7 @@ public function handle(LocalIllustrations $local): int
|
||||
$snapshots[$pool] = $snapshot;
|
||||
$expected[$pool] = $keys;
|
||||
}
|
||||
$result = $local->synchronize($snapshots);
|
||||
$result = $local->synchronize($snapshots, $baseline);
|
||||
foreach ($expected as $pool => $keys) {
|
||||
foreach ($keys as $key) {
|
||||
if (! $local->thumbnail($pool, $key)) $result['pending'][$pool.':'.$key] ??= 'local_copy_unavailable';
|
||||
|
||||
@ -128,9 +128,16 @@ public function storeSelection(string $pool, string $slug, array $image): string
|
||||
}
|
||||
|
||||
/** One atomic manifest switch after all downloadable files pass verification. */
|
||||
public function synchronize(array $snapshots): array
|
||||
public function selectionBaseline(): array
|
||||
{
|
||||
return $this->locked(function () use ($snapshots) {
|
||||
$this->manifest = null;
|
||||
return $this->read()['selections'];
|
||||
}
|
||||
|
||||
public function synchronize(array $snapshots, ?array $baseline = null): array
|
||||
{
|
||||
$baseline ??= $this->selectionBaseline();
|
||||
return $this->locked(function () use ($snapshots, $baseline) {
|
||||
$state = $this->read();
|
||||
$pending = [];
|
||||
foreach ($snapshots as $pool => $snapshot) {
|
||||
@ -155,6 +162,18 @@ public function synchronize(array $snapshots): array
|
||||
$pending[$pool.':'.$slug] = $reason;
|
||||
}
|
||||
}
|
||||
// Receivers may publish new mappings while the remote snapshot is in flight.
|
||||
// Never replace a local change made after this sync's baseline was captured.
|
||||
$current = $state['selections'][$pool] ?? [];
|
||||
$before = $baseline[$pool] ?? [];
|
||||
foreach (array_unique(array_merge(array_keys($before), array_keys($current))) as $slug) {
|
||||
$exists = array_key_exists($slug, $current);
|
||||
if ($exists !== array_key_exists($slug, $before) || ($current[$slug] ?? null) !== ($before[$slug] ?? null)) {
|
||||
if ($exists) $selections[$slug] = $current[$slug];
|
||||
else unset($selections[$slug]);
|
||||
unset($pending[$pool.':'.$slug]);
|
||||
}
|
||||
}
|
||||
$state['selections'][$pool] = $selections;
|
||||
}
|
||||
$this->save($state);
|
||||
|
||||
@ -125,4 +125,18 @@ public function test_missing_manifest_never_fetches_resource_or_returns_remote_f
|
||||
$this->assertSame('/tutorial.png', IllustrationPool::thumbnail(['iCategoryId' => 3, 'sSlug' => 'guide'], '/tutorial.png'));
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_sync_does_not_overwrite_concurrent_article_assignments(): void
|
||||
{
|
||||
$this->fakeImages();$local = app(LocalIllustrations::class);$old = $this->image();
|
||||
$local->storeSelection('freenode', 'fn1', $old);
|
||||
$baseline = $local->selectionBaseline();
|
||||
$new = $this->image('33333333-3333-4333-8333-333333333333');
|
||||
$local->storeSelection('freenode', 'fn1', $new);
|
||||
$local->storeSelection('freenode', 'fn2', $new);
|
||||
$local->synchronize(['freenode' => ['items' => [$old], 'selections' => ['fn1' => $old], 'unavailable' => []]], $baseline);
|
||||
$state = json_decode(file_get_contents($this->dir.'/private/manifest.json'), true);
|
||||
$this->assertSame($new['asset_id'], $state['selections']['freenode']['fn1']);
|
||||
$this->assertSame($new['asset_id'], $state['selections']['freenode']['fn2']);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user