import Vapor private var eventLogHandle: FileHandle? private var registeredGuests = Set() private var declinedGuests = Set() private var guestListPath: URL! private var declinedListPath: URL! private var maximumGuestCount = 100 private let df: DateFormatter = { let df = DateFormatter() df.dateFormat = "dd.MM. HH:mm" return df }() func guestCount() -> Int { registeredGuests .reduce([]) { $0 + $1.components(separatedBy: "+") } .reduce([]) { $0 + $1.components(separatedBy: " - ") } .reduce([]) { $0 + $1.components(separatedBy: ",") } .reduce([]) { $0 + $1.components(separatedBy: " und ") } .reduce([]) { $0 + $1.components(separatedBy: "&") } //.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .count } func add(guest: String) -> String { guard registeredGuests.count < maximumGuestCount else { return "Too many requests" } registeredGuests.insert(guest) declinedGuests.remove(guest) defer { saveLists() } return log(event: "\(guest) registered") } func remove(guest: String) -> String { guard declinedGuests.count < maximumGuestCount else { return "Too many requests" } registeredGuests.remove(guest) declinedGuests.insert(guest) defer { saveLists() } return log(event: "\(guest) declined") } private func saveLists() { saveList(registeredGuests, named: "guest", to: guestListPath) saveList(declinedGuests, named: "declined", to: declinedListPath) } private func saveList(_ set: Set, named name: String, to url: URL) { guard let list = set.sorted().joined(separator: "\n").data(using: .utf8) else { log("Failed to save \(name) list, no data") return } do { try list.write(to: url) log("Saved \(name) list with \(set.count) entries") } catch { log("Failed to write \(name) list: \(error)") } } private func loadList(from url: URL) throws -> Set { do { let users = try String(contentsOf: url) .split(separator: "\n") .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } log("Loaded list \(url.path) (\(users.count) entries)") return .init(users) } catch { log("Failed to load \(url.path): \(error)") throw error } } private func log(event: String) -> String { guard let handle = eventLogHandle else { return "No handle" } let date = Date() let dateString = df.string(from: date) guard let entry = "[\(dateString)] \(event)\n".data(using: .utf8) else { return "Invalid name" } guard #available(macOS 10.15.4, *) else { handle.write(entry) handle.synchronizeFile() return "Success" } do { try handle.write(contentsOf: entry) handle.synchronizeFile() return "Success" } catch { return "Save failed" } } private func createFileIfNeeded(at path: URL) throws { guard !FileManager.default.fileExists(atPath: path.path) else { return } do { try Data().write(to: path) } catch { log("Failed to create \(path.path): \(error)") throw error } } private func readConfig(at path: URL) throws -> String { do { let content = try String(contentsOf: path) .trimmingCharacters(in: .whitespacesAndNewlines) return content } catch { log("Failed to read configuration file at \(path.path): \(error)") throw error } } private func configureFromFile(at configPath: URL, app: Application) throws { let config = try readConfig(at: configPath) .components(separatedBy: "\n") .map { $0.trimmingCharacters(in: .whitespaces) } .filter { !$0.isEmpty } guard config.count == 3 else { log("Invalid configuration file at \(configPath.path)") throw FestivalError.invalidConfiguration } try Log.set(logFile: config[0]) guard let count = Int(config[1]) else { log("Invalid maximum guest count '\(config[1])', using default") return } maximumGuestCount = count guard let port = Int(config[2]) else { log("Invalid port '\(config[2])', using default") return } app.http.server.configuration.port = port } public func configure(_ app: Application) throws { let configPath = URL(fileURLWithPath: app.directory.resourcesDirectory) .appendingPathComponent("config.conf") try configureFromFile(at: configPath, app: app) let listDirectory = URL(fileURLWithPath: app.directory.publicDirectory) .appendingPathComponent("lists") let eventLog = listDirectory.appendingPathComponent("events.txt") guestListPath = listDirectory.appendingPathComponent("registered.txt") declinedListPath = listDirectory.appendingPathComponent("declined.txt") // Create the files try createFileIfNeeded(at: eventLog) try createFileIfNeeded(at: guestListPath) try createFileIfNeeded(at: declinedListPath) // Create handle to write events eventLogHandle = try FileHandle(forWritingTo: eventLog) if #available(macOS 10.15.4, *) { try eventLogHandle?.seekToEnd() } else { // Fallback on earlier versions eventLogHandle?.seekToEndOfFile() } registeredGuests = try loadList(from: guestListPath) declinedGuests = try loadList(from: declinedListPath) // register routes try routes(app) let date = Date() let dateString = df.string(from: date) log("[\(dateString)] Server started on port \(app.http.server.configuration.port): \(registeredGuests.count) registered, \(declinedGuests.count) declined, event log open: \(eventLogHandle != nil)") }