import Foundation struct Files { let configuration: DnsConfiguration var lastDataURL: URL { .init(fileURLWithPath: configuration.lastUpdateFilePath) } var logFileURL: URL { .init(fileURLWithPath: configuration.logFilePath) } var domainConfigurationURL: URL { .init(fileURLWithPath: configuration.domainConfigFilePath) } init(configurationFileURL: URL) throws { let data: Data do { data = try Data(contentsOf: configurationFileURL) } catch { throw DNSError.failedToReadLogFile(error) } do { self.configuration = try JSONDecoder().decode(DnsConfiguration.self, from: data) } catch { throw DNSError.failedToDecodeLogFile(error) } } func loadDomainConfiguration() throws -> DomainConfiguration { let data: Data do { data = try Data(contentsOf: domainConfigurationURL) } catch { throw DNSError.failedToReadDomainConfiguration(error) } do { return try JSONDecoder().decode(DomainConfiguration.self, from: data) } catch { throw DNSError.failedToDecodeDomainConfiguration(error) } } func loadLastState() throws -> AppState { guard FileManager.default.fileExists(atPath: configuration.lastUpdateFilePath) else { return .init(date: Date(), state: .nominal, domainStates: [:]) } let data: Data do { data = try Data(contentsOf: lastDataURL) } catch { throw DNSError.failedToReadLastState(error) } let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 let state: AppState do { state = try decoder.decode(AppState.self, from: data) } catch { throw DNSError.failedToDecodeLastState(error) } return state } func save(lastState: AppState) throws { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .secondsSince1970 encoder.outputFormatting = .prettyPrinted let data: Data do { data = try encoder.encode(lastState) } catch { throw DNSError.failedToEncodeState(error) } do { try data.write(to: lastDataURL) } catch { throw DNSError.failedToWriteState(error) } } func addLogEntry(changedDomains: [String : DomainState]) throws { if !FileManager.default.fileExists(atPath: configuration.logFilePath) { do { try Data().write(to: logFileURL) } catch { throw DNSError.failedToCreateDNSLog(error) } } guard let handle = FileHandle(forUpdatingAtPath: logFileURL.path) else { throw DNSError.failedToOpenDNSLogForWriting } let entry = changedDomains.map { $1.logEntry(domain: $0) } .joined(separator: "\n") + "\n" let entryData = entry.data(using: .utf8)! do { try handle.seekToEnd() try handle.write(contentsOf: entryData) try handle.close() } catch { throw DNSError.failedToWriteDNSLog(error) } } }