fastlane 2.149.1 → 2.150.0.rc5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/deliver/lib/deliver.rb +0 -1
- data/deliver/lib/deliver/app_screenshot.rb +26 -25
- data/deliver/lib/deliver/download_screenshots.rb +46 -26
- data/deliver/lib/deliver/options.rb +6 -11
- data/deliver/lib/deliver/runner.rb +7 -21
- data/deliver/lib/deliver/setup.rb +5 -30
- data/deliver/lib/deliver/submit_for_review.rb +170 -86
- data/deliver/lib/deliver/upload_metadata.rb +355 -143
- data/deliver/lib/deliver/upload_price_tier.rb +22 -8
- data/deliver/lib/deliver/upload_screenshots.rb +140 -39
- data/{scan/lib/scan/.test_command_generator.rb.swp → fastlane/lib/fastlane/actions/.hockey.rb.swp} +0 -0
- data/fastlane/lib/fastlane/actions/.slack.rb.swp +0 -0
- data/{snapshot/lib/snapshot/.test_command_generator_base.rb.swp → fastlane/lib/fastlane/actions/.update_project_provisioning.rb.swp} +0 -0
- data/fastlane/lib/fastlane/actions/docs/capture_ios_screenshots.md +1 -1
- data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +81 -96
- data/fastlane/lib/fastlane/actions/docs/upload_to_play_store.md +3 -2
- data/fastlane/lib/fastlane/actions/download_dsyms.rb +7 -1
- data/fastlane/lib/fastlane/actions/google_play_track_release_names.rb +74 -0
- data/fastlane/lib/fastlane/actions/set_changelog.rb +23 -20
- data/fastlane/lib/fastlane/actions/slack.rb +1 -1
- data/fastlane/lib/fastlane/actions/spm.rb +7 -0
- data/fastlane/lib/fastlane/actions/upload_symbols_to_crashlytics.rb +1 -32
- data/fastlane/lib/fastlane/lane.rb +3 -3
- data/fastlane/lib/fastlane/swift_fastlane_function.rb +8 -4
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/ControlCommand.swift +1 -0
- data/fastlane/swift/Fastlane.swift +48 -12
- data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.xcworkspace/xcuserdata/josh.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/xcshareddata/xcschemes/FastlaneRunner.xcscheme +3 -9
- data/fastlane/swift/LaneFileProtocol.swift +2 -5
- data/fastlane/swift/MatchfileProtocol.swift +1 -1
- data/fastlane/swift/RubyCommand.swift +29 -6
- data/fastlane/swift/RubyCommandable.swift +1 -0
- data/fastlane/swift/Runner.swift +85 -13
- data/fastlane/swift/ScanfileProtocol.swift +1 -1
- data/fastlane/swift/SnapshotfileProtocol.swift +3 -3
- data/fastlane/swift/SocketClient.swift +76 -45
- data/fastlane/swift/SocketClientDelegateProtocol.swift +1 -1
- data/fastlane/swift/SocketResponse.swift +1 -0
- data/fastlane_core/lib/fastlane_core/build_watcher.rb +4 -4
- data/fastlane_core/lib/fastlane_core/configuration/config_item.rb +1 -3
- data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +89 -52
- data/fastlane_core/lib/fastlane_core/pkg_file_analyser.rb +7 -0
- data/frameit/lib/frameit/device_types.rb +100 -100
- data/pilot/lib/pilot/.manager.rb.swp +0 -0
- data/produce/lib/produce/itunes_connect.rb +59 -21
- data/produce/lib/produce/options.rb +3 -3
- data/sigh/lib/assets/resign.sh +7 -7
- data/snapshot/lib/assets/SnapshotHelper.swift +5 -5
- data/snapshot/lib/assets/SnapshotHelperXcode8.swift +3 -3
- data/snapshot/lib/snapshot/options.rb +0 -1
- data/snapshot/lib/snapshot/reports_generator.rb +8 -1
- data/spaceship/lib/spaceship/client.rb +4 -3
- data/spaceship/lib/spaceship/connect_api.rb +25 -2
- data/spaceship/lib/spaceship/connect_api/.DS_Store +0 -0
- data/spaceship/lib/spaceship/connect_api/client.rb +97 -31
- data/spaceship/lib/spaceship/connect_api/file_uploader.rb +98 -0
- data/spaceship/lib/spaceship/connect_api/model.rb +1 -1
- data/spaceship/lib/spaceship/connect_api/models/age_rating_declaration.rb +113 -0
- data/spaceship/lib/spaceship/connect_api/models/app.rb +135 -3
- data/spaceship/lib/spaceship/connect_api/models/app_category.rb +94 -0
- data/spaceship/lib/spaceship/connect_api/models/app_info.rb +67 -0
- data/spaceship/lib/spaceship/connect_api/models/app_info_localization.rb +38 -0
- data/spaceship/lib/spaceship/connect_api/models/app_preview.rb +129 -0
- data/spaceship/lib/spaceship/connect_api/models/app_preview_set.rb +71 -0
- data/spaceship/lib/spaceship/connect_api/models/app_price.rb +22 -0
- data/spaceship/lib/spaceship/connect_api/models/app_price_tier.rb +12 -0
- data/spaceship/lib/spaceship/connect_api/models/app_review_attachment.rb +71 -0
- data/spaceship/lib/spaceship/connect_api/models/app_screenshot.rb +146 -0
- data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +125 -0
- data/spaceship/lib/spaceship/connect_api/models/app_store_review_detail.rb +51 -0
- data/spaceship/lib/spaceship/connect_api/models/app_store_version.rb +183 -0
- data/spaceship/lib/spaceship/connect_api/models/app_store_version_localization.rb +86 -0
- data/spaceship/lib/spaceship/connect_api/models/app_store_version_phased_release.rb +36 -0
- data/spaceship/lib/spaceship/connect_api/models/app_store_version_submission.rb +26 -0
- data/spaceship/lib/spaceship/connect_api/models/build.rb +4 -0
- data/spaceship/lib/spaceship/connect_api/models/idfa_declaration.rb +40 -0
- data/spaceship/lib/spaceship/connect_api/models/reset_ratings_request.rb +26 -0
- data/spaceship/lib/spaceship/connect_api/models/territory.rb +27 -0
- data/spaceship/lib/spaceship/connect_api/models/user.rb +2 -1
- data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +10 -3
- data/spaceship/lib/spaceship/connect_api/tunes/client.rb +33 -0
- data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +844 -0
- data/spaceship/lib/spaceship/connect_api/users/users.rb +13 -0
- data/spaceship/lib/spaceship/spaceauth_runner.rb +2 -2
- data/supply/lib/supply/client.rb +19 -0
- data/supply/lib/supply/reader.rb +16 -0
- metadata +34 -22
- data/deliver/lib/deliver/upload_assets.rb +0 -27
Binary file
|
@@ -26,10 +26,7 @@
|
|
26
26
|
buildConfiguration = "Debug"
|
27
27
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
28
28
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
29
|
-
language = ""
|
30
29
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
31
|
-
<Testables>
|
32
|
-
</Testables>
|
33
30
|
<MacroExpansion>
|
34
31
|
<BuildableReference
|
35
32
|
BuildableIdentifier = "primary"
|
@@ -39,14 +36,13 @@
|
|
39
36
|
ReferencedContainer = "container:FastlaneSwiftRunner.xcodeproj">
|
40
37
|
</BuildableReference>
|
41
38
|
</MacroExpansion>
|
42
|
-
<
|
43
|
-
</
|
39
|
+
<Testables>
|
40
|
+
</Testables>
|
44
41
|
</TestAction>
|
45
42
|
<LaunchAction
|
46
43
|
buildConfiguration = "Debug"
|
47
44
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
48
45
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
49
|
-
language = ""
|
50
46
|
launchStyle = "0"
|
51
47
|
useCustomWorkingDirectory = "NO"
|
52
48
|
ignoresPersistentStateOnLaunch = "NO"
|
@@ -65,7 +61,7 @@
|
|
65
61
|
</BuildableProductRunnable>
|
66
62
|
<CommandLineArguments>
|
67
63
|
<CommandLineArgument
|
68
|
-
argument = "lane
|
64
|
+
argument = "lane testLane"
|
69
65
|
isEnabled = "YES">
|
70
66
|
</CommandLineArgument>
|
71
67
|
<CommandLineArgument
|
@@ -73,8 +69,6 @@
|
|
73
69
|
isEnabled = "YES">
|
74
70
|
</CommandLineArgument>
|
75
71
|
</CommandLineArguments>
|
76
|
-
<AdditionalOptions>
|
77
|
-
</AdditionalOptions>
|
78
72
|
</LaunchAction>
|
79
73
|
<ProfileAction
|
80
74
|
buildConfiguration = "Release"
|
@@ -19,7 +19,6 @@ public protocol LaneFileProtocol: class {
|
|
19
19
|
static func runLane(named: String, parameters: [String : String]) -> Bool
|
20
20
|
|
21
21
|
func recordLaneDescriptions()
|
22
|
-
func beforeAll(currentLane: String, parameters: [String : String])
|
23
22
|
func beforeAll()
|
24
23
|
func afterAll(currentLane: String)
|
25
24
|
func onError(currentLane: String, errorInfo: String)
|
@@ -27,7 +26,6 @@ public protocol LaneFileProtocol: class {
|
|
27
26
|
|
28
27
|
public extension LaneFileProtocol {
|
29
28
|
var fastlaneVersion: String { return "" } // default "" because that means any is fine
|
30
|
-
func beforeAll(currentLane: String, parameters: [String : String]) { } // no op by default
|
31
29
|
func beforeAll() { } // no op by default
|
32
30
|
func afterAll(currentLane: String) { } // no op by default
|
33
31
|
func onError(currentLane: String, errorInfo: String) {} // no op by default
|
@@ -39,8 +37,7 @@ public class LaneFile: NSObject, LaneFileProtocol {
|
|
39
37
|
private(set) static var fastfileInstance: Fastfile?
|
40
38
|
|
41
39
|
// Called before any lane is executed.
|
42
|
-
private func setupAllTheThings(
|
43
|
-
LaneFile.fastfileInstance!.beforeAll(currentLane: lane, parameters: parameters)
|
40
|
+
private func setupAllTheThings() {
|
44
41
|
LaneFile.fastfileInstance!.beforeAll()
|
45
42
|
}
|
46
43
|
|
@@ -126,7 +123,7 @@ public class LaneFile: NSObject, LaneFileProtocol {
|
|
126
123
|
}
|
127
124
|
|
128
125
|
// call all methods that need to be called before we start calling lanes
|
129
|
-
fastfileInstance.setupAllTheThings(
|
126
|
+
fastfileInstance.setupAllTheThings()
|
130
127
|
|
131
128
|
// We need to catch all possible errors here and display a nice message
|
132
129
|
_ = fastfileInstance.perform(NSSelectorFromString(laneMethod), with: parameters)
|
@@ -82,27 +82,50 @@ struct RubyCommand: RubyCommandable {
|
|
82
82
|
let methodName: String
|
83
83
|
let className: String?
|
84
84
|
let args: [Argument]
|
85
|
+
let id: String = UUID().uuidString
|
86
|
+
|
87
|
+
var closure: ((String) -> Void)? {
|
88
|
+
let callbacks = self.args.filter { ($0.type != nil) && $0.type == .stringClosure }
|
89
|
+
guard let callback = callbacks.first else {
|
90
|
+
return nil
|
91
|
+
}
|
92
|
+
|
93
|
+
guard let callbackArgValue = callback.value else {
|
94
|
+
return nil
|
95
|
+
}
|
85
96
|
|
86
|
-
|
97
|
+
guard let callbackClosure = callbackArgValue as? ((String) -> Void) else {
|
98
|
+
return nil
|
99
|
+
}
|
100
|
+
return callbackClosure
|
101
|
+
}
|
102
|
+
|
103
|
+
|
104
|
+
func callbackClosure(_ callbackArg: String) -> ((String) -> Void)? {
|
87
105
|
// WARNING: This will perform the first callback it receives
|
88
106
|
let callbacks = self.args.filter { ($0.type != nil) && $0.type == .stringClosure }
|
89
107
|
guard let callback = callbacks.first else {
|
90
108
|
verbose(message: "received call to performCallback with \(callbackArg), but no callback available to perform")
|
91
|
-
return
|
109
|
+
return nil
|
92
110
|
}
|
93
111
|
|
94
112
|
guard let callbackArgValue = callback.value else {
|
95
113
|
verbose(message: "received call to performCallback with \(callbackArg), but callback is nil")
|
96
|
-
return
|
114
|
+
return nil
|
97
115
|
}
|
98
116
|
|
99
117
|
guard let callbackClosure = callbackArgValue as? ((String) -> Void) else {
|
100
118
|
verbose(message: "received call to performCallback with \(callbackArg), but callback type is unknown \(callbackArgValue.self)")
|
101
|
-
return
|
119
|
+
return nil
|
102
120
|
}
|
121
|
+
return callbackClosure
|
122
|
+
}
|
103
123
|
|
104
|
-
|
105
|
-
|
124
|
+
func performCallback(callbackArg: String, socket: SocketClient, completion: @escaping () -> Void) {
|
125
|
+
verbose(message: "Performing callback with: \(callbackArg)")
|
126
|
+
socket.leave()
|
127
|
+
self.callbackClosure(callbackArg)?(callbackArg)
|
128
|
+
completion()
|
106
129
|
}
|
107
130
|
|
108
131
|
var commandJson: String {
|
data/fastlane/swift/Runner.swift
CHANGED
@@ -33,28 +33,69 @@ class Runner {
|
|
33
33
|
fileprivate var returnValue: String? // lol, so safe
|
34
34
|
fileprivate var currentlyExecutingCommand: RubyCommandable? = nil
|
35
35
|
fileprivate var shouldLeaveDispatchGroupDuringDisconnect = false
|
36
|
+
fileprivate var executeNext: [String: Bool] = [:]
|
36
37
|
|
37
38
|
func executeCommand(_ command: RubyCommandable) -> String {
|
38
39
|
self.dispatchGroup.enter()
|
39
|
-
currentlyExecutingCommand = command
|
40
|
-
socketClient.send(rubyCommand: command)
|
40
|
+
self.currentlyExecutingCommand = command
|
41
|
+
self.socketClient.send(rubyCommand: command)
|
41
42
|
|
42
43
|
let secondsToWait = DispatchTimeInterval.seconds(SocketClient.defaultCommandTimeoutSeconds)
|
43
|
-
let
|
44
|
-
|
44
|
+
let timeoutResult = waitWithPolling(self.executeNext[command.id], toEventually: { $0 == true }, timeout: SocketClient.defaultCommandTimeoutSeconds)
|
45
|
+
executeNext.removeValue(forKey: command.id)
|
45
46
|
let failureMessage = "command didn't execute in: \(SocketClient.defaultCommandTimeoutSeconds) seconds"
|
46
|
-
let success = testDispatchTimeoutResult(timeoutResult, failureMessage: failureMessage, timeToWait: secondsToWait)
|
47
|
+
let success = self.testDispatchTimeoutResult(timeoutResult, failureMessage: failureMessage, timeToWait: secondsToWait)
|
47
48
|
guard success else {
|
48
49
|
log(message: "command timeout")
|
49
50
|
fatalError()
|
50
51
|
}
|
51
52
|
|
52
|
-
if let
|
53
|
-
return
|
53
|
+
if let _returnValue = self.returnValue {
|
54
|
+
return _returnValue
|
54
55
|
} else {
|
55
56
|
return ""
|
56
57
|
}
|
57
58
|
}
|
59
|
+
|
60
|
+
private func waitWithPolling<T>(_ expression: @autoclosure @escaping () throws -> T, toEventually predicate: @escaping (T) -> Bool, timeout: Int, pollingInterval: DispatchTimeInterval = .milliseconds(4)) -> DispatchTimeoutResult {
|
61
|
+
func memoizedClosure<T>(_ closure: @escaping () throws -> T) -> (Bool) throws -> T {
|
62
|
+
var cache: T?
|
63
|
+
return { withoutCaching in
|
64
|
+
if withoutCaching || cache == nil {
|
65
|
+
cache = try closure()
|
66
|
+
}
|
67
|
+
guard let cache = cache else {
|
68
|
+
preconditionFailure()
|
69
|
+
}
|
70
|
+
|
71
|
+
return cache
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
let runLoop = RunLoop.current
|
76
|
+
let timeoutDate = Date(timeInterval: TimeInterval(timeout), since: Date())
|
77
|
+
var fulfilled: Bool = false
|
78
|
+
let _expression = memoizedClosure(expression)
|
79
|
+
repeat {
|
80
|
+
do {
|
81
|
+
let exp = try _expression(true)
|
82
|
+
fulfilled = predicate(exp)
|
83
|
+
} catch {
|
84
|
+
fatalError("Error raised \(error.localizedDescription)")
|
85
|
+
}
|
86
|
+
if !fulfilled {
|
87
|
+
runLoop.run(until: Date(timeIntervalSinceNow: pollingInterval.timeInterval))
|
88
|
+
} else {
|
89
|
+
break
|
90
|
+
}
|
91
|
+
} while Date().compare(timeoutDate) == .orderedAscending
|
92
|
+
|
93
|
+
if fulfilled {
|
94
|
+
return .success
|
95
|
+
} else {
|
96
|
+
return .timedOut
|
97
|
+
}
|
98
|
+
}
|
58
99
|
}
|
59
100
|
|
60
101
|
// Handle threading stuff
|
@@ -110,25 +151,36 @@ extension Runner {
|
|
110
151
|
}
|
111
152
|
|
112
153
|
extension Runner : SocketClientDelegateProtocol {
|
113
|
-
func commandExecuted(serverResponse: SocketClientResponse) {
|
154
|
+
func commandExecuted(serverResponse: SocketClientResponse, completion: (SocketClient) -> Void) {
|
114
155
|
switch serverResponse {
|
115
156
|
case .success(let returnedObject, let closureArgumentValue):
|
116
157
|
verbose(message: "command executed")
|
117
158
|
self.returnValue = returnedObject
|
118
159
|
if let command = self.currentlyExecutingCommand as? RubyCommand {
|
119
|
-
if let closureArgumentValue = closureArgumentValue {
|
120
|
-
command.performCallback(callbackArg: closureArgumentValue)
|
160
|
+
if let closureArgumentValue = closureArgumentValue, !closureArgumentValue.isEmpty {
|
161
|
+
command.performCallback(callbackArg: closureArgumentValue, socket: socketClient) {
|
162
|
+
self.executeNext[command.id] = true
|
163
|
+
}
|
164
|
+
} else {
|
165
|
+
self.executeNext[command.id] = true
|
121
166
|
}
|
122
167
|
}
|
123
|
-
|
168
|
+
dispatchGroup.leave()
|
169
|
+
completion(socketClient)
|
124
170
|
case .clientInitiatedCancelAcknowledged:
|
125
171
|
verbose(message: "server acknowledged a cancel request")
|
126
172
|
self.dispatchGroup.leave()
|
127
|
-
|
173
|
+
if let command = self.currentlyExecutingCommand as? RubyCommand {
|
174
|
+
self.executeNext[command.id] = true
|
175
|
+
}
|
176
|
+
completion(socketClient)
|
128
177
|
case .alreadyClosedSockets, .connectionFailure, .malformedRequest, .malformedResponse, .serverError:
|
129
178
|
log(message: "error encountered while executing command:\n\(serverResponse)")
|
130
179
|
self.dispatchGroup.leave()
|
131
|
-
|
180
|
+
if let command = self.currentlyExecutingCommand as? RubyCommand {
|
181
|
+
self.executeNext[command.id] = true
|
182
|
+
}
|
183
|
+
completion(socketClient)
|
132
184
|
case .commandTimeout(let timeout):
|
133
185
|
log(message: "Runner timed out after \(timeout) second(s)")
|
134
186
|
}
|
@@ -144,6 +196,7 @@ extension Runner : SocketClientDelegateProtocol {
|
|
144
196
|
DispatchQueue.main.async {
|
145
197
|
self.thread?.cancel()
|
146
198
|
self.thread = nil
|
199
|
+
self.socketClient.closeSession()
|
147
200
|
self.socketClient = nil
|
148
201
|
verbose(message: "connection closed!")
|
149
202
|
if self.shouldLeaveDispatchGroupDuringDisconnect {
|
@@ -194,6 +247,25 @@ func verbose(message: String) {
|
|
194
247
|
logger.verbose(message: message)
|
195
248
|
}
|
196
249
|
|
250
|
+
private extension DispatchTimeInterval {
|
251
|
+
var timeInterval: TimeInterval {
|
252
|
+
var result: TimeInterval = 0
|
253
|
+
switch self {
|
254
|
+
case .seconds(let value):
|
255
|
+
result = TimeInterval(value)
|
256
|
+
case .milliseconds(let value):
|
257
|
+
result = TimeInterval(value)*0.001
|
258
|
+
case .microseconds(let value):
|
259
|
+
result = TimeInterval(value)*0.000001
|
260
|
+
case .nanoseconds(let value):
|
261
|
+
result = TimeInterval(value)*0.000000001
|
262
|
+
case .never:
|
263
|
+
fatalError()
|
264
|
+
}
|
265
|
+
return result
|
266
|
+
}
|
267
|
+
}
|
268
|
+
|
197
269
|
// Please don't remove the lines below
|
198
270
|
// They are used to detect outdated files
|
199
271
|
// FastlaneRunnerAPIVersion [0.9.2]
|
@@ -64,7 +64,7 @@ protocol SnapshotfileProtocol: class {
|
|
64
64
|
var addVideos: [String]? { get }
|
65
65
|
|
66
66
|
/// A path to screenshots.html template
|
67
|
-
var htmlTemplate: String { get }
|
67
|
+
var htmlTemplate: String? { get }
|
68
68
|
|
69
69
|
/// The directory where to store the build log
|
70
70
|
var buildlogPath: String { get }
|
@@ -149,7 +149,7 @@ extension SnapshotfileProtocol {
|
|
149
149
|
var appIdentifier: String? { return nil }
|
150
150
|
var addPhotos: [String]? { return nil }
|
151
151
|
var addVideos: [String]? { return nil }
|
152
|
-
var htmlTemplate: String { return
|
152
|
+
var htmlTemplate: String? { return nil }
|
153
153
|
var buildlogPath: String { return "~/Library/Logs/snapshot" }
|
154
154
|
var clean: Bool { return false }
|
155
155
|
var testWithoutBuilding: Bool? { return nil }
|
@@ -174,4 +174,4 @@ extension SnapshotfileProtocol {
|
|
174
174
|
|
175
175
|
// Please don't remove the lines below
|
176
176
|
// They are used to detect outdated files
|
177
|
-
// FastlaneRunnerAPIVersion [0.9.
|
177
|
+
// FastlaneRunnerAPIVersion [0.9.7]
|
@@ -13,6 +13,7 @@
|
|
13
13
|
//
|
14
14
|
|
15
15
|
import Foundation
|
16
|
+
import Dispatch
|
16
17
|
|
17
18
|
public enum SocketClientResponse: Error {
|
18
19
|
case alreadyClosedSockets
|
@@ -41,8 +42,12 @@ class SocketClient: NSObject {
|
|
41
42
|
fileprivate var outputStream: OutputStream!
|
42
43
|
fileprivate var cleaningUpAfterDone = false
|
43
44
|
fileprivate let dispatchGroup: DispatchGroup = DispatchGroup()
|
45
|
+
fileprivate let readSemaphore = DispatchSemaphore(value: 1)
|
46
|
+
fileprivate let writeSemaphore = DispatchSemaphore(value: 1)
|
44
47
|
fileprivate let commandTimeoutSeconds: Int
|
45
48
|
|
49
|
+
private let writeQueue: DispatchQueue
|
50
|
+
private let readQueue: DispatchQueue
|
46
51
|
private let streamQueue: DispatchQueue
|
47
52
|
private let host: String
|
48
53
|
private let port: UInt32
|
@@ -58,7 +63,9 @@ class SocketClient: NSObject {
|
|
58
63
|
self.host = host
|
59
64
|
self.port = port
|
60
65
|
self.commandTimeoutSeconds = commandTimeoutSeconds
|
61
|
-
self.
|
66
|
+
self.readQueue = DispatchQueue(label: "readQueue", qos: .background, attributes: .concurrent)
|
67
|
+
self.writeQueue = DispatchQueue(label: "writeQueue", qos: .background, attributes: .concurrent)
|
68
|
+
self.streamQueue = DispatchQueue.global(qos: .background)
|
62
69
|
self.socketStatus = .closed
|
63
70
|
self.socketDelegate = socketDelegate
|
64
71
|
super.init()
|
@@ -68,7 +75,7 @@ class SocketClient: NSObject {
|
|
68
75
|
var readStream: Unmanaged<CFReadStream>?
|
69
76
|
var writeStream: Unmanaged<CFWriteStream>?
|
70
77
|
|
71
|
-
self.streamQueue.
|
78
|
+
self.streamQueue.sync {
|
72
79
|
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, self.host as CFString, self.port, &readStream, &writeStream)
|
73
80
|
|
74
81
|
self.inputStream = readStream!.takeRetainedValue()
|
@@ -82,12 +89,12 @@ class SocketClient: NSObject {
|
|
82
89
|
}
|
83
90
|
|
84
91
|
self.dispatchGroup.enter()
|
85
|
-
self.
|
92
|
+
self.readQueue.sync {
|
86
93
|
self.inputStream.open()
|
87
94
|
}
|
88
95
|
|
89
96
|
self.dispatchGroup.enter()
|
90
|
-
self.
|
97
|
+
self.writeQueue.sync {
|
91
98
|
self.outputStream.open()
|
92
99
|
}
|
93
100
|
|
@@ -100,7 +107,7 @@ class SocketClient: NSObject {
|
|
100
107
|
let success = testDispatchTimeoutResult(timeoutResult, failureMessage: failureMessage, timeToWait: secondsToWait)
|
101
108
|
|
102
109
|
guard success else {
|
103
|
-
self.socketDelegate?.commandExecuted(serverResponse: .connectionFailure)
|
110
|
+
self.socketDelegate?.commandExecuted(serverResponse: .connectionFailure) { _ in }
|
104
111
|
return
|
105
112
|
}
|
106
113
|
|
@@ -125,7 +132,7 @@ class SocketClient: NSObject {
|
|
125
132
|
log(message: "Timeout: \(failureMessage)")
|
126
133
|
|
127
134
|
if case .seconds(let seconds) = timeToWait {
|
128
|
-
socketDelegate?.commandExecuted(serverResponse: .commandTimeout(seconds: seconds))
|
135
|
+
socketDelegate?.commandExecuted(serverResponse: .commandTimeout(seconds: seconds)) { _ in }
|
129
136
|
}
|
130
137
|
return false
|
131
138
|
}
|
@@ -140,29 +147,30 @@ class SocketClient: NSObject {
|
|
140
147
|
}
|
141
148
|
|
142
149
|
private func sendThroughQueue(string: String) {
|
143
|
-
|
144
|
-
|
145
|
-
_ = data.withUnsafeBytes { self.outputStream.write($0, maxLength: data.count) }
|
146
|
-
}
|
150
|
+
let data = string.data(using: .utf8)!
|
151
|
+
_ = data.withUnsafeBytes { self.outputStream.write($0, maxLength: data.count) }
|
147
152
|
}
|
148
153
|
|
149
154
|
private func privateSend(string: String) {
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
155
|
+
writeQueue.sync {
|
156
|
+
writeSemaphore.wait()
|
157
|
+
self.sendThroughQueue(string: string)
|
158
|
+
writeSemaphore.signal()
|
159
|
+
let timeoutSeconds = self.cleaningUpAfterDone ? 1 : self.commandTimeoutSeconds
|
160
|
+
let timeToWait = DispatchTimeInterval.seconds(timeoutSeconds)
|
161
|
+
let commandTimeout = DispatchTime.now() + timeToWait
|
162
|
+
let timeoutResult = writeSemaphore.wait(timeout: commandTimeout)
|
163
|
+
|
164
|
+
_ = self.testDispatchTimeoutResult(timeoutResult, failureMessage: "Ruby process didn't return after: \(SocketClient.connectTimeoutSeconds) seconds", timeToWait: timeToWait)
|
165
|
+
|
166
|
+
}
|
159
167
|
}
|
160
168
|
|
161
169
|
private func send(string: String) {
|
162
170
|
guard !self.cleaningUpAfterDone else {
|
163
171
|
// This will happen after we abort if there are commands waiting to be executed
|
164
172
|
// Need to check state of SocketClient in command runner to make sure we can accept `send`
|
165
|
-
socketDelegate?.commandExecuted(serverResponse: .alreadyClosedSockets)
|
173
|
+
socketDelegate?.commandExecuted(serverResponse: .alreadyClosedSockets) { _ in }
|
166
174
|
return
|
167
175
|
}
|
168
176
|
|
@@ -185,6 +193,15 @@ class SocketClient: NSObject {
|
|
185
193
|
stopOutputSession()
|
186
194
|
self.socketDelegate?.connectionsClosed()
|
187
195
|
}
|
196
|
+
|
197
|
+
public func enter() {
|
198
|
+
dispatchGroup.enter()
|
199
|
+
}
|
200
|
+
|
201
|
+
public func leave() {
|
202
|
+
readSemaphore.signal()
|
203
|
+
writeSemaphore.signal()
|
204
|
+
}
|
188
205
|
}
|
189
206
|
|
190
207
|
extension SocketClient: StreamDelegate {
|
@@ -246,18 +263,24 @@ extension SocketClient: StreamDelegate {
|
|
246
263
|
}
|
247
264
|
|
248
265
|
func read() {
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
266
|
+
readQueue.sync {
|
267
|
+
self.readSemaphore.wait()
|
268
|
+
var buffer = [UInt8](repeating: 0, count: maxReadLength)
|
269
|
+
var output = ""
|
270
|
+
while self.inputStream!.hasBytesAvailable {
|
271
|
+
let bytesRead: Int = inputStream!.read(&buffer, maxLength: buffer.count)
|
272
|
+
if bytesRead >= 0 {
|
273
|
+
guard let read = String(bytes: buffer[..<bytesRead], encoding: .utf8) else {
|
274
|
+
fatalError("Unable to decode bytes from buffer \(buffer[..<bytesRead])")
|
275
|
+
}
|
276
|
+
output.append(contentsOf: read)
|
277
|
+
} else {
|
278
|
+
verbose(message: "Stream read() error")
|
279
|
+
}
|
257
280
|
}
|
281
|
+
self.processResponse(string: output, socket: self)
|
282
|
+
readSemaphore.signal()
|
258
283
|
}
|
259
|
-
|
260
|
-
processResponse(string: output)
|
261
284
|
}
|
262
285
|
|
263
286
|
func handleFailure(message: [String]) {
|
@@ -266,11 +289,12 @@ extension SocketClient: StreamDelegate {
|
|
266
289
|
self.send(rubyCommand: shutdownCommand)
|
267
290
|
}
|
268
291
|
|
269
|
-
func processResponse(string: String) {
|
292
|
+
func processResponse(string: String, socket: SocketClient) {
|
270
293
|
guard string.count > 0 else {
|
271
|
-
self.socketDelegate?.commandExecuted(serverResponse: .malformedResponse)
|
272
|
-
|
273
|
-
|
294
|
+
self.socketDelegate?.commandExecuted(serverResponse: .malformedResponse) {
|
295
|
+
self.handleFailure(message: ["empty response from ruby process"])
|
296
|
+
$0.writeSemaphore.signal()
|
297
|
+
}
|
274
298
|
return
|
275
299
|
}
|
276
300
|
|
@@ -279,24 +303,31 @@ extension SocketClient: StreamDelegate {
|
|
279
303
|
verbose(message: "response is: \(responseString)")
|
280
304
|
switch socketResponse.responseType {
|
281
305
|
case .clientInitiatedCancel:
|
282
|
-
self.socketDelegate?.commandExecuted(serverResponse: .clientInitiatedCancelAcknowledged)
|
283
|
-
|
306
|
+
self.socketDelegate?.commandExecuted(serverResponse: .clientInitiatedCancelAcknowledged) {
|
307
|
+
self.closeSession(sendAbort: false)
|
308
|
+
$0.writeSemaphore.signal()
|
309
|
+
}
|
310
|
+
|
284
311
|
|
285
312
|
case .failure(let failureInformation):
|
286
|
-
self.socketDelegate?.commandExecuted(serverResponse: .serverError)
|
287
|
-
|
313
|
+
self.socketDelegate?.commandExecuted(serverResponse: .serverError) {
|
314
|
+
self.handleFailure(message: failureInformation)
|
315
|
+
$0.writeSemaphore.signal()
|
316
|
+
}
|
317
|
+
|
288
318
|
|
289
319
|
case .parseFailure(let failureInformation):
|
290
|
-
self.socketDelegate?.commandExecuted(serverResponse: .malformedResponse)
|
291
|
-
|
320
|
+
self.socketDelegate?.commandExecuted(serverResponse: .malformedResponse) {
|
321
|
+
self.handleFailure(message: failureInformation)
|
322
|
+
$0.writeSemaphore.signal()
|
323
|
+
}
|
324
|
+
|
292
325
|
|
293
326
|
case .readyForNext(let returnedObject, let closureArgumentValue):
|
294
|
-
self.socketDelegate?.commandExecuted(serverResponse: .success(returnedObject: returnedObject, closureArgumentValue: closureArgumentValue))
|
295
|
-
|
296
|
-
|
327
|
+
self.socketDelegate?.commandExecuted(serverResponse: .success(returnedObject: returnedObject, closureArgumentValue: closureArgumentValue)) {
|
328
|
+
$0.writeSemaphore.signal()
|
329
|
+
}
|
297
330
|
}
|
298
|
-
|
299
|
-
self.dispatchGroup.leave() // should now pull the next piece of work
|
300
331
|
}
|
301
332
|
}
|
302
333
|
|