// // Track.swift // Tag for iTunes // // Created by Kim Wittenburg on 30.05.15. // Copyright (c) 2015 Kim Wittenburg. All rights reserved. // import Cocoa /// Represents a track of the /// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html). public class Track: iTunesType { // MARK: Types public enum Tag: String { case Name = "name", Artist = "artist", Year = "year", TrackNumber = "trackNumber", TrackCount = "trackCount", DiscNumber = "discNumber", DiscCount = "discCount", Genre = "genre", AlbumName = "album", AlbumArtist = "albumArtist" case SortName = "sortName", SortArtist = "sortArtist", SortAlbumName = "sortAlbum", SortAlbumArtist = "sortAlbumArtist", Composer = "composer", SortComposer = "sortComposer", Comment = "comment" /// Returns `true` for tags that are returned from the /// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html). public var isReturnedBySearchAPI: Bool { switch self { case .Name, .Artist, .Year, .TrackNumber, .TrackCount, .DiscNumber, .DiscCount, .Genre, .AlbumName, .AlbumArtist: return true default: return false } } /// Returns a string identifying the respective tag that can be displayed /// to the user. public var localizedName: String { return NSLocalizedString(self.rawValue, comment: "") } /// Returns the object that should be saved to *clear* the tag. public var clearedValue: AnyObject? { switch self { case .Year, .TrackNumber, .TrackCount, .DiscNumber, .DiscCount: return "" default: return "" } } /// Returns an array of all tags. public static var allTags: [Tag] { return [.Name, .Artist, .Year, .TrackNumber, .TrackCount, .DiscNumber, .DiscCount, .Genre, .AlbumName, .AlbumArtist, .SortName, .SortArtist, .SortAlbumName, .SortAlbumArtist, .Composer, .SortComposer, .Comment] } } // MARK: Properties public let id: iTunesId public let name: String public let censoredName: String public let artistName: String public let releaseDate: NSDate public let trackNumber: Int public let trackCount: Int public let discNumber: Int public let discCount: Int public let genre: String public internal(set) weak var album: Album! /// These tracks will be changed, if `save()` is called. public var associatedTracks = [iTunesTrack]() // MARK: Initializers public required init(data: [iTunesAPI.Field: AnyObject]) { id = data[.TrackId] as! UInt name = data[.TrackName] as! String censoredName = data[.TrackCensoredName] as! String artistName = data[.ArtistName] as! String releaseDate = iTunesAPI.sharedDateFormatter.dateFromString(data[.ReleaseDate] as! String)! trackNumber = data[.TrackNumber] as! Int trackCount = data[.TrackCount] as! Int discNumber = data[.DiscNumber] as! Int discCount = data[.DiscCount] as! Int genre = data[.PrimaryGenreName] as! String } public static var requiredFields: [iTunesAPI.Field] { return [.TrackId, .TrackName, .TrackCensoredName, .ArtistName, .ReleaseDate, .TrackNumber, .TrackCount, .DiscNumber, .DiscCount, .PrimaryGenreName] } // MARK: Methods /// Saves the track. This means that its properties are applied to every /// `iTunesTrack` in its `associatedTracks`. /// /// This method respects the user's preferences (See `Preferences` class). public func save() { saveToTracks(associatedTracks) } /// Applies the receiver's properties to the specified tracks. /// /// This method respects the user's preferences (See `Preferences` class). public func saveToTracks(tracks: [iTunesTrack]) { for track in tracks { saveToTrack(track) } } /// Applies the receiver's properties to the specified `track`. /// /// This method respects the user's preferences (See `Preferences` class). public func saveToTrack(track: iTunesTrack) { let components = NSCalendar.currentCalendar().components(.Year, fromDate: releaseDate) let trackName = Preferences.sharedPreferences.useCensoredNames ? censoredName : name saveTag(.Name, toTrack: track, value: trackName) saveTag(.Artist, toTrack: track, value: artistName) saveTag(.Year, toTrack: track, value: components.year) saveTag(.TrackNumber, toTrack: track, value: trackNumber) saveTag(.TrackCount, toTrack: track, value: trackCount) saveTag(.DiscNumber, toTrack: track, value: discNumber) saveTag(.DiscCount, toTrack: track, value: discCount) saveTag(.Genre, toTrack: track, value: genre) let albumName = Preferences.sharedPreferences.useCensoredNames ? album.censoredName : album.name saveTag(.AlbumName, toTrack: track, value: albumName) saveTag(.AlbumArtist, toTrack: track, value: album.artistName) saveTag(.SortName, toTrack: track, value: nil) saveTag(.SortArtist, toTrack: track, value: nil) saveTag(.SortAlbumName, toTrack: track, value: nil) saveTag(.SortAlbumArtist, toTrack: track, value: nil) saveTag(.Composer, toTrack: track, value: nil) saveTag(.SortComposer, toTrack: track, value: nil) saveTag(.Comment, toTrack: track, value: nil) // TODO: Deal with artworks // if Preferences.sharedPreferences.clearArtworks { // track.artworks().removeAllObjects() // } } private func saveTag(tag: Tag, toTrack track: iTunesTrack, value: AnyObject?) { switch Preferences.sharedPreferences.tagSavingBehaviors[tag]! { case .Save: track.setValue(value, forKey: tag.rawValue) case .Clear: track.setValue(tag.clearedValue, forKey: tag.rawValue) case .Ignore: break } track.setValue(value, forKey: tag.rawValue) } /// Returns `true` if all `associatedTrack`s contain the same values as the /// `Track` instance. public var saved: Bool { let components = NSCalendar.currentCalendar().components(.Year, fromDate: releaseDate) for track in associatedTracks { guard track.name == name && track.artist == artistName && track.year == components.year && track.trackNumber == trackNumber && track.trackCount == trackCount && track.discNumber == discNumber && track.discCount == discCount && track.genre == genre && track.album == album.name && track.albumArtist == album.artistName && track.composer == "" else { return false } } return true } } extension Track: Hashable { public var hashValue: Int { return Int(id) } } public func ==(lhs: Track, rhs: Track) -> Bool { return lhs.id == rhs.id }