From a4b1823859be7308b6c12cc9c8e54896b1447e42 Mon Sep 17 00:00:00 2001 From: Zeke Snider Date: Sat, 22 Aug 2020 11:27:40 -0700 Subject: [PATCH] Improve confiuguration loading --- Jared.xcodeproj/project.pbxproj | 46 +++++++++++++++++------ Jared/Configuration.swift | 12 ++++-- Jared/ConfigurationHelper.swift | 58 +++++++++++++++++++++++++++++ Jared/JaredWebServer.swift | 39 ++----------------- Jared/PluginManager.swift | 45 +++++----------------- Jared/WebHookManager.swift | 2 + JaredTests/JaredWebServerTest.swift | 5 +-- JaredUI/AppDelegate.swift | 13 +++---- JaredUI/ViewController.swift | 2 +- 9 files changed, 125 insertions(+), 97 deletions(-) create mode 100644 Jared/ConfigurationHelper.swift diff --git a/Jared.xcodeproj/project.pbxproj b/Jared.xcodeproj/project.pbxproj index d8a673e..c5d8ee0 100644 --- a/Jared.xcodeproj/project.pbxproj +++ b/Jared.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 7F84B62E1CC511B80059A82B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B3BBA7CD1CB3259A00E1749C /* Localizable.strings */; }; 7FE220ED1CB3C8340081CE23 /* podfile in Resources */ = {isa = PBXBuildFile; fileRef = 7FE220EC1CB3C8340081CE23 /* podfile */; }; 7FF077EB1CBCFFCB008E33CB /* SendImage.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 7FF077EA1CBCFFCB008E33CB /* SendImage.scpt */; }; + B30578D324F1927D00C43037 /* ConfigurationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B30578D224F1927D00C43037 /* ConfigurationHelper.swift */; }; + B30578D424F1927D00C43037 /* ConfigurationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B30578D224F1927D00C43037 /* ConfigurationHelper.swift */; }; B30F765624C5742800ADF57B /* SendTextUI.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B30F765424C5740800ADF57B /* SendTextUI.scpt */; }; B319235724DD311B001904BA /* InternalModuleTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B319235624DD311B001904BA /* InternalModuleTest.swift */; }; B319235824DD319D001904BA /* InternalModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BF17C3244EBF4900CC44C5 /* InternalModule.swift */; }; @@ -54,6 +56,7 @@ B375A65921EB14470073F09F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B375A65821EB14470073F09F /* AboutViewController.swift */; }; B37E82821FA85B210069E15D /* SendTextSingleBuddy.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B37E82811FA85B210069E15D /* SendTextSingleBuddy.scpt */; }; B38BB6AD24EB99BE000700F7 /* Webhook.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3B7E52824EA579600A3FBD1 /* Webhook.swift */; }; + B38BB6AF24EBB4D8000700F7 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B36C8D7E24EA655900C00A99 /* Configuration.swift */; }; B39A65E01CC1FB1B003E26B0 /* PluginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39A65DF1CC1FB1B003E26B0 /* PluginManager.swift */; }; B39F2B391CB3BCA700C0D35C /* SendText.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B39F2B381CB3BCA700C0D35C /* SendText.scpt */; }; B3A4C61423B867E300B7F009 /* MessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A4C61323B867E300B7F009 /* MessageRequest.swift */; }; @@ -156,6 +159,7 @@ 7FE220EC1CB3C8340081CE23 /* podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 7FF077EA1CBCFFCB008E33CB /* SendImage.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = SendImage.scpt; sourceTree = ""; }; 8490C38E773007C8CC882DCB /* Pods-Jared.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Jared.release.xcconfig"; path = "Pods/Target Support Files/Pods-Jared/Pods-Jared.release.xcconfig"; sourceTree = ""; }; + B30578D224F1927D00C43037 /* ConfigurationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationHelper.swift; sourceTree = ""; }; B30F765424C5740800ADF57B /* SendTextUI.scpt */ = {isa = PBXFileReference; lastKnownFileType = text; path = SendTextUI.scpt; sourceTree = ""; }; B30F765724C574FD00ADF57B /* JaredRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = JaredRelease.entitlements; sourceTree = ""; }; B319235624DD311B001904BA /* InternalModuleTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalModuleTest.swift; sourceTree = ""; }; @@ -358,6 +362,30 @@ path = JaredTests; sourceTree = ""; }; + B38BB6AE24EBB4CE000700F7 /* Structures */ = { + isa = PBXGroup; + children = ( + B36C8D7E24EA655900C00A99 /* Configuration.swift */, + B3B7E52324E9EE2000A3FBD1 /* AutomationPermissionState.swift */, + B3B7E52824EA579600A3FBD1 /* Webhook.swift */, + B3A4C61323B867E300B7F009 /* MessageRequest.swift */, + ); + name = Structures; + sourceTree = ""; + }; + B38BB6B024EBB530000700F7 /* Helpers */ = { + isa = PBXGroup; + children = ( + B353946124E64DDE00F9424A /* PersistentContainer.swift */, + B30578D224F1927D00C43037 /* ConfigurationHelper.swift */, + B3FB28F124E9BAF7003BA6CB /* PermissionsHelper.swift */, + B3327DC32206607B009DD882 /* ContactHelper.swift */, + 7F0E931C1D02FC250096BABE /* Global.swift */, + B3FB28F324E9BCBD003BA6CB /* JaredConstants.swift */, + ); + name = Helpers; + sourceTree = ""; + }; B39EC1691CB01F55002C3161 = { isa = PBXGroup; children = ( @@ -395,24 +423,17 @@ B30F765424C5740800ADF57B /* SendTextUI.scpt */, B37E82811FA85B210069E15D /* SendTextSingleBuddy.scpt */, B361EEBA1F1DE24C0041113C /* config.json */, - B353946124E64DDE00F9424A /* PersistentContainer.swift */, + B3EF1DA92206E09B00953DE7 /* Jared.swift */, B3BF17C1244EBE9A00CC44C5 /* Router.swift */, B39A65DF1CC1FB1B003E26B0 /* PluginManager.swift */, B3327DC522066427009DD882 /* WebHookManager.swift */, - B3327DC32206607B009DD882 /* ContactHelper.swift */, - B3FB28F124E9BAF7003BA6CB /* PermissionsHelper.swift */, - B3FB28F324E9BCBD003BA6CB /* JaredConstants.swift */, B3C59283224DD17900116ECB /* JaredWebServer.swift */, - B3B7E52324E9EE2000A3FBD1 /* AutomationPermissionState.swift */, - B3A4C61323B867E300B7F009 /* MessageRequest.swift */, B35CE7142196AD4E002F52A7 /* DatabaseHandler.swift */, - 7F0E931C1D02FC250096BABE /* Global.swift */, - B3EF1DA92206E09B00953DE7 /* Jared.swift */, - B36C8D7E24EA655900C00A99 /* Configuration.swift */, - B3B7E52824EA579600A3FBD1 /* Webhook.swift */, - B3BBA7CD1CB3259A00E1749C /* Localizable.strings */, + B38BB6B024EBB530000700F7 /* Helpers */, + B38BB6AE24EBB4CE000700F7 /* Structures */, B3BF17C9244ED26800CC44C5 /* Delegates */, B39EC17C1CB01FDD002C3161 /* Modules */, + B3BBA7CD1CB3259A00E1749C /* Localizable.strings */, 7FE220EC1CB3C8340081CE23 /* podfile */, ); path = Jared; @@ -785,6 +806,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B30578D324F1927D00C43037 /* ConfigurationHelper.swift in Sources */, B347A4A724DD0FCF00E93657 /* Jared.swift in Sources */, B3BF17C8244ECC0E00CC44C5 /* PluginManagerDelegate.swift in Sources */, B3A4C61423B867E300B7F009 /* MessageRequest.swift in Sources */, @@ -824,6 +846,7 @@ B3B844D224DA6BBB00DC162A /* JaredWebServer.swift in Sources */, B3B844CC24DA671E00DC162A /* DiskAccessDelegate.swift in Sources */, B319235724DD311B001904BA /* InternalModuleTest.swift in Sources */, + B38BB6AF24EBB4D8000700F7 /* Configuration.swift in Sources */, B3B844C924D92D1500DC162A /* ContactHelper.swift in Sources */, B3B844C824D92C2200DC162A /* DatabaseHandler.swift in Sources */, B3B844C024D8F91900DC162A /* DatabaseHandlerTest.swift in Sources */, @@ -833,6 +856,7 @@ B3BF17CA244FDA3F00CC44C5 /* MessageDelegate.swift in Sources */, B3B844C524D9288000DC162A /* RouterDelegate.swift in Sources */, B3B844B824D7791800DC162A /* ActionTypeTest.swift in Sources */, + B30578D424F1927D00C43037 /* ConfigurationHelper.swift in Sources */, B3B844C724D9298000DC162A /* MockRouter.swift in Sources */, B347A4AD24DD170C00E93657 /* ActionTest.swift in Sources */, B36710E224DFB5EC00DA1C5D /* JaredMock.swift in Sources */, diff --git a/Jared/Configuration.swift b/Jared/Configuration.swift index 8983b80..d17722d 100644 --- a/Jared/Configuration.swift +++ b/Jared/Configuration.swift @@ -8,16 +8,22 @@ import Foundation -class ConfigurationFile: Decodable { +struct ConfigurationFile: Decodable { let routes: [String: RouteConfiguration] let webhooks: [Webhook] let webServer: WebserverConfiguration + + init() { + routes = [:] + webhooks = [] + webServer = WebserverConfiguration(port: 3000) + } } -class WebserverConfiguration: Decodable { +struct WebserverConfiguration: Decodable { let port: Int } -class RouteConfiguration: Decodable { +struct RouteConfiguration: Decodable { let disabled: Bool } diff --git a/Jared/ConfigurationHelper.swift b/Jared/ConfigurationHelper.swift new file mode 100644 index 0000000..f2ec810 --- /dev/null +++ b/Jared/ConfigurationHelper.swift @@ -0,0 +1,58 @@ +// +// ConfigurationHelper.swift +// Jared +// +// Created by Zeke Snider on 8/22/20. +// Copyright © 2020 Zeke Snider. All rights reserved. +// + +import Foundation + +struct ConfigurationHelper { + static let fileManager = FileManager.default + + static func getConfiguration() -> ConfigurationFile { + let configPath = ConfigurationHelper.getSupportDirectory() + .appendingPathComponent("config.json") + ConfigurationHelper.createConfigFileIfNotExists(at: configPath, using: fileManager) + + //Read the JSON config file + var config: ConfigurationFile + let jsonData = try? NSData(contentsOfFile: configPath.path, options: .mappedIfSafe) + if let parsedConfig = try? JSONDecoder().decode(ConfigurationFile.self, from: jsonData! as Data) { + config = parsedConfig + } else { + NSLog("Unable to parse configuration file, using default") + config = ConfigurationFile() + } + + return config + } + + static func getSupportDirectory() -> URL { + let filemanager = FileManager.default + let appsupport = filemanager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] + let supportDir = appsupport.appendingPathComponent("Jared") + + try! filemanager.createDirectory(at: supportDir, withIntermediateDirectories: true, attributes: nil) + + return supportDir + } + + static func getPluginDirectory() -> URL { + let supportDir = getSupportDirectory() + .appendingPathComponent("Plugins") + + try! fileManager.createDirectory(at: supportDir, withIntermediateDirectories: true, attributes: nil) + + return supportDir + } + + //Copy an empty config file if the conig file does not exist + private static func createConfigFileIfNotExists(at path: URL, using fileManager: FileManager) { + //Copy an empty config file if the conig file does not exist + if !fileManager.fileExists(atPath: path.path) { + try! fileManager.copyItem(at: (Bundle.main.resourceURL?.appendingPathComponent("config.json"))!, to: path) + } + } +} diff --git a/Jared/JaredWebServer.swift b/Jared/JaredWebServer.swift index 888501a..cd7e4a4 100644 --- a/Jared/JaredWebServer.swift +++ b/Jared/JaredWebServer.swift @@ -8,17 +8,15 @@ class JaredWebServer: NSObject { var server: Server! var port: Int! var sender: MessageSender - var configurationURL: URL - init(sender: MessageSender, configurationURL: URL) { - self.configurationURL = configurationURL + init(sender: MessageSender, configuration: WebserverConfiguration) { self.sender = sender super.init() defaults = UserDefaults.standard server = Server() server.route(.POST, "message", handleMessageRequest) - port = assignPort() + port = configuration.port defaults.addObserver(self, forKeyPath: JaredConstants.restApiIsDisabled, options: .new, context: nil) updateServerState() @@ -29,35 +27,6 @@ class JaredWebServer: NSObject { UserDefaults.standard.removeObserver(self, forKeyPath: JaredConstants.jaredIsDisabled) } - // Attempt to pull the port number from the config - func assignPort() -> Int { - let filemanager = FileManager.default - do { - // If config file does not exist, use default port - guard filemanager.fileExists(atPath: configurationURL.path) else { - return JaredWebServer.DEFAULT_PORT - } - - //Read the JSON config file - let jsonData = try! NSData(contentsOfFile: configurationURL.path, options: .mappedIfSafe) - - // If the JSON format is not as expected at all, use the default port - guard let jsonResult = ((try? JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String:AnyObject]) as [String : AnyObject]??) else { - return JaredWebServer.DEFAULT_PORT - } - - guard let serverConfig = jsonResult?["webserver"] as? [String : AnyObject] else { - return JaredWebServer.DEFAULT_PORT - } - - guard let configPort = serverConfig["port"] as? NSNumber else { - return JaredWebServer.DEFAULT_PORT - } - - return Int(truncating: configPort) - } - } - override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if (keyPath == JaredConstants.restApiIsDisabled) { updateServerState() @@ -73,11 +42,11 @@ class JaredWebServer: NSObject { } - func start() { + public func start() { try? server.start(port: port) } - func stop() { + public func stop() { server.stop() } diff --git a/Jared/PluginManager.swift b/Jared/PluginManager.swift index e9e7a3f..610e3ff 100644 --- a/Jared/PluginManager.swift +++ b/Jared/PluginManager.swift @@ -13,7 +13,7 @@ class PluginManager: PluginManagerDelegate { var FrameworkVersion: String = "J3.0.0" private var modules: [RoutingModule] = [] private var bundles: [Bundle] = [] - var supportDir: URL? + var pluginDir: URL var disabled = false var config: ConfigurationFile var webhooks: [String]? @@ -21,48 +21,25 @@ class PluginManager: PluginManagerDelegate { var sender: MessageSender public var router: Router! - init (sender: MessageSender) { + init (sender: MessageSender, configuration: ConfigurationFile, pluginDir: URL) { self.sender = sender - let filemanager = FileManager.default - let appsupport = filemanager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] - let supportDir = appsupport.appendingPathComponent("Jared") - let pluginDir = supportDir.appendingPathComponent("Plugins") - - try! filemanager.createDirectory(at: supportDir, withIntermediateDirectories: true, attributes: nil) - try! filemanager.createDirectory(at: pluginDir, withIntermediateDirectories: true, attributes: nil) + self.pluginDir = pluginDir + self.config = configuration - let configPath = supportDir.appendingPathComponent("config.json") - do { - PluginManager.createConfigFileIfNotExists(at: configPath, using: filemanager) - - //Read the JSON config file - let jsonData = try! NSData(contentsOfFile: supportDir.appendingPathComponent("config.json").path, options: .mappedIfSafe) - self.config = try! JSONDecoder().decode(ConfigurationFile.self, from: jsonData as Data) - } - - webHookManager = WebHookManager(webhooks: config.webhooks, sender: sender) + webHookManager = WebHookManager(webhooks: configuration.webhooks, sender: sender) router = Router(pluginManager: self, messageDelegates: [webHookManager]) - loadPlugins(pluginDir) + loadPlugins() addInternalModules() } - //Copy an empty config file if the conig file does not exist - private static func createConfigFileIfNotExists(at path: URL, using fileManager: FileManager) { - //Copy an empty config file if the conig file does not exist - if !fileManager.fileExists(atPath: path.path) { - try! fileManager.copyItem(at: (Bundle.main.resourceURL?.appendingPathComponent("config.json"))!, to: path) - } - } - - private func addInternalModules() { modules.append(CoreModule(sender: sender)) modules.append(InternalModule(sender: sender, pluginManager: self)) modules.append(webHookManager) } - func loadPlugins(_ pluginDir: URL) { + func loadPlugins() { //Loop through all files in our plugin directory let filemanager = FileManager.default let files = filemanager.enumerator(at: pluginDir, includingPropertiesForKeys: [], @@ -108,11 +85,7 @@ class PluginManager: PluginManagerDelegate { modules.append(module) } - func reload() { - let appsupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] - let supportDir = appsupport.appendingPathComponent("Jared") - let pluginDir = supportDir.appendingPathComponent("Plugins") - + func reload() { modules.removeAll() for bundle in bundles { @@ -121,7 +94,7 @@ class PluginManager: PluginManagerDelegate { bundles.removeAll() - loadPlugins(pluginDir) + loadPlugins() addInternalModules() } diff --git a/Jared/WebHookManager.swift b/Jared/WebHookManager.swift index 3fa8c3f..2c68954 100644 --- a/Jared/WebHookManager.swift +++ b/Jared/WebHookManager.swift @@ -74,6 +74,8 @@ class WebHookManager: MessageDelegate, RoutingModule { } public func updateHooks(to hooks: [Webhook]?) { + // Change all routes to have a callback that calls the webhook manager's + // notify route method self.webhooks = (hooks ?? []).map({ (hook) -> Webhook in var newHook = hook newHook.routes = (newHook.routes ?? []).map({ (route) -> Route in diff --git a/JaredTests/JaredWebServerTest.swift b/JaredTests/JaredWebServerTest.swift index 372a184..5988809 100644 --- a/JaredTests/JaredWebServerTest.swift +++ b/JaredTests/JaredWebServerTest.swift @@ -19,9 +19,8 @@ class JaredWebServerTest: XCTestCase { override func setUp() { jaredMock = JaredMock() - let bundle = Bundle(for: type(of: self)) - testDatabaseLocation = bundle.url(forResource: "config", withExtension: "json") - webServer = JaredWebServer(sender: jaredMock, configurationURL: testDatabaseLocation) + let configuration = WebserverConfiguration(port: 3005) + webServer = JaredWebServer(sender: jaredMock, configuration: configuration) } override func tearDown() { diff --git a/JaredUI/AppDelegate.swift b/JaredUI/AppDelegate.swift index 377e164..f0be972 100644 --- a/JaredUI/AppDelegate.swift +++ b/JaredUI/AppDelegate.swift @@ -12,7 +12,7 @@ import Contacts @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { var sender: Jared - var router: PluginManager + var pluginManager: PluginManager var server: JaredWebServer var databaseHelper: DatabaseHandler! override init() { @@ -22,15 +22,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { JaredConstants.contactsAccess: CNAuthorizationStatus.notDetermined.rawValue, JaredConstants.fullDiskAccess: true ]) - let _ = PermissionsHelper.getContactsStatus() - let configurationURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] - .appendingPathComponent("Jared") - .appendingPathComponent("config.json") + let config = ConfigurationHelper.getConfiguration() sender = Jared() - router = PluginManager(sender: sender) - server = JaredWebServer(sender: sender, configurationURL: configurationURL) + pluginManager = PluginManager(sender: sender, configuration: config, pluginDir: ConfigurationHelper.getPluginDirectory()) + server = JaredWebServer(sender: sender, configuration: config.webServer) super.init() } @@ -42,7 +39,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { let messageDatabaseURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0] .appendingPathComponent("Messages").appendingPathComponent("chat.db") let viewController = NSApplication.shared.keyWindow?.contentViewController as? ViewController - databaseHelper = DatabaseHandler(router: router.router, databaseLocation: messageDatabaseURL, diskAccessDelegate: viewController) + databaseHelper = DatabaseHandler(router: pluginManager.router, databaseLocation: messageDatabaseURL, diskAccessDelegate: viewController) } func applicationWillTerminate(_ aNotification: Notification) { diff --git a/JaredUI/ViewController.swift b/JaredUI/ViewController.swift index 0d76733..feee347 100644 --- a/JaredUI/ViewController.swift +++ b/JaredUI/ViewController.swift @@ -234,7 +234,7 @@ class ViewController: NSViewController, DiskAccessDelegate { } @IBAction func ReloadButtonPressed(_ sender: Any) { if let appDelegate = NSApplication.shared.delegate as? AppDelegate { - appDelegate.router.reload() + appDelegate.pluginManager.reload() } }