-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #108 from czechboy0/hd/xcs_live_updates
Live Updates from Xcode Server
- Loading branch information
Showing
12 changed files
with
540 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,32 @@ | ||
|
||
use_frameworks! | ||
|
||
utils_name = 'BuildaUtils' | ||
utils_version = '0.0.11' | ||
def utils | ||
pod 'BuildaUtils', '0.1.0' | ||
end | ||
|
||
def tests | ||
pod 'DVR', :git => "https:/czechboy0/DVR.git", :tag => "v0.0.5-czechboy0" | ||
end | ||
|
||
target 'XcodeServerSDK - OS X' do | ||
pod utils_name, utils_version | ||
utils | ||
end | ||
|
||
target 'XcodeServerSDK - OS X Tests' do | ||
pod utils_name, utils_version | ||
pod 'DVR', :git => "https:/czechboy0/DVR.git", :tag => "v0.0.4-czechboy0" | ||
utils | ||
tests | ||
end | ||
|
||
target 'XcodeServerSDK - iOS' do | ||
pod utils_name, utils_version | ||
utils | ||
end | ||
|
||
target 'XcodeServerSDK - iOS Tests' do | ||
pod utils_name, utils_version | ||
pod 'DVR', :git => "https:/czechboy0/DVR.git", :tag => "v0.0.4-czechboy0" | ||
utils | ||
tests | ||
end | ||
|
||
target 'XcodeServerSDK - watchOS' do | ||
pod utils_name, utils_version | ||
utils | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
141 changes: 141 additions & 0 deletions
141
XcodeServerSDK/API Routes/XcodeServer+LiveUpdates.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// | ||
// XcodeServer+LiveUpdates.swift | ||
// XcodeServerSDK | ||
// | ||
// Created by Honza Dvorsky on 25/09/2015. | ||
// Copyright © 2015 Honza Dvorsky. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import BuildaUtils | ||
|
||
// MARK: - XcodeSever API Routes for Live Updates | ||
extension XcodeServer { | ||
|
||
public typealias MessageHandler = (messages: [LiveUpdateMessage]) -> () | ||
public typealias StopHandler = () -> () | ||
public typealias ErrorHandler = (error: ErrorType) -> () | ||
|
||
private class LiveUpdateState { | ||
var task: NSURLSessionTask? | ||
var messageHandler: MessageHandler? | ||
var errorHandler: ErrorHandler? | ||
var pollId: String? | ||
var terminated: Bool = false | ||
|
||
func cancel() { | ||
self.task?.cancel() | ||
self.task = nil | ||
self.terminated = true | ||
} | ||
|
||
func error(error: ErrorType) { | ||
self.cancel() | ||
self.errorHandler?(error: error) | ||
} | ||
|
||
deinit { | ||
self.cancel() | ||
} | ||
} | ||
|
||
/** | ||
* Returns StopHandler - call it when you want to stop receiving updates. | ||
*/ | ||
public func startListeningForLiveUpdates(message: MessageHandler, error: ErrorHandler? = nil) -> StopHandler { | ||
|
||
let state = LiveUpdateState() | ||
state.errorHandler = error | ||
state.messageHandler = message | ||
self.startPolling(state) | ||
return { | ||
state.cancel() | ||
} | ||
} | ||
|
||
private func queryWithTimestamp() -> [String: String] { | ||
let timestamp = Int(NSDate().timeIntervalSince1970)*1000 | ||
return [ | ||
"t": "\(timestamp)" | ||
] | ||
} | ||
|
||
private func sendRequest(state: LiveUpdateState, params: [String: String]?, completion: (message: String) -> ()) { | ||
|
||
let query = queryWithTimestamp() | ||
let task = self.sendRequestWithMethod(.GET, endpoint: .LiveUpdates, params: params, query: query, body: nil, portOverride: 443) { | ||
(response, body, error) -> () in | ||
|
||
if let error = error { | ||
state.error(error) | ||
return | ||
} | ||
|
||
guard let message = body as? String else { | ||
let e = Error.withInfo("Wrong body: \(body)") | ||
state.error(e) | ||
return | ||
} | ||
|
||
completion(message: message) | ||
} | ||
state.task = task | ||
} | ||
|
||
private func startPolling(state: LiveUpdateState) { | ||
|
||
self.sendRequest(state, params: nil) { [weak self] (message) -> () in | ||
self?.processInitialResponse(message, state: state) | ||
} | ||
} | ||
|
||
private func processInitialResponse(initial: String, state: LiveUpdateState) { | ||
if let pollId = initial.componentsSeparatedByString(":").first { | ||
state.pollId = pollId | ||
self.poll(state) | ||
} else { | ||
state.error(Error.withInfo("Unexpected initial poll message: \(initial)")) | ||
} | ||
} | ||
|
||
private func poll(state: LiveUpdateState) { | ||
precondition(state.pollId != nil) | ||
let params = [ | ||
"poll_id": state.pollId! | ||
] | ||
|
||
self.sendRequest(state, params: params) { [weak self] (message) -> () in | ||
|
||
let packets = SocketIOHelper.parsePackets(message) | ||
self?.handlePackets(packets, state: state) | ||
} | ||
} | ||
|
||
private func handlePackets(packets: [SocketIOPacket], state: LiveUpdateState) { | ||
|
||
//check for errors | ||
if let lastPacket = packets.last where lastPacket.type == .Error { | ||
let (_, advice) = lastPacket.parseError() | ||
if | ||
let advice = advice, | ||
case .Reconnect = advice { | ||
//reconnect! | ||
self.startPolling(state) | ||
return | ||
} | ||
print("Unrecognized socket.io error: \(lastPacket.stringPayload)") | ||
} | ||
|
||
//we good? | ||
let events = packets.filter { $0.type == .Event } | ||
let validEvents = events.filter { $0.jsonPayload != nil } | ||
let messages = validEvents.map { LiveUpdateMessage(json: $0.jsonPayload!) } | ||
if messages.count > 0 { | ||
state.messageHandler?(messages: messages) | ||
} | ||
if !state.terminated { | ||
self.poll(state) | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// | ||
// LiveUpdateMessage.swift | ||
// XcodeServerSDK | ||
// | ||
// Created by Honza Dvorsky on 26/09/2015. | ||
// Copyright © 2015 Honza Dvorsky. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
public class LiveUpdateMessage: XcodeServerEntity { | ||
|
||
public enum MessageType: String { | ||
|
||
//bots | ||
case BotCreated = "botCreated" | ||
case BotUpdated = "botUpdated" | ||
case BotRemoved = "botRemoved" | ||
|
||
//devices | ||
case DeviceCreated = "deviceCreated" | ||
case DeviceUpdated = "deviceUpdated" | ||
case DeviceRemoved = "deviceRemoved" | ||
|
||
//integrations | ||
case PendingIntegrations = "pendingIntegrations" | ||
case IntegrationCreated = "integrationCreated" | ||
case IntegrationStatus = "integrationStatus" | ||
case IntegrationCanceled = "cancelIntegration" | ||
case IntegrationRemoved = "integrationRemoved" | ||
case AdvisoryIntegrationStatus = "advisoryIntegrationStatus" | ||
|
||
//repositories | ||
case ListRepositories = "listRepositories" | ||
case CreateRepository = "createRepository" | ||
|
||
//boilerplate | ||
case Ping = "ping" | ||
case Pong = "pong" | ||
case ACLUpdated = "aclUpdated" | ||
case RequestPortalSync = "requestPortalSync" | ||
|
||
case Unknown = "" | ||
} | ||
|
||
public let type: MessageType | ||
public let message: String? | ||
public let progress: Double? | ||
public let integrationId: String? | ||
public let botId: String? | ||
public let result: Integration.Result? | ||
public let currentStep: Integration.Step? | ||
|
||
required public init(json: NSDictionary) { | ||
|
||
let typeString = json.optionalStringForKey("name") ?? "" | ||
|
||
self.type = MessageType(rawValue: typeString) ?? .Unknown | ||
|
||
let args = json["args"]?[0] as? NSDictionary | ||
|
||
self.message = args?["message"] as? String | ||
self.progress = args?["percentage"] as? Double | ||
self.integrationId = args?["_id"] as? String | ||
self.botId = args?["botId"] as? String | ||
|
||
if | ||
let resultString = args?["result"] as? String, | ||
let result = Integration.Result(rawValue: resultString) { | ||
self.result = result | ||
} else { | ||
self.result = nil | ||
} | ||
if | ||
let stepString = args?["currentStep"] as? String, | ||
let step = Integration.Step(rawValue: stepString) { | ||
self.currentStep = step | ||
} else { | ||
self.currentStep = nil | ||
} | ||
|
||
super.init(json: json) | ||
} | ||
|
||
} | ||
|
||
extension LiveUpdateMessage: CustomStringConvertible { | ||
|
||
public var description: String { | ||
|
||
let empty = "" //fixed in Swift 2.1 | ||
|
||
let nonNilComps = [ | ||
self.message, | ||
"\(self.progress?.description ?? empty)", | ||
self.result?.rawValue, | ||
self.currentStep?.rawValue | ||
] | ||
.filter { $0 != nil } | ||
.map { $0! } | ||
.filter { $0.characters.count > 0 } | ||
.map { "\"\($0)\"" } | ||
|
||
let str = nonNilComps.joinWithSeparator(", ") | ||
return "LiveUpdateMessage \"\(self.type)\", \(str)" | ||
} | ||
} |
Oops, something went wrong.