diff --git a/ios/Network/CommandClient.swift b/ios/Network/CommandClient.swift deleted file mode 100644 index 4b2cac05..00000000 --- a/ios/Network/CommandClient.swift +++ /dev/null @@ -1,160 +0,0 @@ -import Foundation -import Libcore - -public class CommandClient: ObservableObject { - public enum ConnectionType { - case status - case groups - case log - case clashMode - } - - private let connectionType: ConnectionType - private let logMaxLines: Int - private var commandClient: LibboxCommandClient? - private var connectTask: Task? - - @Published public var isConnected: Bool - @Published public var status: LibboxStatusMessage? - @Published public var groups: [LibboxOutboundGroup]? - @Published public var logList: [String] - @Published public var clashModeList: [String] - @Published public var clashMode: String - - public init(_ connectionType: ConnectionType, logMaxLines: Int = 300) { - self.connectionType = connectionType - self.logMaxLines = logMaxLines - logList = [] - clashModeList = [] - clashMode = "" - isConnected = false - } - - public func connect() { - if isConnected { - return - } - if let connectTask { - connectTask.cancel() - } - connectTask = Task { - await connect0() - } - } - - public func disconnect() { - if let connectTask { - connectTask.cancel() - self.connectTask = nil - } - if let commandClient { - try? commandClient.disconnect() - self.commandClient = nil - } - } - - private nonisolated func connect0() async { - let clientOptions = LibboxCommandClientOptions() - switch connectionType { - case .status: - clientOptions.command = LibboxCommandStatus - case .groups: - clientOptions.command = LibboxCommandGroup - case .log: - clientOptions.command = LibboxCommandLog - case .clashMode: - clientOptions.command = LibboxCommandClashMode - } - clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC) - let client = LibboxNewCommandClient(clientHandler(self), clientOptions)! - do { - for i in 0 ..< 10 { - try await Task.sleep(nanoseconds: UInt64(Double(100 + (i * 50)) * Double(NSEC_PER_MSEC))) - try Task.checkCancellation() - do { - try client.connect() - await MainActor.run { - commandClient = client - } - return - } catch {} - try Task.checkCancellation() - } - } catch { - try? client.disconnect() - } - } - - private class clientHandler: NSObject, LibboxCommandClientHandlerProtocol { - private let commandClient: CommandClient - - init(_ commandClient: CommandClient) { - self.commandClient = commandClient - } - - func connected() { - DispatchQueue.main.async { [self] in - if commandClient.connectionType == .log { - commandClient.logList = [] - } - commandClient.isConnected = true - } - } - - func disconnected(_: String?) { - DispatchQueue.main.async { [self] in - commandClient.isConnected = false - } - } - - func clearLog() { - DispatchQueue.main.async { [self] in - commandClient.logList.removeAll() - } - } - - func writeLog(_ message: String?) { - guard let message else { - return - } - DispatchQueue.main.async { [self] in - if commandClient.logList.count > commandClient.logMaxLines { - commandClient.logList.removeFirst() - } - commandClient.logList.append(message) - } - } - - func writeStatus(_ message: LibboxStatusMessage?) { - DispatchQueue.main.async { [self] in - commandClient.status = message - } - } - - func writeGroups(_ groups: LibboxOutboundGroupIteratorProtocol?) { - guard let groups else { - return - } - var newGroups: [LibboxOutboundGroup] = [] - while groups.hasNext() { - newGroups.append(groups.next()!) - } - DispatchQueue.main.async { [self] in - commandClient.groups = newGroups - } - } - - func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) { - DispatchQueue.main.async { [self] in - commandClient.clashModeList = modeList!.toArray() - commandClient.clashMode = currentMode! - } - } - - func updateClashMode(_ newMode: String?) { - DispatchQueue.main.async { [self] in - commandClient.clashMode = newMode! - } - } - } -} \ No newline at end of file diff --git a/ios/Network/Extension+Iterator.swift b/ios/Network/Extension+Iterator.swift deleted file mode 100644 index b54611d0..00000000 --- a/ios/Network/Extension+Iterator.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation -import Libcore - -extension LibboxStringIteratorProtocol { - func toArray() -> [String] { - var array: [String] = [] - while hasNext() { - array.append(next()) - } - return array - } -} diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 22c5f1fb..225a73c1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -42,8 +42,6 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - D78B64E92B6EB41900C5D800 /* Extension+Iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B64E82B6EB41900C5D800 /* Extension+Iterator.swift */; }; - D7A729302B6E44F200FFFF41 /* CommandClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A7292F2B6E44F200FFFF41 /* CommandClient.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -166,8 +164,6 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9AC67B4DCF829F5B6F63AA7D /* Pods-Runner-SingBoxPacketTunnel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SingBoxPacketTunnel.release.xcconfig"; path = "Target Support Files/Pods-Runner-SingBoxPacketTunnel/Pods-Runner-SingBoxPacketTunnel.release.xcconfig"; sourceTree = ""; }; C20A211B58CE31B2738D133C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - D78B64E82B6EB41900C5D800 /* Extension+Iterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Iterator.swift"; sourceTree = ""; }; - D7A7292F2B6E44F200FFFF41 /* CommandClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandClient.swift; sourceTree = ""; }; F3FFE1D9C2D5629FACC123EE /* Pods-Runner-SingBoxPacketTunnel.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SingBoxPacketTunnel.profile.xcconfig"; path = "Target Support Files/Pods-Runner-SingBoxPacketTunnel/Pods-Runner-SingBoxPacketTunnel.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -321,7 +317,6 @@ 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( - D7A7292E2B6E44D500FFFF41 /* Network */, 6836D3FF2B57FECF00A79D75 /* Local Packages */, 03E392CD2ADDE103000ADF15 /* Shared */, 9740EEB11CF90186004384FC /* Flutter */, @@ -399,15 +394,6 @@ name = Frameworks; sourceTree = ""; }; - D7A7292E2B6E44D500FFFF41 /* Network */ = { - isa = PBXGroup; - children = ( - D7A7292F2B6E44F200FFFF41 /* CommandClient.swift */, - D78B64E82B6EB41900C5D800 /* Extension+Iterator.swift */, - ); - path = Network; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -687,14 +673,12 @@ 03B516772AE7634400EA47E2 /* Logger.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 03B516712AE74CCD00EA47E2 /* VPNConfig.swift in Sources */, - D78B64E92B6EB41900C5D800 /* Extension+Iterator.swift in Sources */, 03B5166B2AE7315E00EA47E2 /* AlertsEventHandler.swift in Sources */, 0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */, 0736958D2B3B79E0007249BE /* StatsEventHandler.swift in Sources */, 03B516692AE7306B00EA47E2 /* StatusEventHandler.swift in Sources */, 032158B82ADDF8BF008D943B /* VPNManager.swift in Sources */, 0736958F2B3B8048007249BE /* PlatformMethodHandler.swift in Sources */, - D7A729302B6E44F200FFFF41 /* CommandClient.swift in Sources */, 03B516672AE6B93A00EA47E2 /* MethodHandler.swift in Sources */, 03B5166D2AE7325500EA47E2 /* LogsEventHandler.swift in Sources */, 03E392D02ADDF1BD000ADF15 /* FilePath.swift in Sources */, diff --git a/ios/Runner/Handlers/LogsEventHandler.swift b/ios/Runner/Handlers/LogsEventHandler.swift index 368f6e66..c1b86208 100644 --- a/ios/Runner/Handlers/LogsEventHandler.swift +++ b/ios/Runner/Handlers/LogsEventHandler.swift @@ -1,28 +1,72 @@ import Foundation import Combine +import Libcore -public class LogsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler { +class LogsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler, LibboxCommandClientHandlerProtocol { static let name = "\(Bundle.main.serviceIdentifier)/service.logs" private var channel: FlutterEventChannel? - private var cancellable: AnyCancellable? - + + private var commandClient: LibboxCommandClient? + private var events: FlutterEventSink? + private var maxLines: Int + private var logList: [String] = [] + + private var lock: NSLock = NSLock() + public static func register(with registrar: FlutterPluginRegistrar) { - let instance = LogsEventHandler() - instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger()) - instance.channel?.setStreamHandler(instance) + let instance = LogsEventHandler() + instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger()) + instance.channel?.setStreamHandler(instance) + } + + init(maxLines: Int = 32) { + self.maxLines = maxLines + super.init() + let opts = LibboxCommandClientOptions() + opts.command = LibboxCommandLog + opts.statusInterval = Int64(2 * NSEC_PER_SEC) + commandClient = LibboxCommandClient(self, options: opts) + try? commandClient?.connect() } + deinit { + try? commandClient?.disconnect() + } + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { - VPNManager.shared.logClient?.connect() - cancellable = VPNManager.shared.logClient?.$logList.sink { [events] logsList in - events(logsList) - } + events(logList) + self.events = events return nil } - + public func onCancel(withArguments arguments: Any?) -> FlutterError? { - cancellable?.cancel() + events = nil return nil } + + func writeLog(_ message: String?) { + guard let message else { + return + } + lock.withLock { [self] in + if logList.count > maxLines { + logList.removeFirst() + } + logList.append(message) + DispatchQueue.main.async { [self] () in + events?(logList) + } + } + } +} + +extension LogsEventHandler { + public func clearLog() {} + public func connected() {} + public func disconnected(_ message: String?) {} + public func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) {} + public func updateClashMode(_ newMode: String?) {} + public func writeGroups(_ message: LibboxOutboundGroupIteratorProtocol?) {} + public func writeStatus(_ message: LibboxStatusMessage?) {} } diff --git a/ios/Runner/VPN/VPNManager.swift b/ios/Runner/VPN/VPNManager.swift index 0bb1d9c3..8bdff45b 100644 --- a/ios/Runner/VPN/VPNManager.swift +++ b/ios/Runner/VPN/VPNManager.swift @@ -39,9 +39,6 @@ class VPNManager: ObservableObject { @Published private(set) var upload: Int64 = 0 @Published private(set) var download: Int64 = 0 @Published private(set) var elapsedTime: TimeInterval = 0 - @Published private(set) var logList: [String] = [] - @Published private(set) var logCallback = false - private(set) var logClient: CommandClient? private var _connectTime: Date? private var connectTime: Date? { @@ -74,8 +71,6 @@ class VPNManager: ObservableObject { updateStats() elapsedTime = -1 * (connectTime?.timeIntervalSinceNow ?? 0) } - - logClient = CommandClient(.log) } deinit { @@ -91,7 +86,7 @@ class VPNManager: ObservableObject { do { try await loadVPNPreference() } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } @@ -112,7 +107,7 @@ class VPNManager: ObservableObject { try await newManager.loadFromPreferences() self.manager = newManager } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } @@ -122,7 +117,7 @@ class VPNManager: ObservableObject { try await manager.saveToPreferences() try await manager.loadFromPreferences() } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } @@ -162,7 +157,7 @@ class VPNManager: ObservableObject { } try await self?.loadVPNPreference() } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } }.store(in: &cancelBag) @@ -193,7 +188,7 @@ class VPNManager: ObservableObject { } } } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } @@ -207,7 +202,7 @@ class VPNManager: ObservableObject { "DisableMemoryLimit": (disableMemoryLimit ? "YES" : "NO") as NSString, ]) } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } connectTime = .now } @@ -216,17 +211,4 @@ class VPNManager: ObservableObject { guard state == .connected else { return } manager.connection.stopVPNTunnel() } - - func onServiceWriteLog(message: String) { - logCallback = true - if logList.count > 300 { - logList.removeFirst() - } - logList.append(message) - } - - func onServiceResetLogs() { - logCallback = false - logList.removeAll() - } }