fix(12-02): reorder orphan cleanup to delete FK children before recordings
recording_genres and release_group_recordings reference recordings.id, so they must be deleted BEFORE the recordings table is cleaned. Also extends toast duration to 8s for readability.
This commit is contained in:
+22
-19
@@ -288,7 +288,28 @@ func (l *Library) RemoveLibrary(id int64) (*RemovalSummary, error) {
|
||||
|
||||
tracksDeleted, _ := result.RowsAffected()
|
||||
|
||||
// 7. Delete orphaned recordings.
|
||||
// 7. Delete orphaned recording_genres (must run BEFORE recordings
|
||||
// because recording_genres.recording_id references recordings.id).
|
||||
// SAFETY: Hand-crafted orphan cleanup SQL. Parameterless.
|
||||
if _, err := tx.ExecContext(l.ctx,
|
||||
`DELETE FROM recording_genres WHERE recording_id NOT IN (
|
||||
SELECT DISTINCT recording_id FROM audio_files
|
||||
)`); err != nil {
|
||||
return nil, fmt.Errorf("could not delete orphaned recording_genres: %w", err)
|
||||
}
|
||||
|
||||
// 8. Delete orphaned release_group_recordings (must run BEFORE
|
||||
// recordings because release_group_recordings.recording_id
|
||||
// references recordings.id).
|
||||
// SAFETY: Hand-crafted orphan cleanup SQL. Parameterless.
|
||||
if _, err := tx.ExecContext(l.ctx,
|
||||
`DELETE FROM release_group_recordings WHERE recording_id NOT IN (
|
||||
SELECT DISTINCT recording_id FROM audio_files
|
||||
)`); err != nil {
|
||||
return nil, fmt.Errorf("could not delete orphaned release_group_recordings: %w", err)
|
||||
}
|
||||
|
||||
// 9. Delete orphaned recordings (safe now that child tables are cleaned).
|
||||
// SAFETY: Hand-crafted orphan cleanup SQL. Reference-counting delete
|
||||
// with NOT IN subquery unsupported by sqlc. No user input.
|
||||
if _, err := tx.ExecContext(l.ctx,
|
||||
@@ -298,24 +319,6 @@ func (l *Library) RemoveLibrary(id int64) (*RemovalSummary, error) {
|
||||
return nil, fmt.Errorf("could not delete orphaned recordings: %w", err)
|
||||
}
|
||||
|
||||
// 8. Delete orphaned recording_genres.
|
||||
// SAFETY: Hand-crafted orphan cleanup SQL. Parameterless.
|
||||
if _, err := tx.ExecContext(l.ctx,
|
||||
`DELETE FROM recording_genres WHERE recording_id NOT IN (
|
||||
SELECT id FROM recordings
|
||||
)`); err != nil {
|
||||
return nil, fmt.Errorf("could not delete orphaned recording_genres: %w", err)
|
||||
}
|
||||
|
||||
// 9. Delete orphaned release_group_recordings.
|
||||
// SAFETY: Hand-crafted orphan cleanup SQL. Parameterless.
|
||||
if _, err := tx.ExecContext(l.ctx,
|
||||
`DELETE FROM release_group_recordings WHERE recording_id NOT IN (
|
||||
SELECT id FROM recordings
|
||||
)`); err != nil {
|
||||
return nil, fmt.Errorf("could not delete orphaned release_group_recordings: %w", err)
|
||||
}
|
||||
|
||||
// 10. Delete orphaned release_groups.
|
||||
// SAFETY: Hand-crafted orphan cleanup SQL. Parameterless.
|
||||
result, err = tx.ExecContext(l.ctx,
|
||||
|
||||
@@ -1456,7 +1456,7 @@ export class ConfigPage extends LitElement {
|
||||
|
||||
this.toastTimer = setTimeout(() => {
|
||||
this.toastVisible = false;
|
||||
}, 4000);
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
private handleConcurrencyChange = (
|
||||
|
||||
+10
-9
@@ -1,9 +1,10 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {sqlcgen} from '../models';
|
||||
import {library} from '../models';
|
||||
import {context} from '../models';
|
||||
|
||||
export function AddLibrary(arg1:string):Promise<library.Info>;
|
||||
export function AddLibrary(arg1:string):Promise<sqlcgen.Library>;
|
||||
|
||||
export function CancelAllScans():Promise<void>;
|
||||
|
||||
@@ -15,8 +16,6 @@ export function FullRescan():Promise<library.ScanMetrics>;
|
||||
|
||||
export function GetAlbumTracks(arg1:number):Promise<Array<library.Track>>;
|
||||
|
||||
export function GetAllLibrariesWithTrackCounts():Promise<Array<library.Info>>;
|
||||
|
||||
export function GetAlbumsByArtist(arg1:number):Promise<Array<library.Album>>;
|
||||
|
||||
export function GetAllAlbums():Promise<Array<library.Album>>;
|
||||
@@ -25,6 +24,8 @@ export function GetAllArtists():Promise<Array<library.Artist>>;
|
||||
|
||||
export function GetAllGenresWithCounts():Promise<Array<library.GenreWithCount>>;
|
||||
|
||||
export function GetAllLibrariesWithTrackCounts():Promise<Array<library.Info>>;
|
||||
|
||||
export function GetAllTracks():Promise<Array<library.Track>>;
|
||||
|
||||
export function GetRemovalImpact(arg1:number):Promise<library.RemovalImpact>;
|
||||
@@ -39,15 +40,13 @@ export function IsScanPaused():Promise<boolean>;
|
||||
|
||||
export function PauseScan():Promise<void>;
|
||||
|
||||
export function RemoveLibrary(arg1:number):Promise<library.RemovalSummary>;
|
||||
|
||||
export function RenameLibrary(arg1:number, arg2:string):Promise<void>;
|
||||
|
||||
export function QueuedLibraryNames():Promise<Array<string>>;
|
||||
|
||||
export function ResumeScan():Promise<void>;
|
||||
export function RemoveLibrary(arg1:number):Promise<library.RemovalSummary>;
|
||||
|
||||
export function Scan():Promise<library.ScanMetrics>;
|
||||
export function RenameLibrary(arg1:number,arg2:string):Promise<void>;
|
||||
|
||||
export function ResumeScan():Promise<void>;
|
||||
|
||||
export function ScanAllLibraries():Promise<void>;
|
||||
|
||||
@@ -57,4 +56,6 @@ export function SearchTracks(arg1:string):Promise<Array<library.Track>>;
|
||||
|
||||
export function SetContext(arg1:context.Context):Promise<void>;
|
||||
|
||||
export function SetRemovalHooks(arg1:library.RemovalHooks):Promise<void>;
|
||||
|
||||
export function SetRescanHooks(arg1:library.RescanHooks):Promise<void>;
|
||||
|
||||
@@ -26,10 +26,6 @@ export function GetAlbumTracks(arg1) {
|
||||
return window['go']['library']['Library']['GetAlbumTracks'](arg1);
|
||||
}
|
||||
|
||||
export function GetAllLibrariesWithTrackCounts() {
|
||||
return window['go']['library']['Library']['GetAllLibrariesWithTrackCounts']();
|
||||
}
|
||||
|
||||
export function GetAlbumsByArtist(arg1) {
|
||||
return window['go']['library']['Library']['GetAlbumsByArtist'](arg1);
|
||||
}
|
||||
@@ -46,6 +42,10 @@ export function GetAllGenresWithCounts() {
|
||||
return window['go']['library']['Library']['GetAllGenresWithCounts']();
|
||||
}
|
||||
|
||||
export function GetAllLibrariesWithTrackCounts() {
|
||||
return window['go']['library']['Library']['GetAllLibrariesWithTrackCounts']();
|
||||
}
|
||||
|
||||
export function GetAllTracks() {
|
||||
return window['go']['library']['Library']['GetAllTracks']();
|
||||
}
|
||||
@@ -74,6 +74,10 @@ export function PauseScan() {
|
||||
return window['go']['library']['Library']['PauseScan']();
|
||||
}
|
||||
|
||||
export function QueuedLibraryNames() {
|
||||
return window['go']['library']['Library']['QueuedLibraryNames']();
|
||||
}
|
||||
|
||||
export function RemoveLibrary(arg1) {
|
||||
return window['go']['library']['Library']['RemoveLibrary'](arg1);
|
||||
}
|
||||
@@ -82,18 +86,10 @@ export function RenameLibrary(arg1, arg2) {
|
||||
return window['go']['library']['Library']['RenameLibrary'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function QueuedLibraryNames() {
|
||||
return window['go']['library']['Library']['QueuedLibraryNames']();
|
||||
}
|
||||
|
||||
export function ResumeScan() {
|
||||
return window['go']['library']['Library']['ResumeScan']();
|
||||
}
|
||||
|
||||
export function Scan() {
|
||||
return window['go']['library']['Library']['Scan']();
|
||||
}
|
||||
|
||||
export function ScanAllLibraries() {
|
||||
return window['go']['library']['Library']['ScanAllLibraries']();
|
||||
}
|
||||
@@ -110,6 +106,10 @@ export function SetContext(arg1) {
|
||||
return window['go']['library']['Library']['SetContext'](arg1);
|
||||
}
|
||||
|
||||
export function SetRemovalHooks(arg1) {
|
||||
return window['go']['library']['Library']['SetRemovalHooks'](arg1);
|
||||
}
|
||||
|
||||
export function SetRescanHooks(arg1) {
|
||||
return window['go']['library']['Library']['SetRescanHooks'](arg1);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,20 @@ export namespace library {
|
||||
this.Name = source["Name"];
|
||||
}
|
||||
}
|
||||
export class GenreWithCount {
|
||||
Name: string;
|
||||
TrackCount: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new GenreWithCount(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.Name = source["Name"];
|
||||
this.TrackCount = source["TrackCount"];
|
||||
}
|
||||
}
|
||||
export class Info {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -58,6 +72,18 @@ export namespace library {
|
||||
this.trackCount = source["trackCount"];
|
||||
}
|
||||
}
|
||||
export class RemovalHooks {
|
||||
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new RemovalHooks(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
|
||||
}
|
||||
}
|
||||
export class RemovalImpact {
|
||||
trackCount: number;
|
||||
playlistsAffected: number;
|
||||
@@ -96,20 +122,6 @@ export namespace library {
|
||||
this.queueItemsRemoved = source["queueItemsRemoved"];
|
||||
}
|
||||
}
|
||||
export class GenreWithCount {
|
||||
Name: string;
|
||||
TrackCount: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new GenreWithCount(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.Name = source["Name"];
|
||||
this.TrackCount = source["TrackCount"];
|
||||
}
|
||||
}
|
||||
export class RescanHooks {
|
||||
|
||||
|
||||
@@ -606,6 +618,48 @@ export namespace queue {
|
||||
|
||||
}
|
||||
|
||||
export namespace sqlcgen {
|
||||
|
||||
export class Library {
|
||||
ID: number;
|
||||
Name: string;
|
||||
Path: string;
|
||||
// Go type: time
|
||||
CreatedAt: any;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Library(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.ID = source["ID"];
|
||||
this.Name = source["Name"];
|
||||
this.Path = source["Path"];
|
||||
this.CreatedAt = this.convertValues(source["CreatedAt"], null);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace tracklist {
|
||||
|
||||
export class Column {
|
||||
|
||||
Vendored
+2
@@ -9,6 +9,8 @@ export function AddTracks(arg1:Array<string>):Promise<void>;
|
||||
|
||||
export function Clear():Promise<void>;
|
||||
|
||||
export function CompactAfterLibraryRemoval():Promise<void>;
|
||||
|
||||
export function CycleRepeat():Promise<void>;
|
||||
|
||||
export function EmitCurrentState():Promise<void>;
|
||||
|
||||
@@ -14,6 +14,10 @@ export function Clear() {
|
||||
return window['go']['queue']['Queue']['Clear']();
|
||||
}
|
||||
|
||||
export function CompactAfterLibraryRemoval() {
|
||||
return window['go']['queue']['Queue']['CompactAfterLibraryRemoval']();
|
||||
}
|
||||
|
||||
export function CycleRepeat() {
|
||||
return window['go']['queue']['Queue']['CycleRepeat']();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user