From ef966b0aa35113434046ba4e90584f34b971b36a Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Tue, 21 Dec 2021 15:50:49 +0100 Subject: [PATCH] Optimise files on start --- Sources/App/Management/DiskWriter.swift | 16 +++++++- Sources/App/Management/PlayerManagement.swift | 28 ++++++++++--- Sources/App/Management/TableManagement.swift | 41 +++++++++++++++---- 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/Sources/App/Management/DiskWriter.swift b/Sources/App/Management/DiskWriter.swift index 8a96aa6..0425fca 100644 --- a/Sources/App/Management/DiskWriter.swift +++ b/Sources/App/Management/DiskWriter.swift @@ -8,14 +8,26 @@ import Foundation -protocol DiskWriter { +protocol DiskWriter: AnyObject { - var storageFile: FileHandle { get } + var storageFile: FileHandle { get set } var storageFileUrl: URL { get } } extension DiskWriter { + + func replaceFile(data: String) throws { + let data = data.data(using: .utf8)! + try storageFile.close() + try data.write(to: storageFileUrl) + storageFile = try FileHandle(forUpdating: storageFileUrl) + if #available(macOS 10.15.4, *) { + try storageFile.seekToEnd() + } else { + storageFile.seekToEndOfFile() + } + } static func prepareFile(at url: URL) throws -> FileHandle { if !FileManager.default.fileExists(atPath: url.path) { diff --git a/Sources/App/Management/PlayerManagement.swift b/Sources/App/Management/PlayerManagement.swift index fd623e1..a1252ca 100644 --- a/Sources/App/Management/PlayerManagement.swift +++ b/Sources/App/Management/PlayerManagement.swift @@ -16,7 +16,7 @@ final class PlayerManagement: DiskWriter { /// A reverse mapping between generated access tokens and player name private var playerNameForToken = [SessionToken: PlayerName]() - let storageFile: FileHandle + var storageFile: FileHandle let storageFileUrl: URL @@ -25,7 +25,8 @@ final class PlayerManagement: DiskWriter { storageFileUrl = url storageFile = try Self.prepareFile(at: url) - + + var redundantEntries = 0 try readLinesFromDisk().forEach { line in let parts = line.components(separatedBy: ":") // Token may contain the separator @@ -37,12 +38,29 @@ final class PlayerManagement: DiskWriter { let token = parts.dropFirst().joined(separator: ":") if token == "" { playerPasswordHashes[name] = nil - } else { - playerPasswordHashes[name] = token + redundantEntries += 2 // One for creation, one for deletion + return } + if playerPasswordHashes[name] != nil { + redundantEntries += 1 + } + playerPasswordHashes[name] = token } - print("Loaded \(playerPasswordHashes.count) players") + let playerCount = playerPasswordHashes.count + let totalEntries = playerCount + redundantEntries + let percentage = playerCount * 100 / totalEntries + print("Loaded \(playerCount) players from \(totalEntries) entries (\(percentage) % useful)") + if percentage < 80 && redundantEntries > 10 { + try optimizePlayerFile() + } + } + + private func optimizePlayerFile() throws { + print("Optimizing player file...") + let lines = playerPasswordHashes.map { $0.key + ":" + $0.value + "\n" }.joined() + try replaceFile(data: lines) + print("Done.") } private func save(password: PasswordHash, forPlayer player: PlayerName) -> Bool { diff --git a/Sources/App/Management/TableManagement.swift b/Sources/App/Management/TableManagement.swift index 627da99..07c3522 100644 --- a/Sources/App/Management/TableManagement.swift +++ b/Sources/App/Management/TableManagement.swift @@ -13,7 +13,7 @@ final class TableManagement: DiskWriter { private var tables = [TableId : ManageableTable]() /// The handle to the file where the tables are persisted - let storageFile: FileHandle + var storageFile: FileHandle /// The url to the file where the tables are persisted let storageFileUrl: URL @@ -30,6 +30,7 @@ final class TableManagement: DiskWriter { storageFile = try Self.prepareFile(at: url) var entries = [TableId : (name: TableName, isPublic: Bool, players: [PlayerName])]() + var redundantEntries = 0 try readLinesFromDisk().forEach { line in // Each line has parts: ID | NAME | PLAYER, PLAYER, ... let parts = line.components(separatedBy: ":") @@ -43,14 +44,37 @@ final class TableManagement: DiskWriter { let players = parts[3].components(separatedBy: ",") if name == "" { entries[id] = nil - } else { - entries[id] = (name, isPublic, players) + redundantEntries += 2 // One for creation, one for deletion + return } + if entries[id] != nil { + redundantEntries += 1 + } + entries[id] = (name, isPublic, players) } entries.forEach { id, tableData in tables[id] = WaitingTable(id: id, name: tableData.name, isPublic: tableData.isPublic, players: tableData.players) } - print("Loaded \(tables.count) tables") + let totalEntries = entries.count + redundantEntries + let percentage = entries.count * 100 / totalEntries + print("Loaded \(tables.count) tables from \(totalEntries) entries (\(percentage) % useful)") + if percentage < 80 && redundantEntries > 10 { + try optimizeTableFile() + } + } + + private func optimizeTableFile() throws { + print("Optimizing tables file...") + let lines = tables.values.map(entry).joined(separator: "\n") + "\n" + try replaceFile(data: lines) + print("Done.") + } + + private func entry(for table: ManageableTable) -> String { + let visible = table.isPublic ? "public" : "private" + let players = table.playerNames + .joined(separator: ",") + return [table.id, table.name, visible, players].joined(separator: ":") } /** @@ -62,11 +86,7 @@ final class TableManagement: DiskWriter { */ @discardableResult private func writeTableToDisk(table: ManageableTable) -> Bool { - let visible = table.isPublic ? "public" : "private" - let players = table.playerNames - .joined(separator: ",") - let entry = [table.id, table.name, visible, players] - .joined(separator: ":") + let entry = entry(for: table) return writeToDisk(line: entry) } @@ -187,6 +207,9 @@ final class TableManagement: DiskWriter { } tables[newTable.id] = newTable newTable.sendUpdateToAllPlayers() + if newTable is FinishedTable || newTable is DealingTable { + writeTableToDisk(table: newTable) + } return .success }