Skip to content

Commit

Permalink
Cleanup operation that runs on startup
Browse files Browse the repository at this point in the history
Tries to delete debris from Core Data store that could pose problems.

More operations can be added.

Fixes GH-210
  • Loading branch information
NattyNarwhal committed Jun 12, 2024
1 parent 5cbd481 commit b84a0b9
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Submariner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
3EC5A6452A019B5C00025812 /* SBRepeatModeButtonStateTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EC5A6442A019B5C00025812 /* SBRepeatModeButtonStateTransformer.swift */; };
3ECF63FA280362BA004F9176 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3ECF63F9280362BA004F9176 /* Assets.xcassets */; };
3ED4C4D42AC0E25400649FB2 /* SBLibraryPurgeOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED4C4D32AC0E25400649FB2 /* SBLibraryPurgeOperation.swift */; };
3EE4C1882C18EB780063BB9D /* SBLibraryCleanupOrphansOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EE4C1872C18EB780063BB9D /* SBLibraryCleanupOrphansOperation.swift */; };
3EF978022BC3C4E300C986E9 /* SBMessageTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF978012BC3C4E300C986E9 /* SBMessageTextView.swift */; };
3EF978042BC4659B00C986E9 /* Binding+Nil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF978032BC4659B00C986E9 /* Binding+Nil.swift */; };
3EF978062BC49A9100C986E9 /* SBPropertyFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF978052BC49A9100C986E9 /* SBPropertyFieldView.swift */; };
Expand Down Expand Up @@ -267,6 +268,7 @@
3EC5A6442A019B5C00025812 /* SBRepeatModeButtonStateTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBRepeatModeButtonStateTransformer.swift; sourceTree = "<group>"; };
3ECF63F9280362BA004F9176 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
3ED4C4D32AC0E25400649FB2 /* SBLibraryPurgeOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBLibraryPurgeOperation.swift; sourceTree = "<group>"; };
3EE4C1872C18EB780063BB9D /* SBLibraryCleanupOrphansOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBLibraryCleanupOrphansOperation.swift; sourceTree = "<group>"; };
3EF978012BC3C4E300C986E9 /* SBMessageTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBMessageTextView.swift; sourceTree = "<group>"; };
3EF978032BC4659B00C986E9 /* Binding+Nil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Nil.swift"; sourceTree = "<group>"; };
3EF978052BC49A9100C986E9 /* SBPropertyFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBPropertyFieldView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -706,6 +708,7 @@
3E702DE82A428CF6005F7184 /* SBSubsonicParsingOperation.swift */,
3ED4C4D32AC0E25400649FB2 /* SBLibraryPurgeOperation.swift */,
3E2E1C832A395B79001A3148 /* SBSubsonicRequestOperation.swift */,
3EE4C1872C18EB780063BB9D /* SBLibraryCleanupOrphansOperation.swift */,
);
name = Operations;
sourceTree = "<group>";
Expand Down Expand Up @@ -917,6 +920,7 @@
3E32BE502B8D9A5C00E77CF0 /* SBTableView+DragImage.swift in Sources */,
4C7AA24D139D64930050BE95 /* SBServerLibraryController.m in Sources */,
4CFAFC2C139EF08800E82B57 /* SBServerHomeController.m in Sources */,
3EE4C1882C18EB780063BB9D /* SBLibraryCleanupOrphansOperation.swift in Sources */,
3EF978042BC4659B00C986E9 /* Binding+Nil.swift in Sources */,
3E04F5F22B71E33000E24E56 /* SBServerDirectoryController.swift in Sources */,
4CFAFC37139EF20600E82B57 /* MGScopeBar.m in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Submariner/SBAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, catego
self.managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
self.managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator

// #MARK: Run cleanup steps
let cleanupOperation = SBLibraryCleanupOrphansOperation(managedObjectContext: self.managedObjectContext)
OperationQueue.sharedServerQueue.addOperation(cleanupOperation)

// #MARK: Init Window Controllers
self.databaseController = SBDatabaseController(managedObjectContext: self.managedObjectContext)
self.preferencesController = SBPreferencesController()
Expand Down
81 changes: 81 additions & 0 deletions Submariner/SBLibraryCleanupOrphansOperation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// SBLibraryCleanupOrphansOperation.swift
// Submariner
//
// Created by Calvin Buckley on 2024-06-11.
//
// Copyright (c) 2024 Calvin Buckley
// SPDX-License-Identifier: BSD-3-Clause
//

import Cocoa
import os

fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "SBLibraryCleanupOrphansOperation")

class SBLibraryCleanupOrphansOperation: SBOperation {
init(managedObjectContext: NSManagedObjectContext) {
super.init(managedObjectContext: managedObjectContext, name: "Deleting Orphaned Objects")
}

override func main() {
defer {
saveThreadedContext()
finish()
}
logger.info("Deleting orphan playlists")
cleanupOrphanPlaylists()
logger.info("Deleting orphan covers")
cleanupOrphanCovers()
// XXX: Do tracks/albums/artists have similar issues?
}

private func cleanupOrphanPlaylists() {
// look for orphaned playlists as part of a delete
let fetchRequest: NSFetchRequest<SBPlaylist> = SBPlaylist.fetchRequest()
// Server playlists have a server relation, local playlists are in the playlist SBSection
fetchRequest.predicate = NSPredicate(format: "(server == nil) && (section == nil)")
if let playlists = try? threadedContext.fetch(fetchRequest) {
for playlist in playlists {
let name = playlist.resourceName ?? "<nil>"
logger.info("Deleting orphan playlist \"\(name, privacy: .public)\"")
self.threadedContext.delete(playlist)
}
}
}

private func otherCoversUsingFile(_ cover: SBCover) -> Bool {
// imagePath returns absolute paths, but play it safe. perhaps we can clean up file orphans not in the DB later
guard let imageFile = cover.imagePath?.lastPathComponent else {
return false
}

let fetchRequest: NSFetchRequest<SBCover> = SBCover.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "(imagePath ENDSWITH %@)", imageFile)
if let covers = try? threadedContext.fetch(fetchRequest) {
logger.debug("\(covers.count) covers with the filename \(imageFile) found")
return covers.count > 1
}

return false
}

private func cleanupOrphanCovers() {
let fetchRequest: NSFetchRequest<SBCover> = SBCover.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "(track == nil) && (album == nil)")
if let covers = try? threadedContext.fetch(fetchRequest) {
for cover in covers {
let name = cover.itemId ?? "<nil>"
logger.info("Deleting orphan cover \"\(name, privacy: .public)\"")
if let path = cover.imagePath as? String, FileManager.default.fileExists(atPath: path),
!otherCoversUsingFile(cover) {
logger.warning("Should delete orphan cover file at \(path)")
// safe to delete - we avoid deleting if any duplicate filename could possibly exist.
// won't get it all, but avoids damage
try? FileManager.default.removeItem(atPath: path)
}
self.threadedContext.delete(cover)
}
}
}
}

0 comments on commit b84a0b9

Please sign in to comment.