Optimise files on start

This commit is contained in:
Christoph Hagen 2021-12-21 15:50:49 +01:00
parent 19aa91eff2
commit ef966b0aa3
3 changed files with 69 additions and 16 deletions

View File

@ -8,15 +8,27 @@
import Foundation import Foundation
protocol DiskWriter { protocol DiskWriter: AnyObject {
var storageFile: FileHandle { get } var storageFile: FileHandle { get set }
var storageFileUrl: URL { get } var storageFileUrl: URL { get }
} }
extension DiskWriter { 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 { static func prepareFile(at url: URL) throws -> FileHandle {
if !FileManager.default.fileExists(atPath: url.path) { if !FileManager.default.fileExists(atPath: url.path) {
try Data().write(to: url) try Data().write(to: url)

View File

@ -16,7 +16,7 @@ final class PlayerManagement: DiskWriter {
/// A reverse mapping between generated access tokens and player name /// A reverse mapping between generated access tokens and player name
private var playerNameForToken = [SessionToken: PlayerName]() private var playerNameForToken = [SessionToken: PlayerName]()
let storageFile: FileHandle var storageFile: FileHandle
let storageFileUrl: URL let storageFileUrl: URL
@ -26,6 +26,7 @@ final class PlayerManagement: DiskWriter {
storageFileUrl = url storageFileUrl = url
storageFile = try Self.prepareFile(at: url) storageFile = try Self.prepareFile(at: url)
var redundantEntries = 0
try readLinesFromDisk().forEach { line in try readLinesFromDisk().forEach { line in
let parts = line.components(separatedBy: ":") let parts = line.components(separatedBy: ":")
// Token may contain the separator // Token may contain the separator
@ -37,12 +38,29 @@ final class PlayerManagement: DiskWriter {
let token = parts.dropFirst().joined(separator: ":") let token = parts.dropFirst().joined(separator: ":")
if token == "" { if token == "" {
playerPasswordHashes[name] = nil playerPasswordHashes[name] = nil
} else { redundantEntries += 2 // One for creation, one for deletion
return
}
if playerPasswordHashes[name] != nil {
redundantEntries += 1
}
playerPasswordHashes[name] = token playerPasswordHashes[name] = token
} }
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()
}
} }
print("Loaded \(playerPasswordHashes.count) players") 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 { private func save(password: PasswordHash, forPlayer player: PlayerName) -> Bool {

View File

@ -13,7 +13,7 @@ final class TableManagement: DiskWriter {
private var tables = [TableId : ManageableTable]() private var tables = [TableId : ManageableTable]()
/// The handle to the file where the tables are persisted /// 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 /// The url to the file where the tables are persisted
let storageFileUrl: URL let storageFileUrl: URL
@ -30,6 +30,7 @@ final class TableManagement: DiskWriter {
storageFile = try Self.prepareFile(at: url) storageFile = try Self.prepareFile(at: url)
var entries = [TableId : (name: TableName, isPublic: Bool, players: [PlayerName])]() var entries = [TableId : (name: TableName, isPublic: Bool, players: [PlayerName])]()
var redundantEntries = 0
try readLinesFromDisk().forEach { line in try readLinesFromDisk().forEach { line in
// Each line has parts: ID | NAME | PLAYER, PLAYER, ... // Each line has parts: ID | NAME | PLAYER, PLAYER, ...
let parts = line.components(separatedBy: ":") let parts = line.components(separatedBy: ":")
@ -43,14 +44,37 @@ final class TableManagement: DiskWriter {
let players = parts[3].components(separatedBy: ",") let players = parts[3].components(separatedBy: ",")
if name == "" { if name == "" {
entries[id] = nil entries[id] = nil
} else { redundantEntries += 2 // One for creation, one for deletion
entries[id] = (name, isPublic, players) return
} }
if entries[id] != nil {
redundantEntries += 1
}
entries[id] = (name, isPublic, players)
} }
entries.forEach { id, tableData in entries.forEach { id, tableData in
tables[id] = WaitingTable(id: id, name: tableData.name, isPublic: tableData.isPublic, players: tableData.players) 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 @discardableResult
private func writeTableToDisk(table: ManageableTable) -> Bool { private func writeTableToDisk(table: ManageableTable) -> Bool {
let visible = table.isPublic ? "public" : "private" let entry = entry(for: table)
let players = table.playerNames
.joined(separator: ",")
let entry = [table.id, table.name, visible, players]
.joined(separator: ":")
return writeToDisk(line: entry) return writeToDisk(line: entry)
} }
@ -187,6 +207,9 @@ final class TableManagement: DiskWriter {
} }
tables[newTable.id] = newTable tables[newTable.id] = newTable
newTable.sendUpdateToAllPlayers() newTable.sendUpdateToAllPlayers()
if newTable is FinishedTable || newTable is DealingTable {
writeTableToDisk(table: newTable)
}
return .success return .success
} }