Stuff…
This commit is contained in:
181
TagTunes/LookupQueue.swift
Executable file
181
TagTunes/LookupQueue.swift
Executable file
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// LookupController.swift
|
||||
// TagTunes
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.03.16.
|
||||
// Copyright © 2016 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
import SearchAPI
|
||||
import AppKitPlus
|
||||
|
||||
/// The state of a lookup process.
|
||||
public enum LookupState {
|
||||
|
||||
/// The track has not been processed for lookup. This is the initial state.
|
||||
case Unprocessed
|
||||
|
||||
/// The track is being prepared for lookup. During preparation the track's id
|
||||
/// is read.
|
||||
case Preparing
|
||||
|
||||
/// The lookup is currently searching for the track's metadata.
|
||||
case Searching
|
||||
|
||||
/// The track was found. The associated `Track` represents the found metadata
|
||||
/// of the track.
|
||||
case Found(Track)
|
||||
|
||||
/// The track has been looked up but was not found.
|
||||
case NotFound
|
||||
|
||||
/// The track is not qualified for lookup because its id could not be read. The
|
||||
/// associated value contains an error description identifying the reason why
|
||||
/// the track's id could not be read.
|
||||
case Unqualified(ErrorType)
|
||||
|
||||
/// During the lookup an error occured. This state is also used if the lookup
|
||||
/// was cancelled by the user.
|
||||
case Error(ErrorType?)
|
||||
|
||||
}
|
||||
|
||||
// TODO: Documentation
|
||||
|
||||
|
||||
// DOCUMENTATION: All delegate methods are executed on main thread.
|
||||
protocol LookupQueueDelegate: class {
|
||||
|
||||
/// Invoked before the `lookupController` will process the specified
|
||||
/// `tracks`. This method may be invoked multiple times before
|
||||
/// `lookupControllerDiDFinishLookup(_:)` is invoked, but it is guaranteed
|
||||
/// that it will be invoked at least once. For more information see the
|
||||
/// documentation for `LookupController`.
|
||||
func lookupQueue(lookupQueue: LookupQueue, willBeginLookupForTracks tracks: [TagTunesTrack])
|
||||
|
||||
/// Invoked when the `lookupController` completes the lookup process for the
|
||||
/// specified `tracks`. This method is called for tracks that could be
|
||||
/// successfully looked up or that could not be looked up. There can also be
|
||||
/// both in one invocation of this method. Use the `lookupState` of the
|
||||
/// `TagTunesTrack` instances to get information about the lookup result for
|
||||
/// individual tracks.
|
||||
///
|
||||
/// This method may be called multiple times in a row. Every track that has
|
||||
/// been part of the `tracks` array of a
|
||||
/// `lookupController(_:willBeginLookupForTracks:)` message will be part of
|
||||
/// a invocation of this method exactly once.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - lookupController: The controller that did the lookup.
|
||||
/// - tracks: The tracks that have been processed by the
|
||||
/// `lookupController`. The order of the tracks has no meaning.
|
||||
func lookupQueue(lookupQueue: LookupQueue, completedLookupForTracks tracks: [TagTunesTrack])
|
||||
|
||||
/// Invoked after the `lookupController` finished looking up all enqueued
|
||||
/// tracks.
|
||||
func lookupQueueDidFinishLookup(lookupQueue: LookupQueue)
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// The lookup controller looks up tracks' metadata online based on the iTunes
|
||||
/// store ID embedded into the a file. To be notified about the lookup progress
|
||||
/// set the controller's `delegate` property. There are some valid assumptions
|
||||
/// you can make about the order in which the delgate methods are invoked:
|
||||
///
|
||||
/// 1. `lookupController(_:willBeginLookupForTracks:)` is the first of the
|
||||
/// methods to be invoked.
|
||||
/// 2. Every `TagTunesTrack` that is part of the `tracks` in
|
||||
/// `lookupController(_:willBeginLookupForTracks:)` will be part of the
|
||||
/// `tracks` of `lookupController(_:completedLookupForTracks:)` or.
|
||||
/// 4. `lookupControllerDidFinishLookup(_:)` may only be called after (1) and (2)
|
||||
/// are satisfied.
|
||||
/// 5. After `lookupControllerDidFinishLookup(_:)` the next delegate message will
|
||||
/// be `lookupController(_:willBeginLookupForTracks:)`.
|
||||
///
|
||||
/// The calls to `lookupController(_:willBeginLookupForTracks:)` and
|
||||
/// `lookupControllerDidFinishLookup(_:)` are **not** balanced. There may be
|
||||
/// multiple invokations of the former with only a single invokation of the
|
||||
/// latter.
|
||||
class LookupQueue: OperationQueue {
|
||||
|
||||
static let globalQueue = LookupQueue()
|
||||
|
||||
// TODO: Is there an alternative to two delegates
|
||||
/// The lookup controller's delegate.
|
||||
weak var lookupDelegate: LookupQueueDelegate?
|
||||
|
||||
var lookupOperation: LookupOperation?
|
||||
|
||||
private var aggregatedTracks = [TagTunesTrack]()
|
||||
|
||||
/// Enqueues the specified `tracks` for lookup. The point in time where the
|
||||
/// `tracks` will actually be looked up is not defined. It may be when this
|
||||
/// method returns or at some later point. To get notified when the lookup
|
||||
/// actually starts implement the delgate method
|
||||
/// `lookupController(_:willBeginLookupForTracks:)`.
|
||||
///
|
||||
/// - note: There is no correspondance between the specified `tracks` an the
|
||||
/// tracks passed to the delegate methods. If there is currently a
|
||||
/// lookup in progress the lookup controller might collect the
|
||||
/// enqueued tracks and batch-process them.
|
||||
func enqueueTracksForLookup<S: SequenceType where S.Generator.Element == TagTunesTrack>(tracks: S) {
|
||||
aggregatedTracks.appendContentsOf(tracks)
|
||||
for track in tracks {
|
||||
let preparationOperation = LookupPreparationOperation(track: track)
|
||||
addOperation(preparationOperation)
|
||||
}
|
||||
beginLookupIfPossible()
|
||||
}
|
||||
|
||||
// DOCUMENTATION: Must execute on main thread
|
||||
// FIXME: ? Can there be two lookup operations?
|
||||
private func beginLookupIfPossible() {
|
||||
// TODO: "Finished Lookup" delegate call
|
||||
if lookupOperation == nil && !aggregatedTracks.isEmpty {
|
||||
lookupOperation = LookupOperation(tracks: aggregatedTracks)
|
||||
lookupOperation?.addObserver(BlockObserver(startHandler: { operation in
|
||||
dispatch_sync(dispatch_get_main_queue()) {
|
||||
// TODO: Is dispatch_sync ok?
|
||||
let lookupOperation = operation as! LookupOperation
|
||||
self.lookupDelegate?.lookupQueue(self, willBeginLookupForTracks: lookupOperation.tracks)
|
||||
}
|
||||
}, produceHandler: nil, finishHandler: { (operation, errors) in
|
||||
dispatch_sync(dispatch_get_main_queue()) {
|
||||
// TODO: Is this a retain cycle?
|
||||
// TODO: Is dispatch_sync ok?
|
||||
self.lookupOperation(operation, finishedWithErrors: errors)
|
||||
}
|
||||
}))
|
||||
aggregatedTracks = []
|
||||
for operation in operations where operation is LookupPreparationOperation {
|
||||
lookupOperation?.addDependency(operation)
|
||||
}
|
||||
addOperation(lookupOperation!)
|
||||
}
|
||||
}
|
||||
|
||||
// DOCUMENTATION: Must execute on main thread.
|
||||
private func lookupOperation(operation: Operation, finishedWithErrors errors: [ErrorType]) {
|
||||
let lookupOperation = operation as! LookupOperation
|
||||
self.lookupDelegate?.lookupQueue(self, completedLookupForTracks: lookupOperation.tracks)
|
||||
self.lookupOperation = nil
|
||||
if aggregatedTracks.isEmpty {
|
||||
lookupDelegate?.lookupQueueDidFinishLookup(self)
|
||||
} else {
|
||||
beginLookupIfPossible()
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancels the lookup. This will do three things:
|
||||
///
|
||||
/// 1. Stop any running or pending network request. Set the `lookupState` of
|
||||
/// all tracks to `NSCocoaError.UserCancelledError`.
|
||||
/// 2. Send the delegate a `lookupController(_:completedLookupForTracks:)`
|
||||
/// message.
|
||||
/// 3. Send the delegate a `lookupControllerDidFinishLookup(_:)` message.
|
||||
func cancelLookup() {
|
||||
// TODO: Implementation
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user