CHGenerator/WebsiteGenerator/Generic/FileAccess.swift

174 lines
5.3 KiB
Swift
Raw Normal View History

2022-08-19 18:05:06 +02:00
import Foundation
enum FileAccessError: Error {
case failedToReadFile(String, Error)
}
final class FileAccess {
static let accessTimesFileName = "access.json"
let errorOutput: ErrorOutput
let sourceFolder: URL
private let source = "FileAccess"
private var modificationTimeCacheFile: URL {
sourceFolder.appendingPathComponent(FileAccess.accessTimesFileName)
}
/**
The time stamps of last modified times for all accessed source files.
The key is the relative path to the file from the source
*/
private var sourceLastModifiedTimes: [String : Date] = [:]
private var changedFiles: Set<String> = []
2022-08-25 00:11:53 +02:00
private var accessedFiles: Set<String> = []
2022-08-19 18:05:06 +02:00
init(in root: URL, errorOutput: ErrorOutput) {
self.sourceFolder = root
self.errorOutput = errorOutput
loadSavedModificationTimes()
}
private func loadSavedModificationTimes() {
let url = modificationTimeCacheFile
guard url.exists else {
errorOutput.add(info: "No file modification times loaded, regarding all content as new", source: source)
return
}
let data: Data
do {
data = try Data(contentsOf: url)
} catch {
errorOutput.add(
warning: "File modification times data not read, regarding all content as new",
source: source,
error: error)
return
}
do {
self.sourceLastModifiedTimes = try JSONDecoder().decode(from: data)
} catch {
errorOutput.add(
warning: "File modification times not decoded, regarding all content as new",
source: source,
error: error)
try? url.delete()
return
}
}
private func didAccess(inputPath: String, modified lastModified: Date, source: String) {
2022-08-25 00:11:53 +02:00
accessedFiles.insert(inputPath)
2022-08-19 18:05:06 +02:00
guard let previousDate = sourceLastModifiedTimes[inputPath] else {
// File not processed before, so mark as changed
changedFiles.insert(inputPath)
return
}
guard lastModified > previousDate else {
// File is unchanged
return
}
changedFiles.insert(inputPath)
}
private func lastModifiedTime(of url: URL) -> Date? {
guard url.exists else {
return nil
}
do {
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
guard let date = attributes[.modificationDate] as? Date else {
errorOutput.add(warning: "Failed to read modification time of \(url.path)", source: source)
return nil
}
return date
} catch {
errorOutput.add(warning: "Failed to read file attributes of \(url.path)", source: source)
return nil
}
}
func loadStringContent(inputPath: String) throws -> String? {
try load(inputPath: inputPath, String.init)
}
func loadDataContent(inputPath: String) throws -> Data? {
try load(inputPath: inputPath) { try Data(contentsOf: $0) }
}
private func load<T>(inputPath: String, _ closure: (URL) throws -> T) rethrows -> T? {
let url = sourceFolder.appendingPathComponent(inputPath)
guard let modifiedDate = lastModifiedTime(of: url) else {
sourceLastModifiedTimes[inputPath] = nil
return nil
}
do {
let content = try closure(url)
didAccess(inputPath: inputPath, modified: modifiedDate, source: source)
return content
} catch {
throw FileAccessError.failedToReadFile(inputPath, error)
}
}
func didGenerateAllFiles() {
for file in changedFiles {
let url = sourceFolder.appendingPathComponent(file)
guard let date = lastModifiedTime(of: url) else {
continue
}
sourceLastModifiedTimes[file] = date
}
do {
let data = try JSONEncoder().encode(sourceLastModifiedTimes)
try data.write(to: modificationTimeCacheFile)
} catch {
errorOutput.add(warning: "Failed to save modification times", source: source, error: error)
}
}
func printChangedFilesOverview() {
let count = changedFiles.count
guard count > 0 else {
print("No files modified")
return
}
print("\(count) files modified:")
changedFiles.prefix(10).forEach { print(" " + $0) }
if count > 10 {
print(" ...")
}
}
2022-08-25 00:11:53 +02:00
func printAccessedFilesOverview() {
let count = accessedFiles.count
guard count > 0 else {
print("No files accessed")
return
}
print("\(count) files accessed:")
accessedFiles.prefix(10).forEach { print(" " + $0) }
if count > 10 {
print(" ...")
}
}
func printAllTouchedFiles() {
print("\(accessedFiles.count) files accessed:")
accessedFiles.sorted().forEach { file in
if changedFiles.contains(file) {
print(" \(file) (changed)")
} else {
print(" " + file)
}
}
}
2022-08-19 18:05:06 +02:00
}