add swiftUI code
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Network
|
||||
|
||||
public class NWSocket {
|
||||
private let connection: NWConnection
|
||||
|
||||
public init(_ connection: NWConnection) {
|
||||
self.connection = connection
|
||||
}
|
||||
|
||||
public func read() throws -> Data {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result: Result<Data, Error>!
|
||||
connection.receive(minimumIncompleteLength: 2, maximumLength: 2) { content, _, _, error in
|
||||
if let error {
|
||||
result = .failure(error)
|
||||
} else {
|
||||
result = .success(content!)
|
||||
}
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
let lengthChunk = try result.get()
|
||||
let length = Int(LibboxDecodeLengthChunk(lengthChunk))
|
||||
connection.receive(minimumIncompleteLength: length, maximumLength: length) { content, _, _, error in
|
||||
if let error {
|
||||
result = .failure(error)
|
||||
} else {
|
||||
result = .success(content!)
|
||||
}
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
return try result.get()
|
||||
}
|
||||
|
||||
public func write(_ data: Data?) throws {
|
||||
guard let data else {
|
||||
return
|
||||
}
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result: Error?
|
||||
connection.send(content: LibboxEncodeChunkedMessage(data), isComplete: false, completion: .contentProcessed { error in
|
||||
result = error
|
||||
semaphore.wait()
|
||||
})
|
||||
if let result {
|
||||
throw result
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ data: Data?) {
|
||||
guard let data else {
|
||||
return
|
||||
}
|
||||
connection.send(content: LibboxEncodeChunkedMessage(data), completion: .idempotent)
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
connection.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Network
|
||||
|
||||
public class ProfileServer {
|
||||
private var listener: NWListener
|
||||
|
||||
@available(iOS 16.0, macOS 13.0, *)
|
||||
public init() throws {
|
||||
listener = try NWListener(using: .applicationService)
|
||||
listener.service = NWListener.Service(applicationService: "sing-box:profile")
|
||||
listener.newConnectionHandler = { connection in
|
||||
connection.stateUpdateHandler = { state in
|
||||
if state == .ready {
|
||||
Task.detached {
|
||||
try await Task.sleep(nanoseconds: NSEC_PER_MSEC * 100)
|
||||
await ProfileConnection(connection).process()
|
||||
}
|
||||
}
|
||||
}
|
||||
connection.start(queue: .global())
|
||||
}
|
||||
}
|
||||
|
||||
public func start() {
|
||||
listener.start(queue: .global())
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
listener.cancel()
|
||||
}
|
||||
|
||||
class ProfileConnection {
|
||||
private let connection: NWSocket
|
||||
|
||||
init(_ connection: NWConnection) {
|
||||
self.connection = NWSocket(connection)
|
||||
}
|
||||
|
||||
func process() async {
|
||||
do {
|
||||
try await writeProfilePreviewList()
|
||||
} catch {
|
||||
NSLog("profile server: write profile list: \(error.localizedDescription)")
|
||||
writeError(error.localizedDescription)
|
||||
return
|
||||
}
|
||||
do {
|
||||
while true {
|
||||
let message = try connection.read()
|
||||
try processMessage(message)
|
||||
}
|
||||
} catch {
|
||||
NSLog("profile server: process connection: \(error.localizedDescription)")
|
||||
writeError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func processMessage(_ data: Data) throws {
|
||||
if data.count == 0 {
|
||||
return
|
||||
}
|
||||
let messageType = Int64(data[0])
|
||||
switch messageType {
|
||||
case LibboxMessageTypeProfileContentRequest:
|
||||
Task {
|
||||
try await processProfileContentRequest(data)
|
||||
}
|
||||
default:
|
||||
throw NSError(domain: "unexpected message type \(messageType)", code: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func processProfileContentRequest(_ data: Data) async throws {
|
||||
var error: NSError?
|
||||
let request = LibboxDecodeProfileContentRequest(data, &error)
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
|
||||
let profile = try await ProfileManager.get(request!.profileID)
|
||||
guard let profile else {
|
||||
throw NSError(domain: "profile not found", code: 0)
|
||||
}
|
||||
let content = LibboxProfileContent()
|
||||
content.name = profile.name
|
||||
switch profile.type {
|
||||
case .local:
|
||||
content.type = LibboxProfileTypeLocal
|
||||
case .icloud:
|
||||
content.type = LibboxProfileTypeiCloud
|
||||
case .remote:
|
||||
content.type = LibboxProfileTypeRemote
|
||||
}
|
||||
content.config = try profile.read()
|
||||
if profile.type != .local {
|
||||
content.remotePath = profile.remoteURL!
|
||||
}
|
||||
if profile.type == .remote {
|
||||
content.autoUpdate = profile.autoUpdate
|
||||
content.autoUpdateInterval = profile.autoUpdateInterval
|
||||
if let lastUpdated = profile.lastUpdated {
|
||||
content.lastUpdated = Int64(lastUpdated.timeIntervalSince1970)
|
||||
}
|
||||
}
|
||||
try connection.write(content.encode())
|
||||
}
|
||||
|
||||
private func writeProfilePreviewList() async throws {
|
||||
let profiles = try await ProfileManager.list()
|
||||
let encoder = LibboxProfileEncoder()
|
||||
for profile in profiles {
|
||||
let preview = LibboxProfilePreview()
|
||||
preview.profileID = profile.mustID
|
||||
preview.name = profile.name
|
||||
switch profile.type {
|
||||
case .local:
|
||||
preview.type = LibboxProfileTypeLocal
|
||||
case .icloud:
|
||||
preview.type = LibboxProfileTypeiCloud
|
||||
case .remote:
|
||||
preview.type = LibboxProfileTypeRemote
|
||||
}
|
||||
encoder.append(preview)
|
||||
}
|
||||
try connection.write(encoder.encode())
|
||||
}
|
||||
|
||||
private func writeError(_ message: String) {
|
||||
let errorMessage = LibboxErrorMessage()
|
||||
errorMessage.message = message
|
||||
try? connection.write(errorMessage.encode())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user