From 7265fd0f0dc9a813a11de7020086b6a188c535d4 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Wed, 1 Dec 2021 12:45:42 +0100 Subject: [PATCH] Persist registrations on disk --- Resources/.gitkeep | 1 + Sources/App/Model/Database.swift | 6 +- Sources/App/Model/PlayerManagement.swift | 74 +++++++++++++++++++++++- Sources/App/configure.swift | 4 +- 4 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 Resources/.gitkeep diff --git a/Resources/.gitkeep b/Resources/.gitkeep new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/Resources/.gitkeep @@ -0,0 +1 @@ + diff --git a/Sources/App/Model/Database.swift b/Sources/App/Model/Database.swift index 61e708c..1103a06 100644 --- a/Sources/App/Model/Database.swift +++ b/Sources/App/Model/Database.swift @@ -7,10 +7,10 @@ final class Database { private let tables: TableManagement - init() { - self.players = PlayerManagement() + init(storageFolder: URL) throws { + self.players = try PlayerManagement(storageFolder: storageFolder) self.tables = TableManagement() - // TODO: Load server data from disk + // TODO: Load table data from disk // TODO: Save data to disk } diff --git a/Sources/App/Model/PlayerManagement.swift b/Sources/App/Model/PlayerManagement.swift index b6b037f..5bdf65a 100644 --- a/Sources/App/Model/PlayerManagement.swift +++ b/Sources/App/Model/PlayerManagement.swift @@ -15,9 +15,75 @@ final class PlayerManagement { /// A reverse mapping between generated access tokens and player name private var playerNameForToken = [SessionToken: PlayerName]() + + private let passwordFile: FileHandle + + private let passwordFileUrl: URL - init() { + init(storageFolder: URL) throws { + let url = storageFolder.appendingPathComponent("passwords.txt") + if !FileManager.default.fileExists(atPath: url.path) { + try Data().write(to: url) + } + + passwordFile = try FileHandle(forUpdating: url) + passwordFileUrl = url + + if #available(macOS 10.15.4, *) { + guard let data = try passwordFile.readToEnd() else { + try passwordFile.seekToEnd() + return + } + try loadPasswords(data: data) + } else { + let data = passwordFile.readDataToEndOfFile() + try loadPasswords(data: data) + } + print("Loaded \(playerPasswordHashes.count) players") + } + + private func loadPasswords(data: Data) throws { + String(data: data, encoding: .utf8)! + .components(separatedBy: "\n") + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { $0 != "" } + .forEach { line in + let parts = line.components(separatedBy: ":") + // Token may contain the separator + guard parts.count >= 2 else { + print("Invalid line in password file") + return + } + let name = parts[0] + let token = parts.dropFirst().joined(separator: ":") + if token == "" { + playerPasswordHashes[name] = nil + } else { + playerPasswordHashes[name] = token + } + } + } + + private func save(password: PasswordHash, forPlayer player: PlayerName) -> Bool { + let entry = player + ":" + password + "\n" + let data = entry.data(using: .utf8)! + do { + if #available(macOS 10.15.4, *) { + try passwordFile.write(contentsOf: data) + } else { + passwordFile.write(data) + } + try passwordFile.synchronize() + return true + } catch { + print("Failed to save password to disk: \(error)") + return false + } + } + + private func deletePassword(forPlayer player: PlayerName) -> Bool { + save(password: "", forPlayer: player) } /** @@ -48,6 +114,9 @@ final class PlayerManagement { guard !hasRegisteredPlayer(named: name) else { return nil } + guard save(password: hash, forPlayer: name) else { + return nil + } self.playerPasswordHashes[name] = hash return startNewSessionForRegisteredPlayer(named: name) } @@ -58,6 +127,9 @@ final class PlayerManagement { - Returns: The session token of the current player, if one exists */ func deletePlayer(named name: PlayerName) -> SessionToken? { + guard deletePassword(forPlayer: name) else { + return nil + } playerPasswordHashes.removeValue(forKey: name) guard let sessionToken = sessionTokenForPlayer.removeValue(forKey: name) else { return nil diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 78f9455..8b8b873 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -7,7 +7,9 @@ public func configure(_ app: Application) throws { // serve files from /Public folder app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) - database = Database() + let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory) + database = try Database(storageFolder: storageFolder) + // register routes try routes(app) }