diff --git a/Sources/App/CapServer.swift b/Sources/App/CapServer.swift index 66ee65d..1e025e8 100644 --- a/Sources/App/CapServer.swift +++ b/Sources/App/CapServer.swift @@ -1,7 +1,8 @@ import Foundation import Vapor +import Clairvoyant -final class CapServer { +final class CapServer: ServerOwner { // MARK: Paths @@ -26,6 +27,7 @@ final class CapServer { var classifierVersion: Int = 0 { didSet { writeClassifierVersion() + updateMonitoredClassifierVersionProperty() } } @@ -44,7 +46,10 @@ final class CapServer { private var nextSaveTime: Date? private var caps = [Int: Cap]() { - didSet { scheduleSave() } + didSet { + scheduleSave() + updateMonitoredPropertiesOnCapChange() + } } var nextClassifierVersion: Int { @@ -310,4 +315,81 @@ final class CapServer { classifierVersion = version log("Updated classifier to version \(version)") } + + // MARK: ServerOwner + + let authenticationMethod: PropertyAuthenticationMethod = .accessToken + + func hasReadPermission(for property: UInt32, accessData: Data) -> Bool { + guard let key = String(data: accessData, encoding: .utf8) else { + return false + } + return writers.contains(key) + } + + func hasWritePermission(for property: UInt32, accessData: Data) -> Bool { + guard let key = String(data: accessData, encoding: .utf8) else { + return false + } + return writers.contains(key) + } + + func hasListAccessPermission(_ accessData: Data) -> Bool { + guard let key = String(data: accessData, encoding: .utf8) else { + return false + } + return writers.contains(key) + } + + // MARK: Monitoring + + private let capCountPropertyId = PropertyId(name: "caps", uniqueId: 1) + + private let imageCountPropertyId = PropertyId(name: "images", uniqueId: 2) + + private let classifierVersionPropertyId = PropertyId(name: "classifier", uniqueId: 3) + + func registerProperties(with monitor: PropertyManager) { + let capCountProperty = PropertyRegistration( + uniqueId: capCountPropertyId.uniqueId, + name: capCountPropertyId.name, + updates: .continuous, + isLogged: true, + allowsManualUpdate: false, + read: { [weak self] in + return (self?.capCount ?? 0).timestamped() + }) + monitor.register(capCountProperty, for: self) + + let imageCountProperty = PropertyRegistration( + uniqueId: imageCountPropertyId.uniqueId, + name: imageCountPropertyId.name, + updates: .continuous, + isLogged: true, + allowsManualUpdate: false, + read: { [weak self] in + return (self?.imageCount ?? 0).timestamped() + }) + monitor.register(imageCountProperty, for: self) + + let classifierVersionProperty = PropertyRegistration( + uniqueId: classifierVersionPropertyId.uniqueId, + name: classifierVersionPropertyId.name, + updates: .continuous, + isLogged: true, + allowsManualUpdate: false, + read: { [weak self] in + return (self?.classifierVersion ?? 0).timestamped() + }) + monitor.register(classifierVersionProperty, for: self) + } + + private func updateMonitoredPropertiesOnCapChange() { + try? monitor.logChanged(property: capCountPropertyId, value: capCount.timestamped()) + try? monitor.logChanged(property: imageCountPropertyId, value: imageCount.timestamped()) + } + + private func updateMonitoredClassifierVersionProperty() { + try? monitor.logChanged(property: classifierVersionPropertyId, value: classifierVersion.timestamped()) + } } diff --git a/Sources/App/Log.swift b/Sources/App/Log.swift deleted file mode 100644 index 7d132f2..0000000 --- a/Sources/App/Log.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// Log.swift -// App -// -// Created by Christoph on 05.05.20. -// - -import Foundation - -private let df: DateFormatter = { - let df = DateFormatter() - df.dateStyle = .short - df.timeStyle = .short - df.locale = Locale(identifier: "de") - return df -}() - -func log(_ message: String, file: String = #file, line: Int = #line) { - let date = df.string(from: Date()) - let m = "[\(date)][\(file.components(separatedBy: "/").last ?? file):\(line)] \(message)" - print(m) - Log.write(m + "\n") -} - -enum Log { - - static func set(logFile: String) throws { - let url = URL(fileURLWithPath: logFile) - let date = df.string(from: Date()) - if !FileManager.default.fileExists(atPath: logFile) { - try "[\(date)] New log created.\n".write(to: url, atomically: false, encoding: .utf8) - } - guard let f = FileHandle(forWritingAtPath: logFile) else { - try "[\(date)] Failed to start log.\n".write(to: url, atomically: false, encoding: .utf8) - return - } - f.seekToEndOfFile() - f.write("[\(date)] Logging started.\n".data(using: .utf8)!) - file = f - } - - static func write(_ message: String) { - guard let f = file else { - return - } - f.write(message.data(using: .utf8)!) - } - - private static var file: FileHandle? -} diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index a78039e..89957cf 100755 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -1,46 +1,49 @@ import Vapor import Foundation +import Clairvoyant private(set) var server: CapServer! +private(set) var monitor: PropertyManager! public func configure(_ app: Application) throws { - try Log.set(logFile: config.logPath) - let resourceDirectory = URL(fileURLWithPath: app.directory.resourcesDirectory) let publicDirectory = app.directory.publicDirectory + let config = Config(loadFrom: resourceDirectory) + server = CapServer(in: URL(fileURLWithPath: publicDirectory), writers: config.writers) + + monitor = .init(logFolder: config.logURL, serverOwner: server) + monitor.update(status: .initializing) + + server.registerProperties(with: monitor) + monitor.registerRoutes(app) + + app.http.server.configuration.port = config.port + app.routes.defaultMaxBodySize = .init(stringLiteral: config.maxBodySize) + if config.serveFiles { let middleware = FileMiddleware(publicDirectory: publicDirectory) app.middleware.use(middleware) } - + do { + try server.loadData() + } catch { + monitor.update(status: .initializationFailure) + return + } + // Register routes to the router - try routes(app) + routes(app) + + monitor.update(status: .nominal) } -private func writeDefaultCofig(to path: URL) throws -> Config { - do { - let configData = try JSONEncoder().encode(Config.default) - try configData.write(to: path) - print("Wrote default configuration to \(path.path)") - return .default - } catch { - print("Failed to write default config file at \(path.path): \(error)") - throw error - } -} - -private func loadConfig(at path: URL) throws -> Config { - do { - let configData = try Data(contentsOf: path) - return try JSONDecoder().decode(Config.self, from: configData) - } catch { - print("Failed to load config file at \(path.path): \(error)") - throw error - } +func log(_ message: String) { + monitor.log(message) + print(message) } diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index 819310b..2d46b9d 100755 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -13,7 +13,7 @@ private func authorize(_ request: Request) throws { } } -func routes(_ app: Application) throws { +func routes(_ app: Application) { app.get("version") { _ in "\(server.classifierVersion)"