Stuff…
This commit is contained in:
172
TagTunes/iTunes.swift
Normal file → Executable file
172
TagTunes/iTunes.swift
Normal file → Executable file
@@ -7,171 +7,17 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppKitPlus
|
||||
import SearchAPI
|
||||
|
||||
/// The Cocoa Scripting Bridge interface of iTunes.
|
||||
public let iTunes = iTunesApplication(bundleIdentifier: "com.apple.iTunes")!
|
||||
|
||||
/// An ID as returned from the
|
||||
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html)
|
||||
public typealias iTunesId = UInt
|
||||
/// A pasteboard type for `TagTunesTrack` objects. This type is used for dragging
|
||||
/// tracks in TagTunes. It's data should be a single encoded `TagTunesTrack`
|
||||
/// object.
|
||||
public let TrackPboardType = "public.item.tagtunestrack"
|
||||
|
||||
|
||||
/// This struct contains static helper objects and methods to work with the
|
||||
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
|
||||
public struct iTunesAPI {
|
||||
|
||||
// MARK: Types
|
||||
|
||||
/// Error types indicating error responses from the
|
||||
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
|
||||
public enum Error: ErrorType {
|
||||
case InvalidCountryCode
|
||||
case InvalidLanguageCode
|
||||
case UnknownError
|
||||
}
|
||||
|
||||
/// Contains constants identifying the fields in a response from the
|
||||
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
|
||||
public enum Field: String {
|
||||
case WrapperType = "wrapperType"
|
||||
case Kind = "kind"
|
||||
|
||||
case TrackId = "trackId"
|
||||
case TrackName = "trackName"
|
||||
case TrackCensoredName = "trackCensoredName"
|
||||
case ArtistName = "artistName"
|
||||
case ReleaseDate = "releaseDate"
|
||||
case TrackNumber = "trackNumber"
|
||||
case TrackCount = "trackCount"
|
||||
case DiscNumber = "discNumber"
|
||||
case DiscCount = "discCount"
|
||||
case PrimaryGenreName = "primaryGenreName"
|
||||
|
||||
case CollectionId = "collectionId"
|
||||
case CollectionName = "collectionName"
|
||||
case CollectionCensoredName = "collectionCensoredName"
|
||||
case CollectionViewUrl = "collectionViewUrl"
|
||||
case CollectionArtistName = "collectionArtistName"
|
||||
|
||||
case ArtworkUrl60 = "artworkUrl60"
|
||||
case ArtworkUrl100 = "artworkUrl100"
|
||||
}
|
||||
|
||||
// MARK: Static Properties and Functions
|
||||
|
||||
/// This formatter is configured to be used to parse dates returned from the
|
||||
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html)
|
||||
internal static let sharedDateFormatter: NSDateFormatter = {
|
||||
let dateFormatter = NSDateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
/// Processes the data returned from a request to the
|
||||
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
|
||||
/// The `data` has to be in a valid JSON format. See `NSJSONSerialization`
|
||||
/// for details.
|
||||
///
|
||||
/// Currently only tracks and albums are supported. If there are any other
|
||||
/// entries in the specified data this function will raise an exception.
|
||||
///
|
||||
/// - throws: Parsing errors and `iTUnesAPI.Error` constants.
|
||||
/// - returns: An array of albums populated with all associated tracks in the
|
||||
/// specified data.
|
||||
public static func parseAPIData(data: NSData) throws -> [Album] {
|
||||
guard let parsedData = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] else {
|
||||
throw Error.UnknownError
|
||||
}
|
||||
// Handle API Errors
|
||||
if let errorMessage = parsedData["errorMessage"] as? String {
|
||||
switch errorMessage {
|
||||
case "Invalid value(s) for key(s): [country]":
|
||||
throw Error.InvalidCountryCode
|
||||
case "Invalid value(s) for key(s): [language]":
|
||||
throw Error.InvalidLanguageCode
|
||||
default:
|
||||
throw Error.UnknownError
|
||||
}
|
||||
}
|
||||
// Parse API Results
|
||||
var albums = [iTunesId: Album]()
|
||||
if let results = parsedData["results"] as? [[String: AnyObject]] {
|
||||
for result in results {
|
||||
let convertedResult = result.filter({ (key, value) -> Bool in Field(rawValue: key) != nil }).map({ (Field(rawValue: $0)!, $1)
|
||||
})
|
||||
let albumId = convertedResult[.CollectionId] as! iTunesId
|
||||
if albums[albumId] == nil {
|
||||
albums[albumId] = Album(data: convertedResult)
|
||||
}
|
||||
if isTrack(convertedResult) {
|
||||
albums[albumId]?.addTrack(Track(data: convertedResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array(albums.values)
|
||||
}
|
||||
|
||||
private static func isTrack(data: [Field: AnyObject]) -> Bool {
|
||||
return data[.WrapperType] as! String == "track" && data[.Kind] as! String == "song"
|
||||
}
|
||||
|
||||
/// Creates an URL that searches the
|
||||
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html)
|
||||
/// for albums matching a specific `term`.
|
||||
///
|
||||
/// This function respects the user's preferences (See `Preferences` class).
|
||||
///
|
||||
/// - returns: The query URL or `nil` if `term` is invalid.
|
||||
public static func createAlbumSearchURLForTerm(term: String) -> NSURL? {
|
||||
var searchTerm = term.stringByReplacingOccurrencesOfString(" ", withString: "+")
|
||||
searchTerm = searchTerm.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet())!
|
||||
if searchTerm.isEmpty {
|
||||
return nil
|
||||
}
|
||||
return NSURL(string: "https://itunes.apple.com/search?term=\(searchTerm)&media=music&entity=album&limit=\(Preferences.sharedPreferences.numberOfSearchResults)&country=\(Preferences.sharedPreferences.iTunesStore)" + (Preferences.sharedPreferences.useEnglishTags ? "&lang=en" : ""))
|
||||
}
|
||||
|
||||
/// Creates an URL that looks up all tracks that belong to the album with the
|
||||
/// specified `id` in the
|
||||
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
|
||||
///
|
||||
/// This function respects the user's preferences (See `Preferences` class).
|
||||
public static func createAlbumLookupURLForId(id: iTunesId) -> NSURL {
|
||||
return NSURL(string: "http://itunes.apple.com/lookup?id=\(id)&entity=song&country=\(Preferences.sharedPreferences.iTunesStore)&limit=200" + (Preferences.sharedPreferences.useEnglishTags ? "&lang=en" : ""))!
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a type that can be parsed from a result of the
|
||||
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
|
||||
public protocol iTunesType {
|
||||
|
||||
/// Initializes the receiver with the specified data. The receiver may use
|
||||
/// all or only some of the values.
|
||||
///
|
||||
/// This method requires `data` to contain the expected formats. If data
|
||||
/// contains no data or data in an invalid format for an expected key, this
|
||||
/// method raises an exception. To check wether a specified dictionary is
|
||||
/// valid use `canInitializeFromData`.
|
||||
init(data: [iTunesAPI.Field: AnyObject])
|
||||
|
||||
/// Returns all fields that are required to initialize an instance of the
|
||||
/// receiving type.
|
||||
static var requiredFields: [iTunesAPI.Field] { get }
|
||||
|
||||
}
|
||||
|
||||
extension iTunesType {
|
||||
|
||||
/// Returns wether the specified `data` can be used to initialize a instance
|
||||
/// of the receiving type.
|
||||
public static func canInitializeFromData(data: [iTunesAPI.Field: AnyObject]) -> Bool {
|
||||
for field in requiredFields {
|
||||
if data[field] == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
/// A pasteboard type for `Int`s. The specific use of this type is not specified.
|
||||
/// This pasteboard type should only be used for private purposes (for example to
|
||||
/// *remember* the original indexes of dragged items in a table view).
|
||||
public let IndexPboardType = "public.item.index"
|
||||
Reference in New Issue
Block a user