Merge branch 'pr/159' into develop

# Conflicts:
#	ShadowsocksX-NG/Base.lproj/MainMenu.xib
This commit is contained in:
Charlie Qiu
2017-01-10 15:23:13 +08:00
45 changed files with 1428 additions and 787 deletions

View File

@ -6,7 +6,7 @@ target 'ShadowsocksX-NG' do
use_frameworks!
# Pods for ShadowsocksX-NG
pod 'Alamofire', '~> 4.0.1'
pod 'Alamofire', '~> 4.2.0'
pod "GCDWebServer", "~> 3.0"
target 'ShadowsocksX-NGTests' do

View File

@ -1,20 +1,20 @@
PODS:
- Alamofire (4.0.1)
- Alamofire (4.2.0)
- BRLOptionParser (0.3.1)
- GCDWebServer (3.3.3):
- GCDWebServer/Core (= 3.3.3)
- GCDWebServer/Core (3.3.3)
DEPENDENCIES:
- Alamofire (~> 4.0.1)
- Alamofire (~> 4.2.0)
- BRLOptionParser (~> 0.3.1)
- GCDWebServer (~> 3.0)
SPEC CHECKSUMS:
Alamofire: 7682d43245de14874acd142ec137b144aa1dd335
Alamofire: aa2e09d871c9160ac53c90e83c68064a94e3dfbe
BRLOptionParser: a03256a8ff003ca1f5376c55f55f210e085a3958
GCDWebServer: 1c39a1f0763e4eb492bee021e4270fce097d3555
PODFILE CHECKSUM: e675030dbd86de38216dc6c44e64ca1458be94bc
PODFILE CHECKSUM: d717746ef98bb719d87cee4fc334a392005fd32e
COCOAPODS: 1.0.1
COCOAPODS: 1.2.0.beta.1

View File

@ -55,8 +55,8 @@ In order to keep Alamofire focused specifically on core networking implementatio
## Requirements
- iOS 9.0+ / macOS 10.11+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 8.0+
- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 8.1+
- Swift 3.0+
## Migration Guides
@ -130,13 +130,13 @@ If you prefer not to use either of the aforementioned dependency managers, you c
- Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository:
```bash
```bash
$ git init
```
- Add Alamofire as a git [submodule](http://git-scm.com/docs/git-submodule) by running the following command:
```bash
```bash
$ git submodule add https://github.com/Alamofire/Alamofire.git
```
@ -158,7 +158,7 @@ $ git submodule add https://github.com/Alamofire/Alamofire.git
- And that's it!
> The `Alamofire.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device.
> The `Alamofire.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device.
---
@ -199,7 +199,7 @@ Alamofire contains five different response handlers by default including:
// Response Handler - Unserialized Response
func response(
queue: DispatchQueue?,
completionHandler: @escaping (DefaultDownloadResponse) -> Void)
completionHandler: @escaping (DefaultDataResponse) -> Void)
-> Self
// Response Data Handler - Serialized into Data
@ -240,7 +240,7 @@ The `response` handler does NOT evaluate any of the response data. It merely for
Alamofire.request("https://httpbin.org/get").response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.data)")
print("Error: \(response.error)")
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)")
@ -331,7 +331,7 @@ By default, Alamofire treats any completed request to be successful, regardless
Alamofire.request("https://httpbin.org/get")
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { response in
.responseData { response in
switch response.result {
case .success:
print("Validation Successful")
@ -360,7 +360,7 @@ Alamofire.request("https://httpbin.org/get").validate().responseJSON { response
Response Caching is handled on the system framework level by [`URLCache`](https://developer.apple.com/reference/foundation/urlcache). It provides a composite in-memory and on-disk cache and lets you manipulate the sizes of both the in-memory and on-disk portions.
> By default, Alamofire leverages the shared `URLCache`. In order to customize it, see the [Session Manager Configurations](#session-manager-configurations) section.
> By default, Alamofire leverages the shared `URLCache`. In order to customize it, see the [Session Manager Configurations](#session-manager) section.
### HTTP Methods
@ -520,7 +520,7 @@ Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON
}
```
> For HTTP headers that do not change, it is recommended to set them on the `URLSessionConfiguration` so they are automatically applied to any `URLSessionTask` created by the underlying `URLSession`. For more information, see the [Session Manager Configurations](#session-manager-configurations) section.
> For HTTP headers that do not change, it is recommended to set them on the `URLSessionConfiguration` so they are automatically applied to any `URLSessionTask` created by the underlying `URLSession`. For more information, see the [Session Manager Configurations](#session-manager) section.
The default Alamofire `SessionManager` provides a default set of headers for every `Request`. These include:
@ -528,7 +528,7 @@ The default Alamofire `SessionManager` provides a default set of headers for eve
- `Accept-Language`, which defaults to up to the top 6 preferred languages on the system, formatted like `en;q=1.0`, per [RFC 7231 §5.3.5](https://tools.ietf.org/html/rfc7231#section-5.3.5).
- `User-Agent`, which contains versioning information about the current app. For example: `iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 10.0.0) Alamofire/4.0.0`, per [RFC 7231 §5.5.3](https://tools.ietf.org/html/rfc7231#section-5.5.3).
If you need to customize these headers, a custom `URLSessionManagerConfiguration` should be created, the `defaultHTTPHeaders` property updated and the configuration applied to a new `SessionManager` instance.
If you need to customize these headers, a custom `URLSessionConfiguration` should be created, the `defaultHTTPHeaders` property updated and the configuration applied to a new `SessionManager` instance.
### Authentication
@ -603,7 +603,7 @@ Alamofire.download("https://httpbin.org/image/png").responseData { response in
}
```
> The `Alamofire.download` APIs should also be used if you need to download data while your app is in the background. For more information, please see the [Session Manager Configurations](#session-manager-configurations) section.
> The `Alamofire.download` APIs should also be used if you need to download data while your app is in the background. For more information, please see the [Session Manager Configurations](#session-manager) section.
#### Download File Destination
@ -623,7 +623,7 @@ let destination: DownloadRequest.DownloadFileDestination = { _, _ in
Alamofire.download(urlString, to: destination).response { response in
print(response)
if response.result.isSuccess, let imagePath = response.destinationURL?.path {
if response.error == nil, let imagePath = response.destinationURL?.path {
let image = UIImage(contentsOfFile: imagePath)
}
}
@ -672,6 +672,8 @@ Alamofire.download("https://httpbin.org/image/png")
If a `DownloadRequest` is cancelled or interrupted, the underlying URL session may generate resume data for the active `DownloadRequest`. If this happens, the resume data can be re-used to restart the `DownloadRequest` where it left off. The resume data can be accessed through the download response, then reused when trying to restart the request.
> **IMPORTANT:** On the latest release of all the Apple platforms (iOS 10, macOS 10.12, tvOS 10, watchOS 3), `resumeData` is broken on background URL session configurations. There's an underlying bug in the `resumeData` generation logic where the data is written incorrectly and will always fail to resume the download. For more information about the bug and possible workarounds, please see this Stack Overflow [post](http://stackoverflow.com/a/39347461/1342462).
```swift
class ImageRequestor {
private var resumeData: Data?
@ -711,7 +713,7 @@ class ImageRequestor {
When sending relatively small amounts of data to a server using JSON or URL encoded parameters, the `Alamofire.request` APIs are usually sufficient. If you need to send much larger amounts of data from a file URL or an `InputStream`, then the `Alamofire.upload` APIs are what you want to use.
> The `Alamofire.upload` APIs should also be used if you need to upload data while your app is in the background. For more information, please see the [Session Manager Configurations](#session-manager-configurations) section.
> The `Alamofire.upload` APIs should also be used if you need to upload data while your app is in the background. For more information, please see the [Session Manager Configurations](#session-manager) section.
#### Uploading Data
@ -1289,8 +1291,12 @@ class OAuth2Handler: RequestAdapter, RequestRetrier {
.responseJSON { [weak self] response in
guard let strongSelf = self else { return }
if let json = response.result.value as? [String: String] {
completion(true, json["access_token"], json["refresh_token"])
if
let json = response.result.value as? [String: Any],
let accessToken = json["access_token"] as? String,
let refreshToken = json["refresh_token"] as? String
{
completion(true, accessToken, refreshToken)
} else {
completion(false, nil, nil)
}

View File

@ -132,6 +132,16 @@ public enum AFError: Error {
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}
// MARK: - Adapt Error
struct AdaptError: Error {
let error: Error
}
extension Error {
var underlyingAdaptError: Error? { return (self as? AdaptError)?.error }
}
// MARK: - Error Booleans
extension AFError {

View File

@ -222,6 +222,13 @@ public func download(
/// If `destination` is not specified, the contents will remain in the temporary location determined by the
/// underlying URL session.
///
/// On the latest release of all the Apple platforms (iOS 10, macOS 10.12, tvOS 10, watchOS 3), `resumeData` is broken
/// on background URL session configurations. There's an underlying bug in the `resumeData` generation logic where the
/// data is written incorrectly and will always fail to resume the download. For more information about the bug and
/// possible workarounds, please refer to the following Stack Overflow post:
///
/// - http://stackoverflow.com/a/39347461/1342462
///
/// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
/// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for additional
/// information.
@ -435,6 +442,7 @@ public func upload(
///
/// - returns: The created `StreamRequest`.
@discardableResult
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
public func stream(withHostName hostName: String, port: Int) -> StreamRequest {
return SessionManager.default.stream(withHostName: hostName, port: port)
}
@ -449,6 +457,7 @@ public func stream(withHostName hostName: String, port: Int) -> StreamRequest {
///
/// - returns: The created `StreamRequest`.
@discardableResult
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
public func stream(with netService: NetService) -> StreamRequest {
return SessionManager.default.stream(with: netService)
}

View File

@ -199,7 +199,39 @@ public struct URLEncoding: ParameterEncoding {
var allowedCharacterSet = CharacterSet.urlQueryAllowed
allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
return string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
var escaped = ""
//==========================================================================================================
//
// Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few
// hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no
// longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more
// info, please refer to:
//
// - https://github.com/Alamofire/Alamofire/issues/206
//
//==========================================================================================================
if #available(iOS 8.3, *) {
escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
} else {
let batchSize = 50
var index = string.startIndex
while index != string.endIndex {
let startIndex = index
let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
let range = startIndex..<endIndex
let substring = string.substring(with: range)
escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? substring
index = endIndex
}
}
return escaped
}
private func query(_ parameters: [String: Any]) -> String {
@ -289,6 +321,34 @@ public struct JSONEncoding: ParameterEncoding {
return urlRequest
}
/// Creates a URL request by encoding the JSON object and setting the resulting data on the HTTP body.
///
/// - parameter urlRequest: The request to apply the JSON object to.
/// - parameter jsonObject: The JSON object to apply to the request.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let jsonObject = jsonObject else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
}
// MARK: -

View File

@ -110,6 +110,9 @@ open class Request {
/// The response received from the server, if any.
open var response: HTTPURLResponse? { return task?.response as? HTTPURLResponse }
/// The number of times the request has been retried.
open internal(set) var retryCount: UInt = 0
let originalTask: TaskConvertible?
var startTime: CFAbsoluteTime?
@ -351,13 +354,25 @@ open class DataRequest: Request {
let urlRequest: URLRequest
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
let urlRequest = try self.urlRequest.adapt(using: adapter)
return queue.syncResult { session.dataTask(with: urlRequest) }
do {
let urlRequest = try self.urlRequest.adapt(using: adapter)
return queue.syncResult { session.dataTask(with: urlRequest) }
} catch {
throw AdaptError(error: error)
}
}
}
// MARK: Properties
/// The request sent or to be sent to the server.
open override var request: URLRequest? {
if let request = super.request { return request }
if let requestable = originalTask as? Requestable { return requestable.urlRequest }
return nil
}
/// The progress of fetching the response data from the server for the request.
open var progress: Progress { return dataDelegate.progress }
@ -438,22 +453,37 @@ open class DownloadRequest: Request {
case resumeData(Data)
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
let task: URLSessionTask
do {
let task: URLSessionTask
switch self {
case let .request(urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.downloadTask(with: urlRequest) }
case let .resumeData(resumeData):
task = queue.syncResult { session.downloadTask(withResumeData: resumeData) }
switch self {
case let .request(urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.downloadTask(with: urlRequest) }
case let .resumeData(resumeData):
task = queue.syncResult { session.downloadTask(withResumeData: resumeData) }
}
return task
} catch {
throw AdaptError(error: error)
}
return task
}
}
// MARK: Properties
/// The request sent or to be sent to the server.
open override var request: URLRequest? {
if let request = super.request { return request }
if let downloadable = originalTask as? Downloadable, case let .request(urlRequest) = downloadable {
return urlRequest
}
return nil
}
/// The resume data of the underlying download task if available after a failure.
open var resumeData: Data? { return downloadDelegate.resumeData }
@ -471,7 +501,7 @@ open class DownloadRequest: Request {
NotificationCenter.default.post(
name: Notification.Name.Task.DidCancel,
object: self,
userInfo: [Notification.Key.Task: task]
userInfo: [Notification.Key.Task: task as Any]
)
}
@ -528,26 +558,42 @@ open class UploadRequest: DataRequest {
case stream(InputStream, URLRequest)
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
let task: URLSessionTask
do {
let task: URLSessionTask
switch self {
case let .data(data, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.uploadTask(with: urlRequest, from: data) }
case let .file(url, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.uploadTask(with: urlRequest, fromFile: url) }
case let .stream(_, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.uploadTask(withStreamedRequest: urlRequest) }
switch self {
case let .data(data, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.uploadTask(with: urlRequest, from: data) }
case let .file(url, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.uploadTask(with: urlRequest, fromFile: url) }
case let .stream(_, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.uploadTask(withStreamedRequest: urlRequest) }
}
return task
} catch {
throw AdaptError(error: error)
}
return task
}
}
// MARK: Properties
/// The request sent or to be sent to the server.
open override var request: URLRequest? {
if let request = super.request { return request }
guard let uploadable = originalTask as? Uploadable else { return nil }
switch uploadable {
case .data(_, let urlRequest), .file(_, let urlRequest), .stream(_, let urlRequest):
return urlRequest
}
}
/// The progress of uploading the payload to the server for the upload request.
open var uploadProgress: Progress { return uploadDelegate.uploadProgress }
@ -577,6 +623,7 @@ open class UploadRequest: DataRequest {
#if !os(watchOS)
/// Specific type of `Request` that manages an underlying `URLSessionStreamTask`.
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
open class StreamRequest: Request {
enum Streamable: TaskConvertible {
case stream(hostName: String, port: Int)

View File

@ -38,13 +38,17 @@ public struct DefaultDataResponse {
/// The error encountered while executing or validating the request.
public let error: Error?
/// The timeline of the complete lifecycle of the request.
public let timeline: Timeline
var _metrics: AnyObject?
init(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) {
init(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?, timeline: Timeline = Timeline()) {
self.request = request
self.response = response
self.data = data
self.error = error
self.timeline = timeline
}
}
@ -64,7 +68,7 @@ public struct DataResponse<Value> {
/// The result of response serialization.
public let result: Result<Value>
/// The timeline of the complete lifecycle of the `Request`.
/// The timeline of the complete lifecycle of the request.
public let timeline: Timeline
var _metrics: AnyObject?
@ -139,6 +143,9 @@ public struct DefaultDownloadResponse {
/// The error encountered while executing or validating the request.
public let error: Error?
/// The timeline of the complete lifecycle of the request.
public let timeline: Timeline
var _metrics: AnyObject?
init(
@ -147,7 +154,8 @@ public struct DefaultDownloadResponse {
temporaryURL: URL?,
destinationURL: URL?,
resumeData: Data?,
error: Error?)
error: Error?,
timeline: Timeline = Timeline())
{
self.request = request
self.response = response
@ -155,6 +163,7 @@ public struct DefaultDownloadResponse {
self.destinationURL = destinationURL
self.resumeData = resumeData
self.error = error
self.timeline = timeline
}
}

View File

@ -84,6 +84,22 @@ public struct DownloadResponseSerializer<Value>: DownloadResponseSerializerProto
}
}
// MARK: - Timeline
extension Request {
var timeline: Timeline {
let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
return Timeline(
requestStartTime: self.startTime ?? CFAbsoluteTimeGetCurrent(),
initialResponseTime: initialResponseTime,
requestCompletedTime: requestCompletedTime,
serializationCompletedTime: CFAbsoluteTimeGetCurrent()
)
}
}
// MARK: - Default
extension DataRequest {
@ -101,7 +117,8 @@ extension DataRequest {
request: self.request,
response: self.response,
data: self.delegate.data,
error: self.delegate.error
error: self.delegate.error,
timeline: self.timeline
)
dataResponse.add(self.delegate.metrics)
@ -136,22 +153,12 @@ extension DataRequest {
self.delegate.error
)
let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
let timeline = Timeline(
requestStartTime: self.startTime ?? CFAbsoluteTimeGetCurrent(),
initialResponseTime: initialResponseTime,
requestCompletedTime: requestCompletedTime,
serializationCompletedTime: CFAbsoluteTimeGetCurrent()
)
var dataResponse = DataResponse<T.SerializedObject>(
request: self.request,
response: self.response,
data: self.delegate.data,
result: result,
timeline: timeline
timeline: self.timeline
)
dataResponse.add(self.delegate.metrics)
@ -184,7 +191,8 @@ extension DownloadRequest {
temporaryURL: self.downloadDelegate.temporaryURL,
destinationURL: self.downloadDelegate.destinationURL,
resumeData: self.downloadDelegate.resumeData,
error: self.downloadDelegate.error
error: self.downloadDelegate.error,
timeline: self.timeline
)
downloadResponse.add(self.delegate.metrics)
@ -219,16 +227,6 @@ extension DownloadRequest {
self.downloadDelegate.error
)
let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
let timeline = Timeline(
requestStartTime: self.startTime ?? CFAbsoluteTimeGetCurrent(),
initialResponseTime: initialResponseTime,
requestCompletedTime: requestCompletedTime,
serializationCompletedTime: CFAbsoluteTimeGetCurrent()
)
var downloadResponse = DownloadResponse<T.SerializedObject>(
request: self.request,
response: self.response,
@ -236,7 +234,7 @@ extension DownloadRequest {
destinationURL: self.downloadDelegate.destinationURL,
resumeData: self.downloadDelegate.resumeData,
result: result,
timeline: timeline
timeline: self.timeline
)
downloadResponse.add(self.delegate.metrics)

View File

@ -108,16 +108,53 @@ open class SessionDelegate: NSObject {
#if !os(watchOS)
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:readClosedFor:)`.
open var streamTaskReadClosed: ((URLSession, URLSessionStreamTask) -> Void)?
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
open var streamTaskReadClosed: ((URLSession, URLSessionStreamTask) -> Void)? {
get {
return _streamTaskReadClosed as? (URLSession, URLSessionStreamTask) -> Void
}
set {
_streamTaskReadClosed = newValue
}
}
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:writeClosedFor:)`.
open var streamTaskWriteClosed: ((URLSession, URLSessionStreamTask) -> Void)?
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
open var streamTaskWriteClosed: ((URLSession, URLSessionStreamTask) -> Void)? {
get {
return _streamTaskWriteClosed as? (URLSession, URLSessionStreamTask) -> Void
}
set {
_streamTaskWriteClosed = newValue
}
}
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:betterRouteDiscoveredFor:)`.
open var streamTaskBetterRouteDiscovered: ((URLSession, URLSessionStreamTask) -> Void)?
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
open var streamTaskBetterRouteDiscovered: ((URLSession, URLSessionStreamTask) -> Void)? {
get {
return _streamTaskBetterRouteDiscovered as? (URLSession, URLSessionStreamTask) -> Void
}
set {
_streamTaskBetterRouteDiscovered = newValue
}
}
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:streamTask:didBecome:outputStream:)`.
open var streamTaskDidBecomeInputAndOutputStreams: ((URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void)?
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
open var streamTaskDidBecomeInputAndOutputStreams: ((URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void)? {
get {
return _streamTaskDidBecomeInputStream as? (URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void
}
set {
_streamTaskDidBecomeInputStream = newValue
}
}
var _streamTaskReadClosed: Any?
var _streamTaskWriteClosed: Any?
var _streamTaskBetterRouteDiscovered: Any?
var _streamTaskDidBecomeInputStream: Any?
#endif
@ -166,17 +203,19 @@ open class SessionDelegate: NSObject {
#endif
#if !os(watchOS)
switch selector {
case #selector(URLSessionStreamDelegate.urlSession(_:readClosedFor:)):
return streamTaskReadClosed != nil
case #selector(URLSessionStreamDelegate.urlSession(_:writeClosedFor:)):
return streamTaskWriteClosed != nil
case #selector(URLSessionStreamDelegate.urlSession(_:betterRouteDiscoveredFor:)):
return streamTaskBetterRouteDiscovered != nil
case #selector(URLSessionStreamDelegate.urlSession(_:streamTask:didBecome:outputStream:)):
return streamTaskDidBecomeInputAndOutputStreams != nil
default:
break
if #available(iOS 9.0, macOS 10.11, tvOS 9.0, *) {
switch selector {
case #selector(URLSessionStreamDelegate.urlSession(_:readClosedFor:)):
return streamTaskReadClosed != nil
case #selector(URLSessionStreamDelegate.urlSession(_:writeClosedFor:)):
return streamTaskWriteClosed != nil
case #selector(URLSessionStreamDelegate.urlSession(_:betterRouteDiscoveredFor:)):
return streamTaskBetterRouteDiscovered != nil
case #selector(URLSessionStreamDelegate.urlSession(_:streamTask:didBecome:outputStream:)):
return streamTaskDidBecomeInputAndOutputStreams != nil
default:
break
}
}
#endif
@ -637,6 +676,7 @@ extension SessionDelegate: URLSessionDownloadDelegate {
#if !os(watchOS)
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
extension SessionDelegate: URLSessionStreamDelegate {
/// Tells the delegate that the read side of the connection has been closed.
///

View File

@ -231,12 +231,14 @@ open class SessionManager {
headers: HTTPHeaders? = nil)
-> DataRequest
{
var originalRequest: URLRequest?
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)
originalRequest = try URLRequest(url: url, method: method, headers: headers)
let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
return request(encodedURLRequest)
} catch {
return request(failedWith: error)
return request(originalRequest, failedWith: error)
}
}
@ -248,9 +250,11 @@ open class SessionManager {
///
/// - returns: The created `DataRequest`.
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
var originalRequest: URLRequest?
do {
let originalRequest = try urlRequest.asURLRequest()
let originalTask = DataRequest.Requestable(urlRequest: originalRequest)
originalRequest = try urlRequest.asURLRequest()
let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
@ -261,15 +265,29 @@ open class SessionManager {
return request
} catch {
return request(failedWith: error)
return request(originalRequest, failedWith: error)
}
}
// MARK: Private - Request Implementation
private func request(failedWith error: Error) -> DataRequest {
let request = DataRequest(session: session, requestTask: .data(nil, nil), error: error)
if startRequestsImmediately { request.resume() }
private func request(_ urlRequest: URLRequest?, failedWith error: Error) -> DataRequest {
var requestTask: Request.RequestTask = .data(nil, nil)
if let urlRequest = urlRequest {
let originalTask = DataRequest.Requestable(urlRequest: urlRequest)
requestTask = .data(originalTask, nil)
}
let underlyingError = error.underlyingAdaptError ?? error
let request = DataRequest(session: session, requestTask: requestTask, error: underlyingError)
if let retrier = retrier, error is AdaptError {
allowRetrier(retrier, toRetry: request, with: underlyingError)
} else {
if startRequestsImmediately { request.resume() }
}
return request
}
@ -308,7 +326,7 @@ open class SessionManager {
let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)
return download(encodedURLRequest, to: destination)
} catch {
return download(failedWith: error)
return download(nil, to: destination, failedWith: error)
}
}
@ -334,7 +352,7 @@ open class SessionManager {
let urlRequest = try urlRequest.asURLRequest()
return download(.request(urlRequest), to: destination)
} catch {
return download(failedWith: error)
return download(nil, to: destination, failedWith: error)
}
}
@ -348,6 +366,13 @@ open class SessionManager {
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// On the latest release of all the Apple platforms (iOS 10, macOS 10.12, tvOS 10, watchOS 3), `resumeData` is broken
/// on background URL session configurations. There's an underlying bug in the `resumeData` generation logic where the
/// data is written incorrectly and will always fail to resume the download. For more information about the bug and
/// possible workarounds, please refer to the following Stack Overflow post:
///
/// - http://stackoverflow.com/a/39347461/1342462
///
/// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
/// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for
/// additional information.
@ -372,23 +397,43 @@ open class SessionManager {
{
do {
let task = try downloadable.task(session: session, adapter: adapter, queue: queue)
let request = DownloadRequest(session: session, requestTask: .download(downloadable, task))
let download = DownloadRequest(session: session, requestTask: .download(downloadable, task))
request.downloadDelegate.destination = destination
download.downloadDelegate.destination = destination
delegate[task] = request
delegate[task] = download
if startRequestsImmediately { request.resume() }
if startRequestsImmediately { download.resume() }
return request
return download
} catch {
return download(failedWith: error)
return download(downloadable, to: destination, failedWith: error)
}
}
private func download(failedWith error: Error) -> DownloadRequest {
let download = DownloadRequest(session: session, requestTask: .download(nil, nil), error: error)
if startRequestsImmediately { download.resume() }
private func download(
_ downloadable: DownloadRequest.Downloadable?,
to destination: DownloadRequest.DownloadFileDestination?,
failedWith error: Error)
-> DownloadRequest
{
var downloadTask: Request.RequestTask = .download(nil, nil)
if let downloadable = downloadable {
downloadTask = .download(downloadable, nil)
}
let underlyingError = error.underlyingAdaptError ?? error
let download = DownloadRequest(session: session, requestTask: downloadTask, error: underlyingError)
download.downloadDelegate.destination = destination
if let retrier = retrier, error is AdaptError {
allowRetrier(retrier, toRetry: download, with: underlyingError)
} else {
if startRequestsImmediately { download.resume() }
}
return download
}
@ -418,7 +463,7 @@ open class SessionManager {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(fileURL, with: urlRequest)
} catch {
return upload(failedWith: error)
return upload(nil, failedWith: error)
}
}
@ -436,7 +481,7 @@ open class SessionManager {
let urlRequest = try urlRequest.asURLRequest()
return upload(.file(fileURL, urlRequest))
} catch {
return upload(failedWith: error)
return upload(nil, failedWith: error)
}
}
@ -464,7 +509,7 @@ open class SessionManager {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(data, with: urlRequest)
} catch {
return upload(failedWith: error)
return upload(nil, failedWith: error)
}
}
@ -482,7 +527,7 @@ open class SessionManager {
let urlRequest = try urlRequest.asURLRequest()
return upload(.data(data, urlRequest))
} catch {
return upload(failedWith: error)
return upload(nil, failedWith: error)
}
}
@ -510,7 +555,7 @@ open class SessionManager {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(stream, with: urlRequest)
} catch {
return upload(failedWith: error)
return upload(nil, failedWith: error)
}
}
@ -528,7 +573,7 @@ open class SessionManager {
let urlRequest = try urlRequest.asURLRequest()
return upload(.stream(stream, urlRequest))
} catch {
return upload(failedWith: error)
return upload(nil, failedWith: error)
}
}
@ -614,6 +659,8 @@ open class SessionManager {
let formData = MultipartFormData()
multipartFormData(formData)
var tempFileURL: URL?
do {
var urlRequestWithContentType = try urlRequest.asURLRequest()
urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
@ -637,6 +684,8 @@ open class SessionManager {
let fileName = UUID().uuidString
let fileURL = directoryURL.appendingPathComponent(fileName)
tempFileURL = fileURL
var directoryError: Error?
// Create directory inside serial queue to ensure two threads don't do this in parallel
@ -652,16 +701,37 @@ open class SessionManager {
try formData.writeEncodedData(to: fileURL)
let upload = self.upload(fileURL, with: urlRequestWithContentType)
// Cleanup the temp file once the upload is complete
upload.delegate.queue.addOperation {
do {
try FileManager.default.removeItem(at: fileURL)
} catch {
// No-op
}
}
DispatchQueue.main.async {
let encodingResult = MultipartFormDataEncodingResult.success(
request: self.upload(fileURL, with: urlRequestWithContentType),
request: upload,
streamingFromDisk: true,
streamFileURL: fileURL
)
encodingCompletion?(encodingResult)
}
}
} catch {
// Cleanup the temp file in the event that the multipart form data encoding failed
if let tempFileURL = tempFileURL {
do {
try FileManager.default.removeItem(at: tempFileURL)
} catch {
// No-op
}
}
DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
}
}
@ -684,13 +754,26 @@ open class SessionManager {
return upload
} catch {
return upload(failedWith: error)
return upload(uploadable, failedWith: error)
}
}
private func upload(failedWith error: Error) -> UploadRequest {
let upload = UploadRequest(session: session, requestTask: .upload(nil, nil), error: error)
if startRequestsImmediately { upload.resume() }
private func upload(_ uploadable: UploadRequest.Uploadable?, failedWith error: Error) -> UploadRequest {
var uploadTask: Request.RequestTask = .upload(nil, nil)
if let uploadable = uploadable {
uploadTask = .upload(uploadable, nil)
}
let underlyingError = error.underlyingAdaptError ?? error
let upload = UploadRequest(session: session, requestTask: uploadTask, error: underlyingError)
if let retrier = retrier, error is AdaptError {
allowRetrier(retrier, toRetry: upload, with: underlyingError)
} else {
if startRequestsImmediately { upload.resume() }
}
return upload
}
@ -709,6 +792,7 @@ open class SessionManager {
///
/// - returns: The created `StreamRequest`.
@discardableResult
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
open func stream(withHostName hostName: String, port: Int) -> StreamRequest {
return stream(.stream(hostName: hostName, port: port))
}
@ -723,12 +807,14 @@ open class SessionManager {
///
/// - returns: The created `StreamRequest`.
@discardableResult
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
open func stream(with netService: NetService) -> StreamRequest {
return stream(.netService(netService))
}
// MARK: Private - Stream Implementation
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
private func stream(_ streamable: StreamRequest.Streamable) -> StreamRequest {
do {
let task = try streamable.task(session: session, adapter: adapter, queue: queue)
@ -744,6 +830,7 @@ open class SessionManager {
}
}
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
private func stream(failedWith error: Error) -> StreamRequest {
let stream = StreamRequest(session: session, requestTask: .stream(nil, nil), error: error)
if startRequestsImmediately { stream.resume() }
@ -762,6 +849,7 @@ open class SessionManager {
request.delegate.task = task // resets all task delegate data
request.retryCount += 1
request.startTime = CFAbsoluteTimeGetCurrent()
request.endTime = nil
@ -769,8 +857,31 @@ open class SessionManager {
return true
} catch {
request.delegate.error = error
request.delegate.error = error.underlyingAdaptError ?? error
return false
}
}
private func allowRetrier(_ retrier: RequestRetrier, toRetry request: Request, with error: Error) {
DispatchQueue.utility.async { [weak self] in
guard let strongSelf = self else { return }
retrier.should(strongSelf, retry: request, with: error) { shouldRetry, timeDelay in
guard let strongSelf = self else { return }
guard shouldRetry else {
if strongSelf.startRequestsImmediately { request.resume() }
return
}
let retrySucceeded = strongSelf.retry(request)
if retrySucceeded, let task = request.task {
strongSelf.delegate[task] = request
} else {
if strongSelf.startRequestsImmediately { request.resume() }
}
}
}
}
}

View File

@ -33,13 +33,16 @@ open class TaskDelegate: NSObject {
/// The serial operation queue used to execute all operations after the task completes.
open let queue: OperationQueue
/// The data returned by the server.
public var data: Data? { return nil }
/// The error generated throughout the lifecyle of the task.
public var error: Error?
var task: URLSessionTask? {
didSet { reset() }
}
var data: Data? { return nil }
var error: Error?
var initialResponseTime: CFAbsoluteTime?
var credential: URLCredential?
var metrics: AnyObject? // URLSessionTaskMetrics
@ -331,29 +334,30 @@ class DownloadTaskDelegate: TaskDelegate, URLSessionDownloadDelegate {
{
temporaryURL = location
if let destination = destination {
let result = destination(location, downloadTask.response as! HTTPURLResponse)
let destination = result.destinationURL
let options = result.options
guard
let destination = destination,
let response = downloadTask.response as? HTTPURLResponse
else { return }
do {
destinationURL = destination
let result = destination(location, response)
let destinationURL = result.destinationURL
let options = result.options
if options.contains(.removePreviousFile) {
if FileManager.default.fileExists(atPath: destination.path) {
try FileManager.default.removeItem(at: destination)
}
}
self.destinationURL = destinationURL
if options.contains(.createIntermediateDirectories) {
let directory = destination.deletingLastPathComponent()
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
}
try FileManager.default.moveItem(at: location, to: destination)
} catch {
self.error = error
do {
if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.removeItem(at: destinationURL)
}
if options.contains(.createIntermediateDirectories) {
let directory = destinationURL.deletingLastPathComponent()
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
}
try FileManager.default.moveItem(at: location, to: destinationURL)
} catch {
self.error = error
}
}

10
Pods/Manifest.lock generated
View File

@ -1,20 +1,20 @@
PODS:
- Alamofire (4.0.1)
- Alamofire (4.2.0)
- BRLOptionParser (0.3.1)
- GCDWebServer (3.3.3):
- GCDWebServer/Core (= 3.3.3)
- GCDWebServer/Core (3.3.3)
DEPENDENCIES:
- Alamofire (~> 4.0.1)
- Alamofire (~> 4.2.0)
- BRLOptionParser (~> 0.3.1)
- GCDWebServer (~> 3.0)
SPEC CHECKSUMS:
Alamofire: 7682d43245de14874acd142ec137b144aa1dd335
Alamofire: aa2e09d871c9160ac53c90e83c68064a94e3dfbe
BRLOptionParser: a03256a8ff003ca1f5376c55f55f210e085a3958
GCDWebServer: 1c39a1f0763e4eb492bee021e4270fce097d3555
PODFILE CHECKSUM: e675030dbd86de38216dc6c44e64ca1458be94bc
PODFILE CHECKSUM: d717746ef98bb719d87cee4fc334a392005fd32e
COCOAPODS: 1.0.1
COCOAPODS: 1.2.0.beta.1

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,12 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

View File

@ -1,4 +1,14 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double AlamofireVersionNumber;

View File

@ -6,5 +6,6 @@ OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>4.0.1</string>
<string>4.2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@ -1,4 +1,12 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

View File

@ -4,5 +4,6 @@ HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Priva
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/BRLOptionParser
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

View File

@ -1,4 +1,12 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

View File

@ -1,4 +1,14 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "GCDWebServer.h"
#import "GCDWebServerConnection.h"

View File

@ -6,5 +6,6 @@ OTHER_LDFLAGS = -l"z" -framework "SystemConfiguration"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/GCDWebServer
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

View File

@ -34,6 +34,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>Alamofire</string>
<key>Type</key>
@ -66,6 +68,8 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</string>
<key>License</key>
<string>BSD</string>
<key>Title</key>
<string>GCDWebServer</string>
<key>Type</key>

View File

@ -59,8 +59,13 @@ code_sign_if_enabled() {
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
# Use the current code_sign_identitiy
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\""
/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1""
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
code_sign_cmd="$code_sign_cmd &"
fi
echo "$code_sign_cmd"
eval "$code_sign_cmd"
fi
}
@ -91,3 +96,6 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework"
install_framework "$BUILT_PRODUCTS_DIR/GCDWebServer/GCDWebServer.framework"
fi
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait
fi

View File

@ -18,17 +18,14 @@ case "${TARGETED_DEVICE_FAMILY}" in
2)
TARGET_DEVICE_ARGS="--target-device ipad"
;;
3)
TARGET_DEVICE_ARGS="--target-device tv"
;;
*)
TARGET_DEVICE_ARGS="--target-device mac"
;;
esac
realpath() {
DIRECTORY="$(cd "${1%/*}" && pwd)"
FILENAME="${1##*/}"
echo "$DIRECTORY/$FILENAME"
}
install_resource()
{
if [[ "$1" = /* ]] ; then
@ -70,7 +67,7 @@ EOM
xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
;;
*.xcassets)
ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH")
ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
;;
*)
@ -93,7 +90,7 @@ then
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
while read line; do
if [[ $line != "`realpath $PODS_ROOT`*" ]]; then
if [[ $line != "${PODS_ROOT}*" ]]; then
XCASSET_FILES+=("$line")
fi
done <<<"$OTHER_XCASSETS"

View File

@ -1,4 +1,14 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double Pods_ShadowsocksX_NGVersionNumber;

View File

@ -1,5 +1,5 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
CODE_SIGN_IDENTITY =
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/GCDWebServer"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks'

View File

@ -1,5 +1,5 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
CODE_SIGN_IDENTITY =
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/GCDWebServer"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks'

View File

@ -59,8 +59,13 @@ code_sign_if_enabled() {
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
# Use the current code_sign_identitiy
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\""
/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1""
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
code_sign_cmd="$code_sign_cmd &"
fi
echo "$code_sign_cmd"
eval "$code_sign_cmd"
fi
}
@ -82,3 +87,6 @@ strip_invalid_archs() {
fi
}
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait
fi

View File

@ -18,17 +18,14 @@ case "${TARGETED_DEVICE_FAMILY}" in
2)
TARGET_DEVICE_ARGS="--target-device ipad"
;;
3)
TARGET_DEVICE_ARGS="--target-device tv"
;;
*)
TARGET_DEVICE_ARGS="--target-device mac"
;;
esac
realpath() {
DIRECTORY="$(cd "${1%/*}" && pwd)"
FILENAME="${1##*/}"
echo "$DIRECTORY/$FILENAME"
}
install_resource()
{
if [[ "$1" = /* ]] ; then
@ -70,7 +67,7 @@ EOM
xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
;;
*.xcassets)
ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH")
ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
;;
*)
@ -93,7 +90,7 @@ then
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
while read line; do
if [[ $line != "`realpath $PODS_ROOT`*" ]]; then
if [[ $line != "${PODS_ROOT}*" ]]; then
XCASSET_FILES+=("$line")
fi
done <<<"$OTHER_XCASSETS"

View File

@ -1,4 +1,14 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double Pods_ShadowsocksX_NGTestsVersionNumber;

View File

@ -16,7 +16,7 @@
<key>FooterText</key>
<string>(The MIT License)
Copyright &#169; 2013-2015 Stephen Celis (&lt;stephen@stephencelis.com&gt;)
Copyright © 2013-2015 Stephen Celis (&lt;stephen@stephencelis.com&gt;)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@ -37,6 +37,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>BRLOptionParser</string>
<key>Type</key>

View File

@ -59,8 +59,13 @@ code_sign_if_enabled() {
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
# Use the current code_sign_identitiy
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\""
/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1""
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
code_sign_cmd="$code_sign_cmd &"
fi
echo "$code_sign_cmd"
eval "$code_sign_cmd"
fi
}
@ -82,3 +87,6 @@ strip_invalid_archs() {
fi
}
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait
fi

View File

@ -18,17 +18,14 @@ case "${TARGETED_DEVICE_FAMILY}" in
2)
TARGET_DEVICE_ARGS="--target-device ipad"
;;
3)
TARGET_DEVICE_ARGS="--target-device tv"
;;
*)
TARGET_DEVICE_ARGS="--target-device mac"
;;
esac
realpath() {
DIRECTORY="$(cd "${1%/*}" && pwd)"
FILENAME="${1##*/}"
echo "$DIRECTORY/$FILENAME"
}
install_resource()
{
if [[ "$1" = /* ]] ; then
@ -70,7 +67,7 @@ EOM
xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
;;
*.xcassets)
ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH")
ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
;;
*)
@ -93,7 +90,7 @@ then
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
while read line; do
if [[ $line != "`realpath $PODS_ROOT`*" ]]; then
if [[ $line != "${PODS_ROOT}*" ]]; then
XCASSET_FILES+=("$line")
fi
done <<<"$OTHER_XCASSETS"

View File

@ -57,6 +57,8 @@ So after you quit the app, the ss-local maybe be still running.
Added a manual mode which won't configure the system proxy settings.
Then you could configure your apps to use socks5 proxy manual.
Added global Keyboard shortcut <kbd></kbd> + <kbd></kbd> + <kbd>P</kbd> to switch between `global` mode and `auto` mode.
## Contributing
Contributions must be available on a separately named branch based on the latest version of the main branch develop.

View File

@ -430,7 +430,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 0730;
LastUpgradeCheck = 0810;
ORGANIZATIONNAME = qiuyuzhou;
TargetAttributes = {
9B0BFFE41D0460A70040E62B = {
@ -543,7 +543,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
3545247EFCD033C3FA63EA6C /* [CP] Check Pods Manifest.lock */ = {
@ -558,7 +558,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
3E819BD46B855EDB116A3C70 /* [CP] Copy Pods Resources */ = {
@ -618,7 +618,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
D8C2D6AF002916F4095E15E3 /* [CP] Copy Pods Resources */ = {
@ -768,6 +768,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -778,8 +779,10 @@
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
@ -813,6 +816,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -823,8 +827,10 @@
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
@ -843,6 +849,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
};
name = Release;
};
@ -850,6 +857,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = FE3237E9FB24D9B924A0E630 /* Pods-ShadowsocksX-NG.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
@ -877,6 +885,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = E9E9FB3855DA55D0710EE7BD /* Pods-ShadowsocksX-NG.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -14,7 +14,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "79C040AFDDCE1BCBF6D8B5EB0B85887F"
BlueprintIdentifier = "88E9EC28B8B46C3631E6B242B50F4442"
BuildableName = "Alamofire.framework"
BlueprintName = "Alamofire"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
@ -28,7 +28,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7AD154F318B10A340D705FD3003EAAC6"
BlueprintIdentifier = "3CB2B8006B2B1ACAF9ADCA1DC82E2290"
BuildableName = "libBRLOptionParser.a"
BlueprintName = "BRLOptionParser"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
@ -42,7 +42,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F69966EE760BB16DE7009DBE04D8B18D"
BlueprintIdentifier = "76AB1BA247F8CB57FCB2BA6577D40FE6"
BuildableName = "Pods_ShadowsocksX_NG.framework"
BlueprintName = "Pods-ShadowsocksX-NG"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
@ -56,7 +56,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9D7253CF594B2D7CB8127F7CCAFF916F"
BlueprintIdentifier = "339CC546E4A1696296EFEDC2FA79ADE0"
BuildableName = "Pods_ShadowsocksX_NGTests.framework"
BlueprintName = "Pods-ShadowsocksX-NGTests"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
@ -70,7 +70,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "3F0FDAFB5467946C22EE9F2EC643E2B0"
BlueprintIdentifier = "05800FAF969AA3CD7F8AEB1C36988B53"
BuildableName = "libPods-proxy_conf_helper.a"
BlueprintName = "Pods-proxy_conf_helper"
ReferencedContainer = "container:Pods/Pods.xcodeproj">

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -7,7 +7,7 @@
//
import Cocoa
import Carbon
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate {
@ -19,6 +19,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
var editUserRulesWinCtrl: UserRulesController!
var httpPreferencesWinCtrl : HTTPPreferencesWindowController!
let keyCode = kVK_ANSI_P
let modifierKeys = cmdKey+controlKey
var hotKeyRef: EventHotKeyRef?
var launchAtLoginController: LaunchAtLoginController = LaunchAtLoginController()
@IBOutlet weak var window: NSWindow!
@ -42,6 +46,22 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
@IBOutlet weak var lanchAtLoginMenuItem: NSMenuItem!
@IBOutlet weak var hudWindow: NSPanel!
@IBOutlet weak var panelView: NSView!
@IBOutlet weak var isNameTextField: NSTextField!
let kHudFadeInDuration: Double = 0.25
let kHudFadeOutDuration: Double = 0.5
let kHudDisplayDuration: Double = 2.0
let kHudAlphaValue: CGFloat = 0.75
let kHudCornerRadius: CGFloat = 18.0
let kHudHorizontalMargin: CGFloat = 30
let kHudHeight: CGFloat = 90.0
var timerToFadeOut: Timer? = nil
var fadingOut: Bool = false
var statusItem: NSStatusItem!
static let StatusItemIconWidth:CGFloat = 20
@ -137,8 +157,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
userNote.subtitle = "By Handle SS URL".localized
}
userNote.informativeText = "Host: \(profile.serverHost)"
" Port: \(profile.serverPort)"
" Encription Method: \(profile.method)".localized
//" Port: \(profile.serverPort)"
//" Encription Method: \(profile.method)".localized
userNote.soundName = NSUserNotificationDefaultSoundName
NSUserNotificationCenter.default
@ -167,6 +187,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
ProxyConfHelper.startMonitorPAC()
applyConfig()
SyncSSLocal()
// Register global hotkey
registerHotkey()
}
func applicationWillTerminate(_ aNotification: Notification) {
@ -174,6 +197,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
StopSSLocal()
StopPrivoxy()
ProxyConfHelper.disableProxy()
if let ref = hotKeyRef { UnregisterEventHotKey(ref) }
}
func applyConfig() {
@ -198,6 +222,63 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
}
}
// MARK: - Hotkey Methods
func registerHotkey() -> Void {
var gMyHotKeyID = EventHotKeyID()
gMyHotKeyID.signature = OSType(fourCharCodeFrom(string: "sxng"))
gMyHotKeyID.id = UInt32(keyCode)
var eventType = EventTypeSpec()
eventType.eventClass = OSType(kEventClassKeyboard)
eventType.eventKind = OSType(kEventHotKeyPressed)
// Void pointer to `self`:
let context = Unmanaged.passUnretained(self).toOpaque()
// Install handler.
InstallEventHandler(GetApplicationEventTarget(), {(nextHanlder, theEvent, userContext) -> OSStatus in
// Extract pointer to `self` from void pointer:
let mySelf = Unmanaged<AppDelegate>.fromOpaque(userContext!).takeUnretainedValue()
switch Globals.proxyType {
case .pac:
Globals.proxyType = .global
UserDefaults.standard.setValue("global", forKey: "ShadowsocksRunningMode")
mySelf.isNameTextField.stringValue = "Gobal Mode"
mySelf.updateRunningModeMenu()
mySelf.applyConfig()
case .global:
Globals.proxyType = .pac
UserDefaults.standard.setValue("auto", forKey: "ShadowsocksRunningMode")
mySelf.isNameTextField.stringValue = "Auto Mode"
mySelf.updateRunningModeMenu()
mySelf.applyConfig()
}
mySelf.fadeInHud()
return noErr
}, 1, &eventType, context, nil)
// Register hotkey.
RegisterEventHotKey(UInt32(keyCode),
UInt32(modifierKeys),
gMyHotKeyID,
GetApplicationEventTarget(),
0,
&hotKeyRef)
}
func fourCharCodeFrom(string: String) -> FourCharCode {
assert(string.characters.count == 4, "String length must be 4")
var result: FourCharCode = 0
for char in string.utf16 {
result = (result << 8) + FourCharCode(char)
}
return result
}
// MARK: - UI Methods
@IBAction func toggleRunning(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
var isOn = defaults.bool(forKey: "ShadowsocksOn")
@ -531,3 +612,93 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
}
}
extension AppDelegate {
func fadeInHud() -> Void {
if timerToFadeOut != nil {
timerToFadeOut?.invalidate()
timerToFadeOut = nil
}
fadingOut = false
hudWindow.orderFrontRegardless()
CATransaction.begin()
CATransaction.setAnimationDuration(kHudFadeInDuration)
CATransaction.setCompletionBlock { self.didFadeIn() }
panelView.layer?.opacity = 1.0
CATransaction.commit()
}
func didFadeIn() -> Void {
timerToFadeOut = Timer.scheduledTimer(
timeInterval: kHudDisplayDuration,
target: self,
selector: #selector(fadeOutHud),
userInfo: nil,
repeats: false)
}
func fadeOutHud() -> Void {
fadingOut = true
CATransaction.begin()
CATransaction.setAnimationDuration(kHudFadeOutDuration)
CATransaction.setCompletionBlock { self.didFadeOut() }
panelView.layer?.opacity = 0.0
CATransaction.commit()
}
func didFadeOut() -> Void {
if fadingOut {
self.hudWindow.orderOut(nil)
}
fadingOut = false
}
func setupHud() -> Void {
isNameTextField.stringValue = "Global Mode"
isNameTextField.sizeToFit()
var labelFrame: CGRect = isNameTextField.frame
var hudWindowFrame: CGRect = hudWindow.frame
hudWindowFrame.size.width = labelFrame.size.width + kHudHorizontalMargin * 2
hudWindowFrame.size.height = kHudHeight
let screenRect: NSRect = NSScreen.screens()![0].visibleFrame
hudWindowFrame.origin.x = (screenRect.size.width - hudWindowFrame.size.width) / 2
hudWindowFrame.origin.y = (screenRect.size.height - hudWindowFrame.size.height) / 2
hudWindow.setFrame(hudWindowFrame, display: true)
var viewFrame: NSRect = hudWindowFrame;
viewFrame.origin.x = 0
viewFrame.origin.y = 0
panelView.frame = viewFrame
labelFrame.origin.x = kHudHorizontalMargin
labelFrame.origin.y = (hudWindowFrame.size.height - labelFrame.size.height) / 2
isNameTextField.frame = labelFrame
}
func initUIComponent() -> Void {
hudWindow.isOpaque = false
hudWindow.backgroundColor = .clear
hudWindow.level = Int(CGWindowLevelForKey(.utilityWindow)) + 1000
hudWindow.styleMask = .borderless
hudWindow.hidesOnDeactivate = false
hudWindow.collectionBehavior = .canJoinAllSpaces
let viewLayer: CALayer = CALayer()
viewLayer.backgroundColor = CGColor.init(red: 0.05, green: 0.05, blue: 0.05, alpha: kHudAlphaValue)
viewLayer.cornerRadius = kHudCornerRadius
panelView.wantsLayer = true
panelView.layer = viewLayer
panelView.layer?.opacity = 0.0
setupHud()
}
override func awakeFromNib() {
initUIComponent()
}
}

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="16B2548a" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11542" systemVersion="16B2555" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11542"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -14,13 +15,15 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="ShadowsocksX_NG" customModuleProvider="target">
<connections>
<outlet property="RunningStatusMenuItem" destination="fzk-mE-CEV" id="R4x-gK-Qcw"/>
<outlet property="autoModeMenuItem" destination="r07-Gu-aEz" id="9aH-pQ-Rgi"/>
<outlet property="exportAllServerProfileItem" destination="6k0-gn-DQv" id="W2x-96-ISj"/>
<outlet property="globalModeMenuItem" destination="Mw3-Jm-eXA" id="ar5-Yx-3ze"/>
<outlet property="importBunchJsonFileItem" destination="T9g-gy-gvv" id="vua-jg-YWe"/>
<outlet property="lanchAtLoginMenuItem" destination="eUq-p7-ICK" id="q3D-7x-0db"/>
<outlet property="hudWindow" destination="QWV-F6-ac1" id="K6D-a4-oqE"/>
<outlet property="isNameTextField" destination="rUN-Nq-HDb" id="ayK-aM-rSa"/>
<outlet property="manualModeMenuItem" destination="8PR-gs-c5N" id="9qz-mU-5kt"/>
<outlet property="panelView" destination="rai-SH-9tZ" id="5aU-ld-rWb"/>
<outlet property="proxyMenuItem" destination="diI-fB-Rss" id="Qjk-9U-3Qy"/>
<outlet property="runningStatusMenuItem" destination="fzk-mE-CEV" id="Vwm-Rg-Ykn"/>
<outlet property="scanQRCodeMenuItem" destination="Qe6-bF-paT" id="XHa-pa-nCa"/>
@ -185,7 +188,7 @@
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="22" y="89"/>
<point key="canvasLocation" x="-2367" y="-139"/>
</menu>
<menu id="2oY-e9-q1Z">
<items>
@ -199,6 +202,30 @@
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
<point key="canvasLocation" x="-2113" y="-132"/>
</menu>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="QWV-F6-ac1" customClass="NSPanel">
<windowStyleMask key="styleMask" closable="YES" miniaturizable="YES" resizable="YES" utility="YES"/>
<rect key="contentRect" x="139" y="81" width="200" height="100"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<value key="minSize" type="size" width="200" height="100"/>
<value key="maxSize" type="size" width="200" height="100"/>
<view key="contentView" id="rai-SH-9tZ" userLabel="Panel View">
<rect key="frame" x="0.0" y="0.0" width="200" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="162" translatesAutoresizingMaskIntoConstraints="NO" id="rUN-Nq-HDb" userLabel="Is Name">
<rect key="frame" x="17" y="33" width="166" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" title="Label" usesSingleLineMode="YES" id="jcH-j9-Xl3">
<font key="font" metaFont="system" size="24"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="backgroundColor" name="alternateSelectedControlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<point key="canvasLocation" x="-2357" y="172"/>
</window>
</objects>
</document>

View File

@ -51,7 +51,7 @@ class ServerProfileManager: NSObject {
if activeProfileId != nil {
defaults.set(activeProfileId, forKey: "ActiveServerProfileId")
writeSSLocalConfFile((getActiveProfile()?.toJsonConfig())!)
_ = writeSSLocalConfFile((getActiveProfile()?.toJsonConfig())!)
} else {
defaults.removeObject(forKey: "ActiveServerProfileId")
removeSSLocalConfFile()

View File

@ -23,3 +23,12 @@ extension Data {
return hexBytes.joined(separator: "")
}
}
enum ProxyType {
case pac
case global
}
struct Globals {
static var proxyType = ProxyType.pac
}

View File

@ -44,9 +44,9 @@
"Turn Shadowsocks On" = "打开 Shadowsocks";
"Proxy - Auto By PAC" = "代理 - PAC自动";
"Proxy - Auto By PAC" = "代理 - PAC自动⌃⌘P";
"Proxy - Global" = "代理 - 全局";
"Proxy - Global" = "代理 - 全局⌃⌘P";
"Proxy - Manual" = "代理 - 手动";