Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #1330 #1339

Merged
merged 4 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 40 additions & 9 deletions app/Actions/Album/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,49 @@ public function do(array $albumIDs): FileDeleter
// \Kalnoy\Nestedset\NodeTrait
// I really liked the code of master@0199212 ways better, but it was
// simply too inefficient

// This code also fixes a bug when more than one album with
// sub-albums is deleted, i.e. if we delete a "sub-forest".
// The original code (of the nested set model) updates the
// (lft,rgt)-indices on the DB level for every single deletion.
// However, this way deletion of the second albums fails, if the
// second album has already been hydrated earlier, because the
// indices of the already hydrated models and the indices in the
// DB are out-of-sync.
// Either all remaining models needs to be re-hydrated aka
// "refreshed" from the (already updated) DB after every single
// deletion or the update of the DB needs to be postponed until
// all models have been deleted.
// The latter is more efficient, because we do not reload models
// from the DB.

/** @var array<array{lft: int, rgt:int}> $pendingGapsToMake */
$pendingGapsToMake = [];
$deleteQuery = Album::query();
// First collect all albums to delete in a single query and
// memorize which indices need to be updated later.
foreach ($albums as $album) {
$lft = $album->getLft();
$rgt = $album->getRgt();
$album
->descendants()
->orderBy($album->getLftName(), 'desc')
->delete();
$height = $rgt - $lft + 1;
$album->newNestedSetQuery()->makeGap($rgt + 1, -$height);
$pendingGapsToMake[] = [
'lft' => $album->getLft(),
'rgt' => $album->getRgt(),
];
$deleteQuery->whereDescendantOf($album, 'or', false, true);
}
// For MySQL deletion must be done in correct order otherwise the
// foreign key constraint to `parent_id` fails.
$deleteQuery->orderBy('_lft', 'desc')->delete();
// _After all_ albums have been deleted, remove the gaps which
// have been created by the removed albums.
// Note, the gaps must be removed beginning with the highest
// values first otherwise the later indices won't be correct.
// To save some DB queries, we could implement a "makeMultiGap".
usort($pendingGapsToMake, fn ($a, $b) => $b['lft'] <=> $a['lft']);
foreach ($pendingGapsToMake as $pendingGap) {
$height = $pendingGap['rgt'] - $pendingGap['lft'] + 1;
$album->newNestedSetQuery()->makeGap($pendingGap['rgt'] + 1, -$height);
Album::$actionsPerformed++;
}
Album::query()->whereIn('id', $albumIDs)->delete();

TagAlbum::query()->whereIn('id', $albumIDs)->delete();

// Note, we may need to delete more base albums than those whose
Expand Down
Loading