Add log view, manual transfer
This commit is contained in:
48
TempTrack/Storage/Log.swift
Normal file
48
TempTrack/Storage/Log.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
|
||||
let log = Log()
|
||||
|
||||
final class Log: ObservableObject {
|
||||
|
||||
private let df: DateFormatter
|
||||
|
||||
init() {
|
||||
df = .init()
|
||||
df.dateStyle = .short
|
||||
df.timeStyle = .medium
|
||||
}
|
||||
|
||||
enum Level: String {
|
||||
case info = "INFO"
|
||||
case warning = "WARN"
|
||||
case error = "ERROR"
|
||||
}
|
||||
|
||||
@Published
|
||||
var logEntries: [LogEntry] = []
|
||||
|
||||
func info(_ message: String) {
|
||||
log(.info, message)
|
||||
}
|
||||
|
||||
func warning(_ message: String) {
|
||||
log(.warning, message)
|
||||
}
|
||||
|
||||
func error(_ message: String) {
|
||||
log(.error, message)
|
||||
}
|
||||
|
||||
func log(_ level: Level, _ message: String) {
|
||||
let entry = LogEntry(level: level, message: message)
|
||||
logEntries.insert(entry, at: 0)
|
||||
print(entry)
|
||||
}
|
||||
}
|
||||
|
||||
extension Log.Level: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
rawValue
|
||||
}
|
||||
}
|
33
TempTrack/Storage/LogEntry.swift
Normal file
33
TempTrack/Storage/LogEntry.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
import Foundation
|
||||
|
||||
struct LogEntry: Identifiable {
|
||||
|
||||
let id: TimeInterval
|
||||
|
||||
let date: Date
|
||||
|
||||
let level: Log.Level
|
||||
|
||||
let message: String
|
||||
|
||||
init(date: Date = Date(), level: Log.Level, message: String) {
|
||||
self.id = date.timeIntervalSince1970
|
||||
self.date = date
|
||||
self.level = level
|
||||
self.message = message
|
||||
}
|
||||
}
|
||||
|
||||
private let df: DateFormatter = {
|
||||
let df = DateFormatter()
|
||||
df.dateStyle = .short
|
||||
df.timeStyle = .medium
|
||||
return df
|
||||
}()
|
||||
|
||||
extension LogEntry: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
"[\(df.string(from: date))][\(level.rawValue)] \(message)"
|
||||
}
|
||||
}
|
@@ -57,6 +57,7 @@ final class TemperatureStorage: ObservableObject {
|
||||
|
||||
if lastMeasurements.isEmpty {
|
||||
loadLastMeasurements()
|
||||
loadDailyCounts()
|
||||
} else {
|
||||
setDailyCounts(from: lastMeasurements)
|
||||
}
|
||||
@@ -71,7 +72,7 @@ final class TemperatureStorage: ObservableObject {
|
||||
do {
|
||||
try fm.createDirectory(at: storageFolder, withIntermediateDirectories: true)
|
||||
} catch {
|
||||
print("Failed to create folder: \(error)")
|
||||
log.error("Failed to create folder: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,17 +100,20 @@ final class TemperatureStorage: ObservableObject {
|
||||
let dateIndexOfStart = startDate.dateIndex
|
||||
guard todayIndex != dateIndexOfStart else {
|
||||
recentMeasurements = todayValues
|
||||
log.info("Loaded \(recentMeasurements.count) recent measurements")
|
||||
return
|
||||
}
|
||||
let yesterdayValues = loadMeasurements(for: dateIndexOfStart)
|
||||
.filter { $0.date >= startDate }
|
||||
recentMeasurements = yesterdayValues + todayValues
|
||||
log.info("Loaded \(recentMeasurements.count) recent measurements")
|
||||
}
|
||||
|
||||
private func updateLastMeasurements(_ measurements: [TemperatureMeasurement]) {
|
||||
let startDate = Date().addingTimeInterval(-lastValueInterval).seconds
|
||||
let new = recentMeasurements + measurements
|
||||
recentMeasurements = Array(new.drop { $0.id < startDate })
|
||||
log.info("\(recentMeasurements.count) recent measurements (of \(measurements.count) new entries)")
|
||||
}
|
||||
|
||||
private func loadMeasurements(for date: Date) -> [TemperatureMeasurement] {
|
||||
@@ -123,30 +127,30 @@ final class TemperatureStorage: ObservableObject {
|
||||
private func loadMeasurements(from fileName: String) -> [TemperatureMeasurement] {
|
||||
let fileUrl = fileUrl(for: fileName)
|
||||
guard fm.fileExists(atPath: fileUrl.path) else {
|
||||
print("No measurements for \(fileName)")
|
||||
log.info("No measurements for \(fileName)")
|
||||
return []
|
||||
}
|
||||
do {
|
||||
let content = try Data(contentsOf: fileUrl)
|
||||
let points: [TemperatureMeasurement] = try BinaryDecoder.decode(from: content)
|
||||
print("Loaded \(points.count) points for \(fileName)")
|
||||
log.info("Loaded \(points.count) points from \(fileName)")
|
||||
return points
|
||||
} catch {
|
||||
print("Failed to read file \(fileName): \(error)")
|
||||
log.error("Failed to read file \(fileName): \(error)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func add(_ measurements: [TemperatureMeasurement]) {
|
||||
let lastDate = self.newestMeasurementDate.seconds
|
||||
let newValues = measurements
|
||||
.filter { $0.id > lastDate }
|
||||
.splitByDate()
|
||||
let newerValues = measurements.filter { $0.id > lastDate }
|
||||
let newValues = newerValues.splitByDate()
|
||||
log.info("Adding \(newValues.count) of \(measurements.count) measurements")
|
||||
|
||||
for (dateIndex, values) in newValues {
|
||||
let count = saveNew(values, for: dateIndex)
|
||||
setDailyCount(count, for: dateIndex)
|
||||
print("Day \(dateIndex): \(count) values")
|
||||
//log.info("Day \(dateIndex): \(count) values")
|
||||
}
|
||||
saveDailyCounts()
|
||||
updateLastMeasurements(measurements)
|
||||
@@ -155,7 +159,7 @@ final class TemperatureStorage: ObservableObject {
|
||||
func removeMeasurements(for dateIndex: Int) {
|
||||
let fileUrl = fileUrl(for: dateIndex)
|
||||
guard fm.fileExists(atPath: fileUrl.path) else {
|
||||
print("No measurements for \(fileUrl.lastPathComponent)")
|
||||
log.warning("No measurements for \(fileUrl.lastPathComponent)")
|
||||
return
|
||||
}
|
||||
do {
|
||||
@@ -163,7 +167,7 @@ final class TemperatureStorage: ObservableObject {
|
||||
dailyMeasurementCounts = dailyMeasurementCounts.filter { $0.dateIndex != dateIndex }
|
||||
recentMeasurements = recentMeasurements.filter { $0.date.dateIndex != dateIndex }
|
||||
} catch {
|
||||
print("Failed to delete \(fileUrl.lastPathComponent): \(error)")
|
||||
log.error("Failed to delete \(fileUrl.lastPathComponent): \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +187,7 @@ final class TemperatureStorage: ObservableObject {
|
||||
let data = try BinaryEncoder.encode(measurements.sorted())
|
||||
try data.write(to: fileUrl)
|
||||
} catch {
|
||||
print("Failed to save \(fileName): \(error)")
|
||||
log.error("Failed to save \(fileName): \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +223,7 @@ final class TemperatureStorage: ObservableObject {
|
||||
let data = try Data(contentsOf: overviewFileUrl)
|
||||
dailyMeasurementCounts = try BinaryDecoder.decode(from: data)
|
||||
} catch {
|
||||
print("Failed to load overview: \(error)")
|
||||
log.error("Failed to load overview: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +232,7 @@ final class TemperatureStorage: ObservableObject {
|
||||
let data = try BinaryEncoder.encode(dailyMeasurementCounts)
|
||||
try data.write(to: overviewFileUrl)
|
||||
} catch {
|
||||
print("Failed to write overview: \(error)")
|
||||
log.error("Failed to write overview: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,9 +246,12 @@ final class TemperatureStorage: ObservableObject {
|
||||
|
||||
func recalculateDailyCounts() {
|
||||
do {
|
||||
let newValues: [Int: Int] = try fm.contentsOfDirectory(atPath: storageFolder.path)
|
||||
let files = try fm.contentsOfDirectory(atPath: storageFolder.path)
|
||||
let newValues: [Int: Int] = files
|
||||
.reduce(into: [:]) { counts, fileName in
|
||||
guard let dateIndex = Int(fileName) else {
|
||||
let dateString = fileName.replacingOccurrences(of: ".bin", with: "")
|
||||
guard let dateIndex = Int(dateString) else {
|
||||
log.warning("Found file with invalid name \(fileName)")
|
||||
return
|
||||
}
|
||||
counts[dateIndex] = loadMeasurements(from: fileName).count
|
||||
@@ -253,9 +260,10 @@ final class TemperatureStorage: ObservableObject {
|
||||
self.dailyMeasurementCounts = newValues
|
||||
.map { .init(dateIndex: $0.key, count: $0.value) }
|
||||
.sorted()
|
||||
log.info("Daily counts recalculated from \(files.count) files")
|
||||
}
|
||||
} catch {
|
||||
print("Failed to load daily counts: \(error)")
|
||||
log.error("Failed to load daily counts: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user