Init commit

This commit is contained in:
世界
2023-07-15 15:03:45 +08:00
commit f441b89efb
122 changed files with 7355 additions and 0 deletions
@@ -0,0 +1,144 @@
import Foundation
import Libbox
import Library
import SwiftUI
public struct ActiveDashboardView: View {
public static let NotificationUpdateSelectedProfile = Notification.Name("update-selected-profile")
@Environment(\.scenePhase) var scenePhase
@Environment(\.selection) private var selection
@EnvironmentObject private var profile: ExtensionProfile
@State private var isLoading = true
@State private var profileList: [Profile] = []
@State private var selectedProfileID: Int64!
@State private var reasserting = false
@State private var observer: Any?
@State private var errorPresented = false
@State private var errorMessage = ""
public init() {}
public var body: some View {
viewBuilder {
if isLoading {
ProgressView().onAppear {
Task.detached {
await doReload()
}
}
} else {
if profileList.isEmpty {
Text("Empty profiles")
} else {
#if os(iOS)
StartStopButton()
#endif
if profile.status.isConnected {
Section("Status") {
ExtensionStatusView()
}
}
Section("Profile") {
#if os(iOS)
Picker(selection: $selectedProfileID) {
ForEach(profileList, id: \.id) { profile in
Text(profile.name).tag(profile.id)
}
} label: {}
.pickerStyle(.inline)
#elseif os(macOS)
ForEach(profileList, id: \.id) { profile in
Picker(profile.name, selection: $selectedProfileID) {
Text("").tag(profile.id)
}
}
.pickerStyle(.radioGroup)
#endif
}
.onChange(of: selectedProfileID) { _ in
reasserting = true
Task.detached {
await switchProfile(selectedProfileID!)
}
}
.disabled(!profile.status.isSwitchable || reasserting)
}
}
}
#if os(iOS)
.onChange(of: scenePhase, perform: { newValue in
if newValue == .active {
Task.detached {
await doReload()
}
}
})
.onChange(of: selection.wrappedValue, perform: { newValue in
if newValue == .dashboard {
Task.detached {
await doReload()
}
}
})
#elseif os(macOS)
.onAppear {
if observer == nil {
observer = NotificationCenter.default.addObserver(forName: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { _ in
Task.detached {
await doReload()
}
})
}
}
.onDisappear {
if let observer {
NotificationCenter.default.removeObserver(observer)
}
}
#endif
}
private func doReload() {
defer {
isLoading = false
}
do {
profileList = try ProfileManager.list()
} catch {
errorMessage = error.localizedDescription
errorPresented = true
return
}
if profileList.isEmpty {
return
}
selectedProfileID = SharedPreferences.selectedProfileID
if profileList.filter({ profile in
profile.id == selectedProfileID
})
.isEmpty {
selectedProfileID = profileList[0].id!
SharedPreferences.selectedProfileID = selectedProfileID
}
}
private func switchProfile(_ newProfileID: Int64) {
SharedPreferences.selectedProfileID = newProfileID
NotificationCenter.default.post(name: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil)
if profile.status.isConnected {
do {
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.serviceReload()
} catch {
errorMessage = error.localizedDescription
errorPresented = true
}
}
reasserting = false
}
}
@@ -0,0 +1,17 @@
import SwiftUI
public struct DashboardView: View {
@Environment(\.extensionProfile) private var extensionProfile
public init() {}
public var body: some View {
FormView {
if let profile = extensionProfile.wrappedValue {
ActiveDashboardView().environmentObject(profile)
} else {
InstallProfileButton()
}
}.navigationTitle("Dashboard")
}
}
@@ -0,0 +1,116 @@
import Libbox
import Library
import SwiftUI
public struct ExtensionStatusView: View {
@State private var commandClient: LibboxCommandClient?
@State private var message: LibboxStatusMessage?
@State private var connectTask: Task<Void, Error>?
@State private var errorPresented = false
@State private var errorMessage = ""
private let infoFont = Font.system(.caption, design: .monospaced)
public init() {}
public var body: some View {
viewBuilder {
if let message {
FormTextItem("Memory", LibboxFormatBytes(message.memory))
FormTextItem("Goroutines", "\(message.goroutines)")
FormTextItem("Connections", "\(message.connections)").contextMenu {
Button("Close", role: .destructive) {
Task.detached {
closeConnections()
}
}
}
} else {
FormTextItem("Memory", "Loading...")
FormTextItem("Goroutines", "Loading...")
FormTextItem("Connections", "Loading...")
}
}
.onAppear(perform: doReload)
.onDisappear {
connectTask?.cancel()
if let commandClient {
try? commandClient.disconnect()
}
commandClient = nil
}
.alert(isPresented: $errorPresented) {
Alert(
title: Text("Error"),
message: Text(errorMessage),
dismissButton: .default(Text("Ok"))
)
}
}
private func doReload() {
connectTask?.cancel()
connectTask = Task.detached {
await connect()
}
}
private func connect() async {
let clientOptions = LibboxCommandClientOptions()
clientOptions.command = LibboxCommandStatus
clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC)
let client = LibboxNewCommandClient(FilePath.sharedDirectory.relativePath, statusHandler(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()
let isConnected: Bool
do {
try client.connect()
isConnected = true
} catch {
isConnected = false
}
try Task.checkCancellation()
if isConnected {
commandClient = client
return
}
}
} catch {
NSLog("failed to connect status: \(error.localizedDescription)")
try? client.disconnect()
}
}
private func closeConnections() {
do {
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.closeConnections()
} catch {
errorMessage = error.localizedDescription
errorPresented = true
}
}
private class statusHandler: NSObject, LibboxCommandClientHandlerProtocol {
private let statusView: ExtensionStatusView
init(_ statusView: ExtensionStatusView) {
self.statusView = statusView
}
func connected() {}
func disconnected(_: String?) {}
func writeLog(_: String?) {}
func writeStatus(_ message: LibboxStatusMessage?) {
statusView.message = message
}
func writeGroups(_: LibboxOutboundGroupIteratorProtocol?) {}
}
}
@@ -0,0 +1,35 @@
import Library
import SwiftUI
public struct InstallProfileButton: View {
@Environment(\.extensionProfile) private var extensionProfile
@State private var errorPresented = false
@State private var errorMessage = ""
public init() {}
public var body: some View {
Button("Install NetworkExtension") {
Task {
await installProfile()
}
}
.alert(isPresented: $errorPresented) {
Alert(
title: Text("Error"),
message: Text(errorMessage),
dismissButton: .default(Text("Ok"))
)
}
}
private func installProfile() async {
do {
try await ExtensionProfile.install()
} catch {
errorMessage = error.localizedDescription
errorPresented = true
}
}
}
@@ -0,0 +1,90 @@
import Library
import NetworkExtension
import SwiftUI
public struct StartStopButton: View {
@Environment(\.extensionProfile) private var extensionProfile
public init() {}
public var body: some View {
viewBuilder {
if let profile = extensionProfile.wrappedValue {
Button0(profile)
} else {
#if os(iOS)
Toggle(isOn: .constant(false)) {
Text("Enabled")
}
#elseif os(macOS)
Button(action: {}, label: {
Label("Start", systemImage: "play.fill")
})
.disabled(true)
#endif
}
}
}
private struct Button0: View {
@Environment(\.logClient) private var logClient
@ObservedObject private var profile: ExtensionProfile
@State private var errorPresented = false
@State private var errorMessage = ""
init(_ profile: ExtensionProfile) {
self.profile = profile
}
var body: some View {
viewBuilder {
#if os(iOS)
Toggle(isOn: Binding(get: {
profile.status.isConnected
}, set: { newValue, _ in
Task.detached {
await switchProfile(newValue)
}
})) {
Text("Enabled")
}
#elseif os(macOS)
Button(action: {
Task.detached {
await switchProfile(!profile.status.isConnected)
}
}, label: {
if !profile.status.isConnected {
Label("Start", systemImage: "play.fill")
} else {
Label("Stop", systemImage: "stop.fill")
}
})
#endif
}
.disabled(!profile.status.isEnabled)
.alert(isPresented: $errorPresented) {
Alert(
title: Text("Error"),
message: Text(errorMessage),
dismissButton: .default(Text("Ok"))
)
}
}
private func switchProfile(_ isEnabled: Bool) async {
do {
if isEnabled {
try await profile.start()
logClient.wrappedValue?.reconnect()
} else {
profile.stop()
}
} catch {
errorMessage = error.localizedDescription
errorPresented = true
return
}
}
}
}