Archived
1
This commit is contained in:
Kim Wittenburg
2019-02-01 22:59:01 +01:00
parent ba472864df
commit 0a485ff42a
82 changed files with 3975 additions and 1822 deletions

172
TagTunes/iTunes.swift Normal file → Executable file
View 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"