fastlane 2.149.0 → 2.150.0.rc4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +82 -82
- data/deliver/lib/deliver.rb +0 -1
- data/deliver/lib/deliver/app_screenshot.rb +26 -25
- 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 +155 -90
- 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 +134 -39
- data/fastlane/lib/fastlane/actions/docs/capture_ios_screenshots.md +1 -1
- data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +77 -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/Deliverfile.swift +1 -1
- data/fastlane/swift/Fastlane.swift +48 -12
- data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/xcshareddata/xcschemes/FastlaneRunner.xcscheme +3 -9
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/LaneFileProtocol.swift +2 -5
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +1 -1
- data/fastlane/swift/Precheckfile.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/Scanfile.swift +1 -1
- data/fastlane/swift/ScanfileProtocol.swift +1 -1
- data/fastlane/swift/Screengrabfile.swift +1 -1
- data/fastlane/swift/Snapshotfile.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/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/fastlane_core/lib/fastlane_core/project.rb +0 -1
- data/frameit/lib/frameit/device_types.rb +100 -100
- data/produce/lib/produce/itunes_connect.rb +32 -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/.DS_Store +0 -0
- data/spaceship/lib/spaceship/client.rb +4 -3
- data/spaceship/lib/spaceship/connect_api.rb +25 -2
- 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 +120 -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 +93 -0
- data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +101 -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/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 +841 -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 +44 -39
- data/deliver/lib/deliver/upload_assets.rb +0 -27
- data/fastlane/lib/assets/.s3_html_template.erb.swp +0 -0
- data/fastlane/lib/fastlane/actions/.hipchat.rb.swp +0 -0
- data/fastlane/lib/fastlane/actions/.slack.rb.swp +0 -0
- data/scan/lib/scan/.options.rb.swp +0 -0
@@ -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
|
|