diff --git a/jmc.xcodeproj/project.pbxproj b/jmc.xcodeproj/project.pbxproj index 9da88fa..7021dfc 100644 --- a/jmc.xcodeproj/project.pbxproj +++ b/jmc.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ 0D51C8A81EAC1646002F6182 /* URLStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D51C8A71EAC1646002F6182 /* URLStringFormatter.swift */; }; 0D51C8AA1EAC1866002F6182 /* TransformerURLStringToURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D51C8A91EAC1866002F6182 /* TransformerURLStringToURL.swift */; }; 0D51C8B21EB01731002F6182 /* JMTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D51C8B11EB01731002F6182 /* JMTabView.swift */; }; + 0D5682731FD8A3AD0031C94B /* AtomicFileMoveOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D5682721FD8A3AD0031C94B /* AtomicFileMoveOperation.swift */; }; 0D58F7491D18E84F001A99F5 /* TableViewYouCanPressSpacebarOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D58F7481D18E84F001A99F5 /* TableViewYouCanPressSpacebarOn.swift */; }; 0D58F74B1D18F778001A99F5 /* SourceListThatYouCanPressSpacebarOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D58F74A1D18F778001A99F5 /* SourceListThatYouCanPressSpacebarOn.swift */; }; 0D592A221EDF6BCE0054D553 /* TrackExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D592A211EDF6BCE0054D553 /* TrackExtension.swift */; }; @@ -276,6 +277,7 @@ 0D51C8A71EAC1646002F6182 /* URLStringFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = URLStringFormatter.swift; path = Backend/Formatters/URLStringFormatter.swift; sourceTree = ""; }; 0D51C8A91EAC1866002F6182 /* TransformerURLStringToURL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TransformerURLStringToURL.swift; path = Backend/Formatters/TransformerURLStringToURL.swift; sourceTree = ""; }; 0D51C8B11EB01731002F6182 /* JMTabView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JMTabView.swift; path = "Other Windows/JMTabView.swift"; sourceTree = ""; }; + 0D5682721FD8A3AD0031C94B /* AtomicFileMoveOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicFileMoveOperation.swift; sourceTree = ""; }; 0D58F7481D18E84F001A99F5 /* TableViewYouCanPressSpacebarOn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TableViewYouCanPressSpacebarOn.swift; path = "Main Interface Components/Main Table View/TableViewYouCanPressSpacebarOn.swift"; sourceTree = ""; }; 0D58F74A1D18F778001A99F5 /* SourceListThatYouCanPressSpacebarOn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SourceListThatYouCanPressSpacebarOn.swift; path = "Main Interface Components/Subview Controllers/SourceListThatYouCanPressSpacebarOn.swift"; sourceTree = ""; }; 0D592A211EDF6BCE0054D553 /* TrackExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TrackExtension.swift; path = "Core Data Shit/TrackExtension.swift"; sourceTree = ""; }; @@ -797,6 +799,7 @@ isa = PBXGroup; children = ( 0D76F6A71D24B9AC00C6E70F /* DatabaseManager.swift */, + 0D5682721FD8A3AD0031C94B /* AtomicFileMoveOperation.swift */, 0DA02D311EF9D41A00DDE5A9 /* MediaMetadataHandler.swift */, 0DAA85891EDFE90E004BF089 /* OrganizationFieldToken.swift */, 0DE486931EA92B78009B226B /* AddFilesQueueLoop.swift */, @@ -1083,6 +1086,7 @@ 0D592A411EDF6C8B0054D553 /* Composer+CoreDataClass.swift in Sources */, 0D0C2AA71E88D8DA00CA82C3 /* MTImageCell.swift in Sources */, 0D2D06D01EE32FB600A8EC66 /* Album+CoreDataProperties.swift in Sources */, + 0D5682731FD8A3AD0031C94B /* AtomicFileMoveOperation.swift in Sources */, 0DA02D321EF9D41A00DDE5A9 /* MediaMetadataHandler.swift in Sources */, 0D447BB91EB2B73F00372A80 /* JMTextField.swift in Sources */, 0D592A421EDF6C8B0054D553 /* Composer+CoreDataProperties.swift in Sources */, diff --git a/jmc.xcodeproj/project.xcworkspace/xcuserdata/johnmoody.xcuserdatad/UserInterfaceState.xcuserstate b/jmc.xcodeproj/project.xcworkspace/xcuserdata/johnmoody.xcuserdatad/UserInterfaceState.xcuserstate index 4d83647..6431fc7 100644 Binary files a/jmc.xcodeproj/project.xcworkspace/xcuserdata/johnmoody.xcuserdatad/UserInterfaceState.xcuserstate and b/jmc.xcodeproj/project.xcworkspace/xcuserdata/johnmoody.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/jmc.xcodeproj/xcuserdata/johnmoody.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/jmc.xcodeproj/xcuserdata/johnmoody.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 9cad73d..a54b1e9 100644 --- a/jmc.xcodeproj/xcuserdata/johnmoody.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/jmc.xcodeproj/xcuserdata/johnmoody.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -20,7 +20,7 @@ ignoreCount = "0" continueAfterRunningActions = "No" filePath = "jmc/Delegate:Main Window Controller/MainWindowController.swift" - timestampString = "534234841.407924" + timestampString = "534310826.455271" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" startingLineNumber = "336" diff --git a/jmc/AlbumFileLocationViewController.swift b/jmc/AlbumFileLocationViewController.swift index fe1f6c1..a34b5da 100644 --- a/jmc/AlbumFileLocationViewController.swift +++ b/jmc/AlbumFileLocationViewController.swift @@ -156,7 +156,6 @@ class AlbumFilePathTree: NSObject { index += 1 DispatchQueue.main.async { visualUpdateHandler?.increment(thingsDone: index) - print("incrementing \(index)") } } } @@ -230,6 +229,7 @@ class AlbumFileLocationViewController: NSViewController, NSOutlineViewDataSource if self.isSearching == true { self.isSearching = false self.outlineView.reloadData() + self.outlineView.expandItem(nil, expandChildren: true) } let nodes = self.masterTree.getNodesForObjects(objects: items) var indexSet = IndexSet() diff --git a/jmc/AtomicFileMoveOperation.swift b/jmc/AtomicFileMoveOperation.swift new file mode 100644 index 0000000..bf736c9 --- /dev/null +++ b/jmc/AtomicFileMoveOperation.swift @@ -0,0 +1,16 @@ +// +// AtomicFileMoveOperation.swift +// jmc +// +// Created by John Moody on 12/6/17. +// Copyright © 2017 John Moody. All rights reserved. +// + +import Cocoa + +class AtomicFileMoveOperation: Operation { + + override func main() { + + } +} diff --git a/jmc/Backend/Database:Filesystem Management/DatabaseManager.swift b/jmc/Backend/Database:Filesystem Management/DatabaseManager.swift index fcacb6e..41be5d1 100644 --- a/jmc/Backend/Database:Filesystem Management/DatabaseManager.swift +++ b/jmc/Backend/Database:Filesystem Management/DatabaseManager.swift @@ -47,6 +47,7 @@ class DatabaseManager: NSObject { let fileManager = FileManager.default var undoFileLocations = [Track : [String]]() var currentTrack: Track? + var consolidationShouldStop: Bool = false func getArtworkFromFile(_ urlString: String) -> Data? { print("checking for art in file") @@ -510,6 +511,88 @@ class DatabaseManager: NSObject { return (mediaURLs, errors) } + func consolidateLibrary(withLocations newLocationDictionary: [NSObject : URL], visualUpdateHandler: ProgressBarController?, moves: Bool) { + //assume called on not-main queue + let subContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + subContext.parent = managedContext + var index = 0 + let count = newLocationDictionary.count + DispatchQueue.main.async { + visualUpdateHandler?.prepareForNewTask(actionName: "Consolidating", thingName: "tracks", thingCount: count) + } + for (object, newURL) in newLocationDictionary { + guard self.consolidationShouldStop != true else { break } + defer { + index += 1 + DispatchQueue.main.async { + visualUpdateHandler?.increment(thingsDone: index) + } + } + let object = object as! NSManagedObject + let subContextObject = subContext.object(with: object.objectID) + guard let oldURL = {() -> URL? in + switch subContextObject { + case let subContextObject as Track: + return URL(string: subContextObject.location ?? "") + case let subContextObject as AlbumFile: + return URL(string: subContextObject.location ?? "") + case let subContextObject as AlbumArtwork: + return URL(string: subContextObject.location ?? "") + default: + return nil + } + }() else { continue } + do { + try fileManager.createDirectory(at: newURL.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil) + } catch { + print(error) + } + do { + switch moves { + case true: + try fileManager.moveItem(at: oldURL, to: newURL) + case false: + try fileManager.copyItem(at: oldURL, to: newURL) + } + } catch { + print(error) + //append error + continue + } + switch subContextObject { + case let subContextObject as Track: + subContextObject.location = newURL.absoluteString + case let subContextObject as AlbumFile: + subContextObject.location = newURL.absoluteString + case let subContextObject as AlbumArtwork: + subContextObject.location = newURL.absoluteString + default: + break + } + do { + try subContext.save() + DispatchQueue.main.async { + do { + try managedContext.save() + } catch { + self.consolidationShouldStop = true + } + } + } catch { + self.consolidationShouldStop = true + continue + } + } + if self.consolidationShouldStop == true { + print("failure") + } else { + print("success") + } + DispatchQueue.main.async { + visualUpdateHandler?.finish() + } + } + func removeNetworkedLibrary(_ library: Library) { removeSource(library: library) } @@ -659,7 +742,7 @@ class DatabaseManager: NSObject { let volumeURL = getVolumeOfURL(url: url) if let existingVolume = addedVolumes[volumeURL] { track.volume = existingVolume - } else if let existingVolume = checkIfVolumeExists(withURL: volumeURL) { + } else if let existingVolume = checkIfVolumeExists(withURL: volumeURL, subcontext: subContext) { track.volume = subContext.object(with: existingVolume.objectID) as! Volume } else { let newVolume = NSEntityDescription.insertNewObject(forEntityName: "Volume", into: subContext) as! Volume @@ -696,7 +779,7 @@ class DatabaseManager: NSObject { let artistCheck = fileMetadataDictionary[kArtistKey] as? String ?? "" if let alreadyAddedArtist = addedArtists[artistCheck] { track.artist = alreadyAddedArtist - } else if let alreadyAddedArtist = checkIfArtistExists(artistCheck) { + } else if let alreadyAddedArtist = checkIfArtistExists(artistCheck, subcontext: subContext) { track.artist = subContext.object(with: alreadyAddedArtist.objectID) as! Artist } else { let newArtist = NSEntityDescription.insertNewObject(forEntityName: "Artist", into: subContext) as! Artist @@ -710,7 +793,7 @@ class DatabaseManager: NSObject { let albumCheck = fileMetadataDictionary[kAlbumKey] as? String ?? "" if let alreadyAddedAlbum = addedAlbums[track.artist!]?[albumCheck] { track.album = alreadyAddedAlbum - } else if let alreadyAddedAlbum = checkIfAlbumExists(withName: albumCheck, withArtist: track.artist!) { + } else if let alreadyAddedAlbum = checkIfAlbumExists(withName: albumCheck, withArtist: track.artist!, subcontext: subContext) { track.album = subContext.object(with: alreadyAddedAlbum.objectID) as! Album } else { let newAlbum = NSEntityDescription.insertNewObject(forEntityName: "Album", into: subContext) as! Album @@ -734,7 +817,7 @@ class DatabaseManager: NSObject { if let composerCheck = fileMetadataDictionary[kComposerKey] as? String { if let alreadyAddedComposer = addedComposers[composerCheck] { track.composer = alreadyAddedComposer - } else if let alreadyAddedComposer = checkIfComposerExists(composerCheck) { + } else if let alreadyAddedComposer = checkIfComposerExists(composerCheck, subcontext: subContext) { track.composer = subContext.object(with: alreadyAddedComposer.objectID) as! Composer } else { let newComposer = NSEntityDescription.insertNewObject(forEntityName: "Composer", into: subContext) as! Composer diff --git a/jmc/Backend/GlobalFunctions.swift b/jmc/Backend/GlobalFunctions.swift index 5710e9b..b125471 100644 --- a/jmc/Backend/GlobalFunctions.swift +++ b/jmc/Backend/GlobalFunctions.swift @@ -253,12 +253,12 @@ func getVolumeOfURL(url: URL) -> URL { } } -func checkIfVolumeExists(withURL url: URL) -> Volume? { +func checkIfVolumeExists(withURL url: URL, subcontext: NSManagedObjectContext? = nil) -> Volume? { let fetch = NSFetchRequest(entityName: "Volume") let predicate = NSPredicate(format: "location == %@", url.absoluteString) fetch.predicate = predicate do { - let results = try managedContext.fetch(fetch) + let results = subcontext != nil ? (try subcontext!.fetch(fetch)) : (try managedContext.fetch(fetch)) if results.count > 0 { return results[0] } else { @@ -503,12 +503,12 @@ var jmcUnknownComposer = {() -> Composer in } } -func checkIfArtistExists(_ name: String) -> Artist? { +func checkIfArtistExists(_ name: String, subcontext: NSManagedObjectContext? = nil) -> Artist? { let request = NSFetchRequest(entityName: "Artist") let predicate = NSPredicate(format: "name == %@", name) request.predicate = predicate do { - let result = try managedContext.fetch(request) as! [Artist] + let result = subcontext != nil ? (try subcontext!.fetch(request) as! [Artist]) : (try managedContext.fetch(request) as! [Artist]) if result.count > 0 { return result[0] } else { @@ -520,12 +520,12 @@ func checkIfArtistExists(_ name: String) -> Artist? { } } -func checkIfAlbumExists(withName name: String, withArtist artist: Artist) -> Album? { +func checkIfAlbumExists(withName name: String, withArtist artist: Artist, subcontext: NSManagedObjectContext? = nil) -> Album? { let request = NSFetchRequest(entityName: "Album") let predicate = NSPredicate(format: "name == %@ and album_artist == %@", name, artist) request.predicate = predicate do { - let result = try managedContext.fetch(request) as! [Album] + let result = subcontext != nil ? (try subcontext!.fetch(request) as! [Album]) : (try managedContext.fetch(request) as! [Album]) if result.count > 0 { return result[0] } else { @@ -537,12 +537,12 @@ func checkIfAlbumExists(withName name: String, withArtist artist: Artist) -> Alb } } -func checkIfComposerExists(_ name: String) -> Composer? { +func checkIfComposerExists(_ name: String, subcontext: NSManagedObjectContext? = nil) -> Composer? { let request = NSFetchRequest(entityName: "Composer") let predicate = NSPredicate(format: "name == %@", name) request.predicate = predicate do { - let result = try managedContext.fetch(request) as! [Composer] + let result = subcontext != nil ? (try subcontext!.fetch(request) as! [Composer]) : (try managedContext.fetch(request) as! [Composer]) if result.count > 0 { return result[0] } else { @@ -554,12 +554,12 @@ func checkIfComposerExists(_ name: String) -> Composer? { } } -func checkIfCachedOrderExists(_ name: String) -> CachedOrder? { +func checkIfCachedOrderExists(_ name: String, subcontext: NSManagedObjectContext? = nil) -> CachedOrder? { let request = NSFetchRequest(entityName: "CachedOrder") let predicate = NSPredicate(format: "order == %@", name) request.predicate = predicate do { - let result = try managedContext.fetch(request) as! [CachedOrder] + let result = subcontext != nil ? (try subcontext!.fetch(request) as! [CachedOrder]) : (try managedContext.fetch(request) as! [CachedOrder]) if result.count > 0 { return result[0] } else { diff --git a/jmc/Delegate:Main Window Controller/MainWindowController.swift b/jmc/Delegate:Main Window Controller/MainWindowController.swift index 630183e..047046d 100644 --- a/jmc/Delegate:Main Window Controller/MainWindowController.swift +++ b/jmc/Delegate:Main Window Controller/MainWindowController.swift @@ -332,11 +332,11 @@ class MainWindowController: NSWindowController, NSSearchFieldDelegate, NSWindowD self.skipBackward() } @IBAction func tempBreak(_ sender: AnyObject) { - //self.skip() - print("dongels") + self.skip() + /*print("dongels") let fr = NSFetchRequest(entityName: "Track") let result = try? managedContext.fetch(fr) - print(result?.count) + print(result?.count)*/ } @IBAction func addPlaylistButton(_ sender: AnyObject) { sourceListViewController!.createPlaylist(nil, smart_criteria: nil) diff --git a/jmc/Other Windows/EqualizerWindowController.xib b/jmc/Other Windows/EqualizerWindowController.xib index d3c77d6..797e936 100644 --- a/jmc/Other Windows/EqualizerWindowController.xib +++ b/jmc/Other Windows/EqualizerWindowController.xib @@ -1,8 +1,9 @@ - + - + + @@ -34,286 +35,261 @@ - + - - + - + + + - + - + + + - + - - + - + + + - + - + + + - + - + + + - + - - + - + + + - + - - + - - + - - + - - + - - + - - + - - + - - - - + + - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - - - + + - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - - + - + + + @@ -332,6 +310,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jmc/Other Windows/Library Manager/Sheets/ConsolidateLibrarySheetController.swift b/jmc/Other Windows/Library Manager/Sheets/ConsolidateLibrarySheetController.swift index e503136..6aac317 100644 --- a/jmc/Other Windows/Library Manager/Sheets/ConsolidateLibrarySheetController.swift +++ b/jmc/Other Windows/Library Manager/Sheets/ConsolidateLibrarySheetController.swift @@ -28,6 +28,9 @@ class ConsolidateLibrarySheetController: NSWindowController, ProgressBarControll var things = [NSObject : URL]() @IBOutlet weak var progressBar: NSProgressIndicator! @IBOutlet weak var progressTextLabel: NSTextField! + var progressSheet: GenericProgressBarSheetController? + var databaseManager = DatabaseManager() + var moves = true var libraryManager: LibraryManagerViewController? @@ -38,17 +41,6 @@ class ConsolidateLibrarySheetController: NSWindowController, ProgressBarControll @IBAction func radioActio(_ sender: Any) { } - - @IBAction func consolidatePressed(_ sender: Any) { - /*let selectedTracks = self.postConsolidationFileViewController?.trackViewArrayController.selectedObjects as! [DisparateTrack] - self.libraryManager?.databaseManager.batchMoveTracks(tracks: selectedTracks.map({return $0.track}), visualUpdateHandler: self) - self.progressBar.isHidden = false - self.progressTextLabel.isHidden = false - self.progressBar.maxValue = Double(selectedTracks.count) - self.progressTextLabel.stringValue = "Consolidating tracks..." - self.thingCount = selectedTracks.count*/ - - } func prepareForNewTask(actionName: String, thingName: String, thingCount: Int) { @@ -93,6 +85,25 @@ class ConsolidateLibrarySheetController: NSWindowController, ProgressBarControll self.targetView.addArrangedSubview(self.postConsolidationFileViewController.view) self.postConsolidationFileViewController.setupForNewLocations() self.postConsolidationFileViewController.parentController = self + } + + @IBAction func consolidatePressed(_ sender: Any) { + let alert = NSAlert() + alert.addButton(withTitle: "OK") + alert.addButton(withTitle: "Cancel") + alert.informativeText = "Are you sure you want to consolidate your music library? This operation cannot be undone." + let response = alert.runModal() + if response == NSApplication.ModalResponse.alertFirstButtonReturn { + print("consolidating library") + self.progressSheet = GenericProgressBarSheetController(windowNibName: .init("GenericProgressBarSheetController")) + self.window?.beginSheet(self.progressSheet!.window!, completionHandler: nil) + DispatchQueue.global(qos: .default).async { + self.databaseManager.consolidateLibrary(withLocations: self.postConsolidationFileViewController.masterTree.rootNode.objectPathDictionaryIfRoot!, visualUpdateHandler: self.progressSheet, moves: self.moves) + DispatchQueue.main.async { + self.window?.close() + } + } + } } diff --git a/jmc/Other Windows/Library Manager/Sheets/ConsolidateLibrarySheetController.xib b/jmc/Other Windows/Library Manager/Sheets/ConsolidateLibrarySheetController.xib index 2d8ce53..019a983 100644 --- a/jmc/Other Windows/Library Manager/Sheets/ConsolidateLibrarySheetController.xib +++ b/jmc/Other Windows/Library Manager/Sheets/ConsolidateLibrarySheetController.xib @@ -23,16 +23,6 @@ - - - + + + - + + + - + + + -