Preserve independent FNA image copies after source withdrawal
This commit is contained in:
parent
26cb1ed724
commit
21bb77c176
@ -160,17 +160,20 @@ public function synchronize(array $snapshots, ?array $baseline = null): array
|
||||
$state['assets'][$id] = $this->copy($image, $state['assets']);
|
||||
$available[$id] = true;
|
||||
}
|
||||
$selections = [];
|
||||
// Published FNA copies are independent of the source lifecycle.
|
||||
// A deleted source reference must not silently remove a local assignment.
|
||||
$selections = $state['selections'][$pool] ?? [];
|
||||
foreach ($snapshot['selections'] as $slug => $image) $selections[$slug] = $image['asset_id'];
|
||||
foreach ($snapshot['unavailable'] ?? [] as $slug => $reason) {
|
||||
$id = $state['selections'][$pool][$slug] ?? null;
|
||||
$old = $id ? ($state['assets'][$id] ?? null) : null;
|
||||
$local = $old ? config('illustrations.local_directory').'/'.basename($old['url']) : null;
|
||||
// A healthy independent display copy survives physical loss at the source.
|
||||
if (in_array($reason, ['file_missing', 'file_corrupt', 'file_symlink']) && $local && is_file($local) && ! is_link($local) && hash_equals($old['sha256'], hash_file('sha256', $local))) {
|
||||
// Source deletion, disabling or withdrawal never revokes an FNA copy.
|
||||
if ($local && is_file($local) && ! is_link($local) && hash_equals($old['sha256'], hash_file('sha256', $local))) {
|
||||
$selections[$slug] = $id;
|
||||
} else {
|
||||
$selections[$slug] = null;
|
||||
// Report missing/corrupt local bytes without destroying recovery metadata.
|
||||
$selections[$slug] = $id;
|
||||
$pending[$pool.':'.$slug] = $reason;
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,14 +111,35 @@ public function test_new_article_assignment_downloads_before_returning_local_url
|
||||
Http::assertSentCount(4); // Two assignment requests; content downloaded once.
|
||||
}
|
||||
|
||||
public function test_sync_stops_showing_withdrawn_mapping_without_automatically_deleting_shared_file(): void
|
||||
public function test_source_withdrawal_disabling_and_deletion_preserve_independent_local_copies(): void
|
||||
{
|
||||
$this->fakeImages();$local = app(LocalIllustrations::class);$image = $this->image();
|
||||
$local->storeSelection('freenode', 'fn1', $image);$local->storeSelection('freenode', 'fn2', $image);
|
||||
$local->synchronize(['freenode' => ['items' => [$image], 'selections' => ['fn2' => $image], 'unavailable' => ['fn1' => 'publication_revoked']]]);
|
||||
$this->assertNull($local->thumbnail('freenode', 'fn1'));
|
||||
Http::fake();
|
||||
foreach (['publication_revoked', 'asset_disabled', 'asset_inactive', 'access_revoked', 'file_missing', 'file_corrupt', 'file_symlink'] as $reason) {
|
||||
$result = $local->synchronize(['freenode' => ['items' => [], 'selections' => [], 'unavailable' => ['fn1' => $reason]]]);
|
||||
$this->assertSame([], $result['pending']);
|
||||
$this->assertSame('/images/illustrations/'.$this->sha.'.png', $local->thumbnail('freenode', 'fn1'));
|
||||
}
|
||||
// A deleted upstream selection is absent even from the unavailable map.
|
||||
$local->synchronize(['freenode' => ['items' => [], 'selections' => [], 'unavailable' => []]]);
|
||||
$this->assertNotNull($local->thumbnail('freenode', 'fn1'));
|
||||
$this->assertNotNull($local->thumbnail('freenode', 'fn2'));
|
||||
$this->assertFileExists($this->dir.'/public/'.$this->sha.'.png');
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_sync_reports_missing_local_file_without_erasing_its_assignment(): void
|
||||
{
|
||||
$this->fakeImages();$local = app(LocalIllustrations::class);
|
||||
$local->storeSelection('freenode', 'fn1', $this->image());
|
||||
unlink($this->dir.'/public/'.$this->sha.'.png');
|
||||
Http::fake();
|
||||
$result = $local->synchronize(['freenode' => ['items' => [], 'selections' => [], 'unavailable' => ['fn1' => 'asset_disabled']]]);
|
||||
$this->assertSame(['freenode:fn1' => 'asset_disabled'], $result['pending']);
|
||||
$state = json_decode(file_get_contents($this->dir.'/private/manifest.json'), true);
|
||||
$this->assertSame($this->id, $state['selections']['freenode']['fn1']);
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_missing_manifest_never_fetches_resource_or_returns_remote_fallback(): void
|
||||
|
||||
Loading…
Reference in New Issue
Block a user