Skip to content

Commit

Permalink
Merge pull request #108 from czechboy0/hd/xcs_live_updates
Browse files Browse the repository at this point in the history
Live Updates from Xcode Server
  • Loading branch information
czechboy0 committed Sep 27, 2015
2 parents a928de8 + aa52505 commit 0058d65
Show file tree
Hide file tree
Showing 12 changed files with 540 additions and 69 deletions.
24 changes: 14 additions & 10 deletions Podfile
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

6 changes: 3 additions & 3 deletions Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PODS:
- BuildaUtils (0.0.11)
- BuildaUtils (0.1.0)
- DVR (0.0.4-czechboy0)

DEPENDENCIES:
- BuildaUtils (= 0.0.11)
- BuildaUtils (= 0.1.0)
- DVR (from `https:/czechboy0/DVR.git`, tag `v0.0.4-czechboy0`)

EXTERNAL SOURCES:
Expand All @@ -17,7 +17,7 @@ CHECKOUT OPTIONS:
:tag: v0.0.4-czechboy0

SPEC CHECKSUMS:
BuildaUtils: be29c9977230329a330a02c6f97454a7422ae534
BuildaUtils: cf7756290492ca286935d996c8bf4a3085662514
DVR: 386f347071f55f3f9105239db6764483009ec875

COCOAPODS: 0.38.2
46 changes: 35 additions & 11 deletions XcodeServerSDK.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

141 changes: 141 additions & 0 deletions XcodeServerSDK/API Routes/XcodeServer+LiveUpdates.swift
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)
}
}
}

107 changes: 107 additions & 0 deletions XcodeServerSDK/Server Entities/LiveUpdateMessage.swift
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)"
}
}
Loading

0 comments on commit 0058d65

Please sign in to comment.