// // Track.swift // TagTunes // // Created by Kim Wittenburg on 17.03.16. // Copyright © 2016 Kim Wittenburg. All rights reserved. // import SearchAPI // TODO: Documentation public enum TagTunesTrackErrors: ErrorType { case FileNotFound case FileNotReadable case NoIDFound } /// Represents a track in TagTunes. A track is directly related to a file. There /// may be different subclasses of this class representing different kinds of /// tracks (for example mp3, aac, ...). /// /// `TagTunesTrack` should be regarded as a protocol. Due to Swift's limitation /// with associated types this is currently not possible. Nevertheless every the /// class `TagTunesTrack` can not be used by itself. public class TagTunesTrack: NSObject, NSSecureCoding { /// The entity containing the track. public internal(set) weak var entity: TagTunesEntityItem? /// The track's lookup state. By default the state is `Unprocessed`. The /// state is changed by the lookup controller and may be changed on any /// thread. public internal(set) var lookupState = LookupState.Unprocessed // TODO: Change DOcumentation /// Returns the `id` of the track. The id can be used to query the track on /// the iTunes Store. If no such id exists, `nil` is returned. /// /// This property may block the main thread until the id could be fetched. /// Subclasses may choose to use the `NSProgress` mechanism to report the /// progress of the fetching of the id. If possible the id should be cached /// to minimize waiting time for the user. /// /// This property must be overridden by subclasses. public private(set) var id: SearchAPIID? // TODO: Documentation public func updateTrackID() { do { try id = readTrackID() } catch { lookupState = LookupState.Unqualified(error) } } // TODO: Documentation public func readTrackID() throws -> SearchAPIID { fatalError("Must override readTrackID()") } /// Initializes the track. This is the designated initializer of /// `TagTunesTrack`. Also it is the only initializer that does not crash the /// programm. public override init() { super.init() } /// Decodes the track. This method must be implemented by subclasses. /// Subclasses must not call this method on `super` in their implementation. /// Instead subclasses should use `super.init()`. @objc public required init?(coder aDecoder: NSCoder) { fatalError("Must override init(coder:)") } /// Encoded the track. This method must be implemented by subclasses. @objc public func encodeWithCoder(aCoder: NSCoder) { fatalError("Must override encodeWithCoder(_:)") } @objc public static func supportsSecureCoding() -> Bool { return true } /// Per-tag querying. This method must be implemented by subclasses. It is /// generally not a good idea to return `nil` from this method. public func valueForTag(tag: Tag) -> AnyObject! { fatalError("Must override valueForTag(_:)") } /// Reveals the track. Normally this launches another application in which to /// reveal the track. Which application is launched depends on the actual /// track. /// /// This method must be implemented by subclasses. public func reveal() { fatalError("Must override reveal()") } // TODO: Documentation public var supportsBatchSaving: Bool { fatalError("Must override property supportsBatchSaving") } // TODO: Remove /// Saves the track using the data from the specified `entity`. Subclasses /// may use the `NSProgress` mechanism to report the saving process. /// /// This method may be overridden by subclasses. It should respect the user's /// preferences. The default implementation invokes `saveTag(_:value:)` for /// each tag to be saved. /// /// - returns: `true` if the `tags` could be saved successfully, `false` /// otherwise. If `false` is returned the `saveErrors` property /// should contain information about the errors that occured. If /// `true` is returned `saveErrors` should be empty. // public func save(entity: TagTunesEntityItem) -> Bool { // var success = true // saveErrors = [] // let progress = NSProgress(totalUnitCount: Int64(Tag.allTags.count)) // for tag in Tag.allTags { // do { // switch Preferences.sharedPreferences.tagSavingBehaviors[tag]! { // case .Save: try success = success && saveTag(tag, value: entity.valueForTag(tag)) // case .Clear: success = success && saveTag(tag, value: nil) // case .Ignore: break // } // } catch let error { // saveErrors.append(error) // } // dispatch_sync(dispatch_get_main_queue()) { // progress.completedUnitCount += 1 // } // } // return success // } // TODO: Documentation public func saveTags(tags: [Tag: AnyObject?]) throws { fatalError("Must override saveTags(_:)") } // TODO: throws documentation /// Saves the specified `value` for the specified `tag`. This method should /// not act depending on the user's `Preferences`. /// /// This method must be overridden by subclasses. /// /// - parameters: /// - value: The value to be saved. If this value is `nil` the value for /// the specified `tag` should be removed from the track. /// - tag: The `Tag` to be saved. public func saveValue(value: AnyObject?, forTag tag: Tag) throws { fatalError("Must override saveTag(_:value:)") } public override var hashValue: Int { fatalError("Must override property hashValue.") } public override func isEqual(object: AnyObject?) -> Bool { if let other = object as? TagTunesTrack { return self == other } return super.isEqual(object) } public override var hash: Int { return hashValue } } public func ==(lhs: TagTunesTrack, rhs: TagTunesTrack) -> Bool { if let leftTrack = lhs as? ImportedTrack, rightTrack = rhs as? ImportedTrack { return leftTrack == rightTrack } return lhs === rhs } public extension TagTunesTrack { /// The track's name. public var name: String? { return valueForTag(.Name) as? String } /// The track's artist. public var artist: String? { return valueForTag(.Artist) as? String } /// The track's year. public var year: Int? { return valueForTag(.Year) as? Int } /// The track's track number. public var trackNumber: Int? { return valueForTag(.TrackNumber) as? Int } /// The track's track count. The track count is respective to the track's /// `discNumber`. public var trackCount: Int? { return valueForTag(.TrackCount) as? Int } /// The track's disc number. public var discNumber: Int? { return valueForTag(.DiscNumber) as? Int } /// The track's disc count. public var discCount: Int? { return valueForTag(.DiscCount) as? Int } /// The track's genre. public var genre: String? { return valueForTag(.Genre) as? String } /// The track's album. public var album: String? { return valueForTag(.AlbumName) as? String } /// The track's album artist. public var albumArtist: String? { return valueForTag(.AlbumArtist) as? String } /// The track's release date. public var releaseDate: NSDate? { return valueForTag(.ReleaseDate) as? NSDate } /// A boolean value indicating whether the track belongs to a compilation /// album. public var compilation: Bool? { return valueForTag(.Compilation) as? Bool } /// The track's artwork. public var artwork: NSImage? { return valueForTag(.Artwork) as? NSImage } /// The track's sort name. public var sortName: String? { return valueForTag(.SortName) as? String } /// The track's sort artist. public var sortArtist: String? { return valueForTag(.SortArtist) as? String } /// The track's sort album. public var sortAlbum: String? { return valueForTag(.SortAlbumName) as? String } /// The track's sort album artist. public var sortAlbumArtist: String? { return valueForTag(.SortAlbumArtist) as? String } /// The track's composer. public var composer: String? { return valueForTag(.Composer) as? String } /// The track's sort composer. public var sortComposer: String? { return valueForTag(.SortComposer) as? String } /// The track's comment. public var comment: String? { return valueForTag(.Comment) as? String } }