From 6c93d6846693a5abbe1bb7247a7dc6657059a21d Mon Sep 17 00:00:00 2001 From: TechUnRestricted <83237609+TechUnRestricted@users.noreply.github.com> Date: Wed, 25 May 2022 17:00:46 +0300 Subject: [PATCH] GUI Enhancements and Code Refactoring --- PSDirectSender/ConnectionDetails.swift | 17 +- PSDirectSender/PSDirectSenderApp.swift | 20 +- PSDirectSender/SFOExplorer.swift | 25 +-- .../Screens/ConfigurationView.swift | 6 +- PSDirectSender/Screens/LogsView.swift | 22 +- PSDirectSender/Screens/QueueView.swift | 204 +++++++++++------- PSDirectSender/Screens/ScreenView.swift | 3 +- 7 files changed, 175 insertions(+), 122 deletions(-) diff --git a/PSDirectSender/ConnectionDetails.swift b/PSDirectSender/ConnectionDetails.swift index 54f5e7f..e7940d8 100644 --- a/PSDirectSender/ConnectionDetails.swift +++ b/PSDirectSender/ConnectionDetails.swift @@ -7,6 +7,15 @@ import Foundation +class LogsCollector: ObservableObject { + @Published var logLines: [String] = [] + func addLog(_ text: String) { + DispatchQueue.main.async { + self.logLines.append("[\(getStringDate())] \(text)") + } + } +} + class ConnectionDetails: ObservableObject { @Published var serverIP: String = "" @Published var serverPort: String = "" @@ -15,8 +24,6 @@ class ConnectionDetails: ObservableObject { @Published var connectionStatus: ServerStatus = .stopped @Published var networkingIPs: [String] = [] - @Published var logLines: [String] = [] - func generateServerDetails() { serverPort = String(networking.findFreePort()) @@ -25,10 +32,4 @@ class ConnectionDetails: ObservableObject { serverIP = ip } } - - func addLog(_ text: String) { - DispatchQueue.main.async { - self.logLines.append("[\(getStringDate())] \(text)") - } - } } diff --git a/PSDirectSender/PSDirectSenderApp.swift b/PSDirectSender/PSDirectSenderApp.swift index f9a33ec..fbe1852 100644 --- a/PSDirectSender/PSDirectSenderApp.swift +++ b/PSDirectSender/PSDirectSenderApp.swift @@ -15,12 +15,14 @@ let message: MessageCollection = .init() @main struct PSDirectSenderApp: App { - @StateObject var currentTheme = ConnectionDetails() - + @StateObject var connectionDetails = ConnectionDetails() + @StateObject var logsCollector = LogsCollector() + var body: some Scene { WindowGroup { ContentView() - .environmentObject(currentTheme) + .environmentObject(connectionDetails) + .environmentObject(logsCollector) .frame(minWidth: 650, idealWidth: 750, maxWidth: .infinity, @@ -181,12 +183,14 @@ func swiftStartServer(serverIP: String, serverPort: String) { } @discardableResult -func sendPackagesToConsole(packageFilename: String, consoleIP: String, consolePort: Int, serverIP: String, serverPort: Int) -> Any? { +func sendPackagesToConsole(packageFilename: String, connection: ConnectionDetails) -> Any? { - let dataStructure = PackageSenderData(type: "direct", packages: ["http://\(serverIP):\(serverPort)/\(packageFilename)"]) + let dataStructure = PackageSenderData(type: "direct", + packages: ["http://\(connection.serverIP):\(connection.serverPort)/\(packageFilename)"] + ) let jsonData = try? JSONEncoder().encode(dataStructure) - let builtURL = URL(string: "http://\(consoleIP):\(consolePort)/api/install") + let builtURL = URL(string: "http://\(connection.consoleIP):\(connection.consolePort)/api/install") var request = URLRequest( url: (builtURL)!, @@ -214,8 +218,8 @@ func sendPackagesToConsole(packageFilename: String, consoleIP: String, consolePo return nil } -func checkIfServerIsWorking(serverIP: String, serverPort: String) -> ServerStatus { - guard let url = URL(string: "http://\(serverIP):\(serverPort)") else { +func checkIfServerIsWorking(connection: ConnectionDetails) -> ServerStatus { + guard let url = URL(string: "http://\(connection.serverIP):\(connection.serverPort)") else { return .fail } var status: ServerStatus = .stopped diff --git a/PSDirectSender/SFOExplorer.swift b/PSDirectSender/SFOExplorer.swift index 9be42b4..fc6124c 100644 --- a/PSDirectSender/SFOExplorer.swift +++ b/PSDirectSender/SFOExplorer.swift @@ -104,13 +104,23 @@ class SFOExplorer { return nil } + func getParamSFOData(url: URL) -> [String: String]? { + do { + let fileHandler: FileHandle = try .init(forReadingFrom: url) + return getParamSFOData(fileHandler: fileHandler) + } catch { } + return nil + } + func getParamSFOData(fileHandler: FileHandle) -> [String: String] { /// MAGIC - /*let magic: Int32*/ _ = loadFileDataBlocks( + /* + let magic: Int32 = loadFileDataBlocks( from: 0, bytesCount: 4, fileHandler: fileHandler ).integerRepresentation + */ /// PARAM.SFO guard let offset32: UInt32 = getParamSFOOffset(fileHandler: fileHandler) else { @@ -171,16 +181,3 @@ class SFOExplorer { return paramSFOData } } -/*printTimeElapsedWhenRunningCode(title: "Main", operation: { - let pkgPath: String = "/Users/macintosh/0000000000000000000.pkg" - - guard let fileHandler: FileHandle = .init(forReadingAtPath: pkgPath) else { - return - } - - let data = getParamSFOData(fileHandler: fileHandler) - for (key, value) in data { - print("\(key)=\(value)") - } - try? fileHandler.close() - })*/ diff --git a/PSDirectSender/Screens/ConfigurationView.swift b/PSDirectSender/Screens/ConfigurationView.swift index 8d995b9..9621642 100644 --- a/PSDirectSender/Screens/ConfigurationView.swift +++ b/PSDirectSender/Screens/ConfigurationView.swift @@ -17,6 +17,8 @@ fileprivate class HelpShow: ObservableObject { struct ConfigurationView: View { @EnvironmentObject var connection: ConnectionDetails + @EnvironmentObject var logsCollector: LogsCollector + @StateObject var inputConnectionData: ConnectionDetails = ConnectionDetails() @StateObject fileprivate var helpShow: HelpShow = HelpShow() @@ -139,7 +141,7 @@ struct ConfigurationView: View { connection.consoleIP = inputConnectionData.consoleIP connection.consolePort = inputConnectionData.consolePort - connection.addLog(""" + logsCollector.addLog(""" Staring web server: [SERVER] IP: \(connection.serverIP) Port: \(connection.serverPort) [CONSOLE] IP: \(connection.consoleIP) Port: \(connection.consolePort) @@ -173,7 +175,7 @@ Staring web server: return } - let status = checkIfServerIsWorking(serverIP: connection.serverIP, serverPort: connection.serverPort) + let status = checkIfServerIsWorking(connection: connection) DispatchQueue.main.async { connection.connectionStatus = status connectionStatusLoaded = true diff --git a/PSDirectSender/Screens/LogsView.swift b/PSDirectSender/Screens/LogsView.swift index 259875a..080a28e 100644 --- a/PSDirectSender/Screens/LogsView.swift +++ b/PSDirectSender/Screens/LogsView.swift @@ -9,22 +9,23 @@ import SwiftUI struct LogsView: View { @EnvironmentObject var connection: ConnectionDetails - + @EnvironmentObject var logsCollector: LogsCollector + var body: some View { VStack { HStack(spacing: 25) { ColorButton(text: "Copy logs", color: .purple, image: Image(systemName: "doc.on.doc"), action: { let pasteboard = NSPasteboard.general pasteboard.clearContents() - pasteboard.setString(connection.logLines.joined(separator: "\n"), forType: .string) + pasteboard.setString(logsCollector.logLines.joined(separator: "\n"), forType: .string) }) ColorButton(text: "Clear logs", color: .red, image: Image(systemName: "trash"), action: { - connection.logLines.removeAll() + logsCollector.logLines.removeAll() }) }.padding() List { - ForEach(connection.logLines, id: \.self) { logLine in + ForEach(logsCollector.logLines, id: \.self) { logLine in Text(LocalizedStringKey(logLine)) .frame(maxWidth: .infinity, alignment: .leading) .lineLimit(3) @@ -42,13 +43,12 @@ struct LogsView_Previews: PreviewProvider { static var previews: some View { let view = LogsView() - .environmentObject({ () -> ConnectionDetails in - let vm = ConnectionDetails() - vm.addLog("Can't get server configuration.") - vm.addLog("Can't get console configuration.") - vm.addLog("Creating package alias \(#""/Volumes/Macintosh HD/game.pkg""#) -> \"\(tempDirectory.path)\(UUID().uuidString).pkg\"") - - return vm + .environmentObject({ () -> LogsCollector in + let logsCollector = LogsCollector() + logsCollector.addLog("Can't get server configuration.") + logsCollector.addLog("Can't get console configuration.") + logsCollector.addLog("Creating package alias \(#""/Volumes/Macintosh HD/game.pkg""#) -> \"\(tempDirectory.path)\(UUID().uuidString).pkg\"") + return logsCollector }()) view view diff --git a/PSDirectSender/Screens/QueueView.swift b/PSDirectSender/Screens/QueueView.swift index d331249..44d55da 100644 --- a/PSDirectSender/Screens/QueueView.swift +++ b/PSDirectSender/Screens/QueueView.swift @@ -30,6 +30,7 @@ struct Package { let id = UUID() let url: URL var task_id: Int? + var title_id: String? var state: PackageState = .sendNotInitiated } @@ -59,6 +60,8 @@ private struct AlertIdentifier: Identifiable { struct QueueView: View { @EnvironmentObject var connection: ConnectionDetails + @EnvironmentObject var logsCollector: LogsCollector + @State fileprivate var alert: AlertIdentifier? @State var packageURLs: [Package] = [] @State private var selection: Set = [] @@ -72,108 +75,68 @@ struct QueueView: View { ZStack { VStack { HStack(spacing: 15) { - ColorButton(text: "Add", color: .orange, image: Image(systemName: "plus.rectangle.on.rectangle"), action: { - let packages = selectPackages() - for package in packages { - if let package = package { - packageURLs.append(Package(url: package)) - } - } - }) + AddButton() + SendButton() + DeleteButton() + .onDeleteCommand(perform: selection.isEmpty ? nil: deleteSelection) - ColorButton(text: "Send", color: .green, image: Image(systemName: "arrow.up.forward.app"), action: { - if packageURLs.isEmpty { return } - - if connection.serverIP.isEmpty || connection.serverPort.isEmpty { - connection.generateServerDetails() - } - - if connection.serverIP.isEmpty || connection.serverPort.isEmpty { - connection.addLog("Can't get server configuration.") - alert = AlertIdentifier(id: .cantGetServerConfiguration) - return - } - - if connection.consoleIP.isEmpty || connection.consolePort.isEmpty { - connection.addLog("Can't get console configuration.") - alert = AlertIdentifier(id: .cantGetConsoleConfiguration) - return - } - - loadingScreenIsShown = true - DispatchQueue.global(qos: .background).async { - for index in packageURLs.indices { - if packageURLs[index].state == .sendSuccess { - continue - } - let alias = createTempDirPackageAlias(package: packageURLs[index])! - - connection.addLog("Creating package alias (\"\(packageURLs[index].url.path)\" -> \"\(tempDirectory.path)/\(alias)\").") - connection.addLog("Sending package \"\(alias)\" to the console (IP: \(connection.consoleIP), Port: \(connection.consolePort))") - - let response = sendPackagesToConsole(packageFilename: alias, consoleIP: connection.consoleIP, consolePort: Int(connection.consolePort)!, serverIP: connection.serverIP, serverPort: Int(connection.serverPort)!) - - if response == nil || response as? String == "" { - connection.addLog("Can't get response from console ([Console] IP: \(connection.consoleIP), Port: \(connection.consolePort))") - DispatchQueue.main.async { - if loadingScreenIsShown { - alert = AlertIdentifier(id: .cantGetResponseFromConsole) - } - } - break - } else if let response = response as? SendSuccess { - connection.addLog("Successfully sent \(packageURLs[index].url) [Package Link: \"\(packageURLs[index].id).pkg\", id: \(response.taskID), title: \"\(response.title)\"]") - DispatchQueue.main.async { - packageURLs[index].state = .sendSuccess - packageURLs[index].task_id = response.taskID - } - } else if let response = response as? SendFailure { - connection.addLog("An error occurred while sending \(packageURLs[index].url) [\(packageURLs[index].id).pkg] {ERROR: \(response.error)}") - DispatchQueue.main.async { - packageURLs[index].state = .sendFailure - } - break - } - } - DispatchQueue.main.async { - loadingScreenIsShown = false - } - - } - - }) - - ColorButton(text: "Delete", color: .red, image: Image(systemName: "trash"), action: { - deleteSelection() - }).onDeleteCommand(perform: selection.isEmpty ? nil: deleteSelection) }.padding() List(selection: $selection) { ForEach(packageURLs, id: \.id) { package in HStack { Image(systemName: "shippingbox") - Text("\(package.url.lastPathComponent)") + Text("\(package.title_id ?? package.url.lastPathComponent)") } .font(.title3) .padding(10) .frame(maxWidth: .infinity, alignment: .leading) .background(package.state.color.opacity(0.5).cornerRadius(5)) .swiftyListDivider() + .contextMenu(menuItems: { + Button("Remove item from queue") { + deleteSelection() + } + }) } } - .onDrop(of: [.fileURL], isTargeted: $isInDropArea, perform: { providers in + .onDrop(of: [.fileURL], isTargeted: $isInDropArea) { providers in for provider in providers { _ = provider.loadObject(ofClass: URL.self) { object, _ in if let url = object, url.pathExtension == "pkg"{ - packageURLs.append(Package(url: url)) + let packageDetails = SFOExplorer().getParamSFOData(url: url) + var title: String? + if let packageDetails = packageDetails { + title = packageDetails["TITLE"] + } + packageURLs.append(Package(url: url, title_id: title)) } } } return true - }) + } .overlay( + ZStack { + ZStack { + VisualEffectView(material: .fullScreenUI, blendingMode: .withinWindow) + VStack { + Image(systemName: "shippingbox") + .resizable() + .frame(width: 100, height: 100) + Text(#"Drop ".pkg" files"#) + .font(.title) + } + .opacity(0.5) + } + .cornerRadius(16) + .hidden(!isInDropArea) + RoundedRectangle(cornerRadius: 16) - .stroke(Color.gray.opacity(0.4), lineWidth: 1.5) + .stroke( + Color.gray.opacity(0.4), + lineWidth: 1.5 + ) + } ) } @@ -213,6 +176,91 @@ struct QueueView: View { selection.removeAll() } + fileprivate func AddButton() -> ColorButton { + return ColorButton(text: "Add", color: .orange, image: Image(systemName: "plus.rectangle.on.rectangle"), action: { + let packages = selectPackages() + for package in packages { + if let package = package { + let packageDetails = SFOExplorer().getParamSFOData(url: package) + var title: String? + if let packageDetails = packageDetails { + title = packageDetails["TITLE"] + } + packageURLs.append(Package(url: package, title_id: title)) + } + } + }) + } + + fileprivate func SendButton() -> ColorButton { + return ColorButton(text: "Send", color: .green, image: Image(systemName: "arrow.up.forward.app"), action: { + if packageURLs.isEmpty { return } + + if connection.serverIP.isEmpty || connection.serverPort.isEmpty { + connection.generateServerDetails() + } + + if connection.serverIP.isEmpty || connection.serverPort.isEmpty { + logsCollector.addLog("Can't get server configuration.") + alert = AlertIdentifier(id: .cantGetServerConfiguration) + return + } + + if connection.consoleIP.isEmpty || connection.consolePort.isEmpty { + logsCollector.addLog("Can't get console configuration.") + alert = AlertIdentifier(id: .cantGetConsoleConfiguration) + return + } + + loadingScreenIsShown = true + DispatchQueue.global(qos: .background).async { + for index in packageURLs.indices { + if packageURLs[index].state == .sendSuccess { + continue + } + let alias = createTempDirPackageAlias(package: packageURLs[index])! + + logsCollector.addLog("Creating package alias (\"\(packageURLs[index].url.path)\" -> \"\(tempDirectory.path)/\(alias)\").") + logsCollector.addLog("Sending package \"\(alias)\" to the console (IP: \(connection.consoleIP), Port: \(connection.consolePort))") + + let response = sendPackagesToConsole(packageFilename: alias, connection: connection) + + if response == nil || response as? String == "" { + logsCollector.addLog("Can't get response from console ([Console] IP: \(connection.consoleIP), Port: \(connection.consolePort))") + DispatchQueue.main.async { + if loadingScreenIsShown { + alert = AlertIdentifier(id: .cantGetResponseFromConsole) + } + } + break + } else if let response = response as? SendSuccess { + logsCollector.addLog("Successfully sent \(packageURLs[index].url) [Package Link: \"\(packageURLs[index].id).pkg\", id: \(response.taskID), title: \"\(response.title)\"]") + DispatchQueue.main.async { + packageURLs[index].state = .sendSuccess + packageURLs[index].task_id = response.taskID + } + } else if let response = response as? SendFailure { + logsCollector.addLog("An error occurred while sending \(packageURLs[index].url) [\(packageURLs[index].id).pkg] {ERROR: \(response.error)}") + DispatchQueue.main.async { + packageURLs[index].state = .sendFailure + } + break + } + } + DispatchQueue.main.async { + loadingScreenIsShown = false + } + + } + + }) + } + + fileprivate func DeleteButton() -> ColorButton { + return ColorButton(text: "Delete", color: .red, image: Image(systemName: "trash"), action: { + deleteSelection() + }) + } } struct QueueView_Previews: PreviewProvider { diff --git a/PSDirectSender/Screens/ScreenView.swift b/PSDirectSender/Screens/ScreenView.swift index 1fe7296..7229327 100644 --- a/PSDirectSender/Screens/ScreenView.swift +++ b/PSDirectSender/Screens/ScreenView.swift @@ -10,7 +10,8 @@ import SwiftUI struct ScreenView: View { let screen: Screen @EnvironmentObject var connection: ConnectionDetails - + @EnvironmentObject var logsCollector: ConnectionDetails + var body: some View { switch screen { case .queue: