Updated for OS X 10.11 and Swift 2
Added more descriptive errors.
This commit is contained in:
committed by
Kim Wittenburg
parent
45f664cb10
commit
80f177807d
@@ -57,7 +57,7 @@ internal class MainViewController: NSViewController {
|
||||
private let urlSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: nil, delegateQueue: NSOperationQueue.mainQueue())
|
||||
|
||||
/// The URL task currently loading the search results
|
||||
private var searchTask: NSURLSessionDataTask?
|
||||
private var searchTask: NSURLSessionTask?
|
||||
|
||||
/// If `true` the search section is displayed at the top of the
|
||||
/// `outlineView`.
|
||||
@@ -77,7 +77,7 @@ internal class MainViewController: NSViewController {
|
||||
/// Errors that occured during loading the tracks for the respective album.
|
||||
private var trackErrors = [Album: NSError]()
|
||||
|
||||
// MARK: Overrides
|
||||
// MARK: View Life Cycle
|
||||
|
||||
override internal func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -109,7 +109,7 @@ internal class MainViewController: NSViewController {
|
||||
if showsSearch {
|
||||
contents.append(OutlineViewConstants.Items.searchResultsHeaderItem)
|
||||
if !searchResults.isEmpty {
|
||||
contents.extend(searchResults as [AnyObject])
|
||||
contents.appendContentsOf(searchResults as [AnyObject])
|
||||
} else if searching {
|
||||
contents.append(OutlineViewConstants.Items.loadingItem)
|
||||
} else if let error = searchError {
|
||||
@@ -121,10 +121,10 @@ internal class MainViewController: NSViewController {
|
||||
contents.append(OutlineViewConstants.Items.albumsHeaderItem)
|
||||
}
|
||||
}
|
||||
contents.extend(albums as [AnyObject])
|
||||
contents.appendContentsOf(albums as [AnyObject])
|
||||
if !unsortedTracks.isEmpty {
|
||||
contents.append(OutlineViewConstants.Items.unsortedTracksHeaderItem)
|
||||
contents.extend(unsortedTracks as [AnyObject])
|
||||
contents.appendContentsOf(unsortedTracks as [AnyObject])
|
||||
}
|
||||
return contents
|
||||
}
|
||||
@@ -190,8 +190,7 @@ internal class MainViewController: NSViewController {
|
||||
|
||||
/// Starts a search for the specified search term. Calling this method
|
||||
internal func beginSearchForTerm(term: String) {
|
||||
searchTask?.cancel()
|
||||
searchResults.removeAll()
|
||||
cancelSearch()
|
||||
if let url = iTunesAPI.createAlbumSearchURLForTerm(term) {
|
||||
showsSearch = true
|
||||
searchTask = urlSession.dataTaskWithURL(url, completionHandler: processSearchResults)
|
||||
@@ -202,24 +201,39 @@ internal class MainViewController: NSViewController {
|
||||
outlineView.reloadData()
|
||||
}
|
||||
|
||||
/// Cancels the current search (if there is one). This also hides the search
|
||||
/// results.
|
||||
internal func cancelSearch() {
|
||||
searchTask?.cancel()
|
||||
searchResults.removeAll()
|
||||
showsSearch = false
|
||||
outlineView.reloadData()
|
||||
}
|
||||
|
||||
/// Processes the data returned from a network request into the
|
||||
/// `searchResults`.
|
||||
private func processSearchResults(data: NSData?, response: NSURLResponse?, error: NSError?) {
|
||||
private func processSearchResults(data: NSData?, response: NSURLResponse?, var error: NSError?) {
|
||||
searchTask = nil
|
||||
if let theError = error {
|
||||
searchError = theError
|
||||
} else if let theData = data {
|
||||
if let theData = data where error == nil {
|
||||
do {
|
||||
let searchResults = try iTunesAPI.parseAPIData(theData).map { SearchResult(representedAlbum: $0) }
|
||||
self.searchResults = searchResults
|
||||
} catch let error as NSError {
|
||||
searchError = error
|
||||
} catch let theError as NSError {
|
||||
error = theError
|
||||
}
|
||||
}
|
||||
if let theError = error {
|
||||
searchErrorOccured(theError)
|
||||
}
|
||||
showsSearch = true
|
||||
outlineView.reloadData()
|
||||
}
|
||||
|
||||
/// Called when an error occurs during searching.
|
||||
private func searchErrorOccured(error: NSError) {
|
||||
searchError = error
|
||||
}
|
||||
|
||||
/// Adds the search result at the specified `row` to the albums section and
|
||||
/// begins loading its tracks.
|
||||
internal func selectSearchResultAtRow(row: Int) {
|
||||
@@ -251,7 +265,7 @@ internal class MainViewController: NSViewController {
|
||||
let task = urlSession.dataTaskWithURL(url) { (data, response, var error) -> Void in
|
||||
self.trackTasks[album] = nil
|
||||
do {
|
||||
if let theData = data {
|
||||
if let theData = data where error == nil {
|
||||
let newAlbum = try iTunesAPI.parseAPIData(theData)[0]
|
||||
let index = self.albums.indexOf(album)!
|
||||
self.albums.removeAtIndex(index)
|
||||
@@ -262,6 +276,7 @@ internal class MainViewController: NSViewController {
|
||||
} catch _ {
|
||||
// Will never happen
|
||||
}
|
||||
|
||||
self.trackErrors[album] = error
|
||||
self.outlineView.reloadData()
|
||||
}
|
||||
@@ -273,6 +288,7 @@ internal class MainViewController: NSViewController {
|
||||
func cancelLoadingTracksForAlbum(album: Album) {
|
||||
trackTasks[album]?.cancel()
|
||||
trackTasks[album] = nil
|
||||
trackErrors[album] = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
|
||||
}
|
||||
|
||||
private func saveTracks(tracks: [Track: [iTunesTrack]]) {
|
||||
@@ -284,6 +300,9 @@ internal class MainViewController: NSViewController {
|
||||
NSProgress.currentProgress()?.localizedAdditionalDescription = progress.localizedAdditionalDescription
|
||||
for (parentTrack, targetTracks) in tracks {
|
||||
for track in targetTracks {
|
||||
if progress.cancelled {
|
||||
return
|
||||
}
|
||||
parentTrack.saveToTrack(track)
|
||||
++progress.completedUnitCount
|
||||
NSProgress.currentProgress()?.localizedAdditionalDescription = progress.localizedAdditionalDescription
|
||||
@@ -302,6 +321,9 @@ internal class MainViewController: NSViewController {
|
||||
NSProgress.currentProgress()?.localizedDescription = NSLocalizedString("Saving artworks…", comment: "Alert message indicating that the artworks for the selected tracks are currently being saved")
|
||||
NSProgress.currentProgress()?.localizedAdditionalDescription = progress.localizedAdditionalDescription
|
||||
for album in albums {
|
||||
if progress.cancelled {
|
||||
return
|
||||
}
|
||||
do {
|
||||
try album.saveArtwork()
|
||||
} catch _ {
|
||||
@@ -338,7 +360,7 @@ internal class MainViewController: NSViewController {
|
||||
alert.beginSheetModalForWindow(view.window!, completionHandler: nil)
|
||||
} else if let selection = iTunes.selection.get() as? [iTunesTrack] {
|
||||
let newTracks = Set(selection).subtract(allITunesTracks)
|
||||
unsortedTracks.extend(newTracks)
|
||||
unsortedTracks.appendContentsOf(newTracks)
|
||||
outlineView.reloadData()
|
||||
}
|
||||
}
|
||||
@@ -413,6 +435,7 @@ internal class MainViewController: NSViewController {
|
||||
if sectionOfRow(row)! != .SearchResults {
|
||||
if let album = item as? Album {
|
||||
cancelLoadingTracksForAlbum(album)
|
||||
trackErrors[album] = nil
|
||||
albums.removeElement(album)
|
||||
} else if let track = item as? Track {
|
||||
track.associatedTracks = []
|
||||
@@ -458,11 +481,47 @@ internal class MainViewController: NSViewController {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Error Handling
|
||||
|
||||
extension MainViewController {
|
||||
|
||||
override internal func willPresentError(error: NSError) -> NSError {
|
||||
let recoveryOptions = [
|
||||
NSLocalizedString("OK", comment: "Button title"),
|
||||
NSLocalizedString("Try Again", comment: "Button title for error alerts offering the user to try again.")
|
||||
]
|
||||
return DescriptiveError(underlyingError: error, userInfo: [NSRecoveryAttempterErrorKey: self, NSLocalizedRecoveryOptionsErrorKey: recoveryOptions])
|
||||
}
|
||||
|
||||
override internal func attemptRecoveryFromError(error: NSError, optionIndex recoveryOptionIndex: Int, delegate: AnyObject?, didRecoverSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {
|
||||
let didRecover = attemptRecoveryFromError(error, optionIndex: recoveryOptionIndex)
|
||||
// TODO: Notify the delegate
|
||||
}
|
||||
|
||||
override internal func attemptRecoveryFromError(error: NSError, optionIndex recoveryOptionIndex: Int) -> Bool {
|
||||
if recoveryOptionIndex == 0 {
|
||||
return true
|
||||
}
|
||||
// TODO: Implementation
|
||||
if error == searchError {
|
||||
|
||||
} else {
|
||||
for (album, trackError) in trackErrors {
|
||||
if error == trackError {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - User Interface Validations
|
||||
|
||||
extension MainViewController: NSUserInterfaceValidations {
|
||||
|
||||
func validateUserInterfaceItem(anItem: NSValidatedUserInterfaceItem) -> Bool {
|
||||
internal func validateUserInterfaceItem(anItem: NSValidatedUserInterfaceItem) -> Bool {
|
||||
if anItem.action() == "performSave:" {
|
||||
for row in outlineView.selectedRowIndexes {
|
||||
return sectionOfRow(row) == .Albums
|
||||
@@ -488,7 +547,7 @@ extension MainViewController: NSUserInterfaceValidations {
|
||||
|
||||
extension MainViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
|
||||
internal func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
|
||||
if item == nil {
|
||||
return outlineViewContents.count
|
||||
} else if let album = item as? Album {
|
||||
@@ -500,7 +559,7 @@ extension MainViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
|
||||
internal func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
|
||||
if item == nil {
|
||||
return outlineViewContents[index]
|
||||
} else if let album = item as? Album {
|
||||
@@ -512,19 +571,19 @@ extension MainViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
|
||||
internal func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
|
||||
return self.outlineView(outlineView, numberOfChildrenOfItem: item) > 0
|
||||
}
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, isGroupItem item: AnyObject) -> Bool {
|
||||
internal func outlineView(outlineView: NSOutlineView, isGroupItem item: AnyObject) -> Bool {
|
||||
return Section.isHeaderItem(item)
|
||||
}
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, shouldSelectItem item: AnyObject) -> Bool {
|
||||
internal func outlineView(outlineView: NSOutlineView, shouldSelectItem item: AnyObject) -> Bool {
|
||||
return !(self.outlineView(outlineView, isGroupItem: item) || item === OutlineViewConstants.Items.loadingItem || item === OutlineViewConstants.Items.noResultsItem || item is NSError)
|
||||
}
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat {
|
||||
internal func outlineView(outlineView: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat {
|
||||
if item is Album || item is SearchResult {
|
||||
return 39
|
||||
} else if let track = item as? Track {
|
||||
@@ -540,7 +599,7 @@ extension MainViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
|
||||
internal func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
|
||||
if item === OutlineViewConstants.Items.searchResultsHeaderItem {
|
||||
var view = outlineView.makeViewWithIdentifier(OutlineViewConstants.ViewIdentifiers.simpleTableCellViewIdentifier, owner: nil) as? AdvancedTableCellView
|
||||
if view == nil {
|
||||
@@ -653,7 +712,7 @@ extension MainViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
return nil
|
||||
}
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, writeItems items: [AnyObject], toPasteboard pasteboard: NSPasteboard) -> Bool {
|
||||
internal func outlineView(outlineView: NSOutlineView, writeItems items: [AnyObject], toPasteboard pasteboard: NSPasteboard) -> Bool {
|
||||
var rows = [Int]()
|
||||
var containsValidItems = false
|
||||
for item in items {
|
||||
@@ -672,7 +731,7 @@ extension MainViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
return true
|
||||
}
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: AnyObject?, proposedChildIndex index: Int) -> NSDragOperation {
|
||||
internal func outlineView(outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: AnyObject?, proposedChildIndex index: Int) -> NSDragOperation {
|
||||
let firstUnsortedRow = outlineViewContents.count - (unsortedTracks.isEmpty ? 0 : unsortedTracks.count+1)
|
||||
// Drop in the 'unsorted' section
|
||||
if item == nil && index >= firstUnsortedRow || item === OutlineViewConstants.Items.unsortedTracksHeaderItem {
|
||||
@@ -698,7 +757,7 @@ extension MainViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
return .Every
|
||||
}
|
||||
|
||||
func outlineView(outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: AnyObject?, childIndex index: Int) -> Bool {
|
||||
internal func outlineView(outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: AnyObject?, childIndex index: Int) -> Bool {
|
||||
guard let data = info.draggingPasteboard().dataForType(OutlineViewConstants.pasteboardType), draggedRows = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [Int] else {
|
||||
return false
|
||||
}
|
||||
@@ -728,7 +787,7 @@ extension MainViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
|
||||
// Add the dragged tracks to the new target
|
||||
if let targetTrack = item as? Track {
|
||||
targetTrack.associatedTracks.extend(draggedTracks)
|
||||
targetTrack.associatedTracks.appendContentsOf(draggedTracks)
|
||||
} else if let targetAlbum = item as? Album {
|
||||
for draggedTrack in draggedTracks {
|
||||
var inserted = false
|
||||
@@ -744,7 +803,7 @@ extension MainViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unsortedTracks.extend(draggedTracks)
|
||||
unsortedTracks.appendContentsOf(draggedTracks)
|
||||
}
|
||||
outlineView.reloadData()
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user