parent
a2f8146a77
commit
2cca84e670
@ -0,0 +1,59 @@ |
||||
// |
||||
// Codable+Extensions.swift |
||||
// LeCountdown |
||||
// |
||||
// Created by Laurent Morvillier on 10/04/2023. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
extension Encodable { |
||||
|
||||
var jsonString: String? { |
||||
if let data = self.jsonData { |
||||
return String(data: data, encoding: .utf8) |
||||
} else { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
var jsonData: Data? { |
||||
let encoder: JSONEncoder = JSONEncoder() |
||||
do { |
||||
return try encoder.encode(self) |
||||
} catch { |
||||
Logger.error(error) |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
extension String { |
||||
|
||||
func decode<T : Decodable>() -> T? { |
||||
return self.data(using: .utf8)?.decode() |
||||
} |
||||
|
||||
func decodeArray<T : Decodable>() throws -> [T]? { |
||||
return try self.data(using: .utf8)?.decodeArray() |
||||
} |
||||
|
||||
} |
||||
|
||||
extension Data { |
||||
|
||||
func decode<T : Decodable>() -> T? { |
||||
do { |
||||
return try JSONDecoder().decode(T.self, from: self) |
||||
} catch { |
||||
Logger.error(error) |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func decodeArray<T : Decodable>() throws -> [T] { |
||||
return try JSONDecoder().decode([T].self, from: self) |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,82 @@ |
||||
// |
||||
// FileLogger.swift |
||||
// LeCountdown |
||||
// |
||||
// Created by Laurent Morvillier on 10/04/2023. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
struct Log: Identifiable, Codable { |
||||
|
||||
var id: String = UUID().uuidString |
||||
|
||||
var date: Date |
||||
var file: String |
||||
var line: Int |
||||
var function: String |
||||
var message: String |
||||
|
||||
var content: String { |
||||
return "\(file).\(line).\(function): \(message)" |
||||
} |
||||
|
||||
} |
||||
|
||||
class FileLogger { |
||||
|
||||
fileprivate let fileName = "logs.json" |
||||
|
||||
static var main: FileLogger = FileLogger() |
||||
|
||||
var logs: [Log] |
||||
|
||||
var timer: Timer? = nil |
||||
|
||||
init() { |
||||
self.logs = [] |
||||
|
||||
do { |
||||
let content = try FileUtils.readDocumentFile(fileName: self.fileName) |
||||
if let logs: [Log] = try content.decodeArray() { |
||||
self.logs = logs |
||||
} else { |
||||
Logger.w("Log decoding failed") |
||||
} |
||||
} catch { |
||||
Logger.error(error) |
||||
} |
||||
|
||||
} |
||||
|
||||
func addLog(_ log: Log) { |
||||
self.logs.append(log) |
||||
self._scheduleWrite() |
||||
} |
||||
|
||||
fileprivate func _scheduleWrite() { |
||||
self.timer?.invalidate() |
||||
self.timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { _ in |
||||
self._writeLogs() |
||||
}) |
||||
} |
||||
|
||||
fileprivate func _writeLogs() { |
||||
DispatchQueue(label: "app.enchant.write", qos: .utility).async { |
||||
if let json = self.logs.jsonString { |
||||
do { |
||||
let _ = try FileUtils.writeToDocumentDirectory(content: json, fileName: self.fileName) |
||||
} catch { |
||||
Logger.error(error) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@objc static public func log(_ message: String, file: String = #file, function: String = #function, line: Int = #line) { |
||||
let filestr = NSString(string: file) |
||||
let log = Log(date: Date(), file: filestr.lastPathComponent, line: line, function: function, message: message) |
||||
FileLogger.main.addLog(log) |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,54 @@ |
||||
// |
||||
// FileUtils.swift |
||||
// LeCountdown |
||||
// |
||||
// Created by Laurent Morvillier on 10/04/2023. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
enum FileError : Error { |
||||
case documentDirectoryNotFound |
||||
} |
||||
|
||||
class FileUtils { |
||||
|
||||
static func pathsFromDocumentsDirectory() throws -> [String] { |
||||
let documentsURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] |
||||
return try FileManager.default.contentsOfDirectory(atPath: documentsURL.path) |
||||
} |
||||
|
||||
static func readDocumentFile(fileName: String) throws -> String { |
||||
if let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { |
||||
let fileURL: URL = dir.appendingPathComponent(fileName) |
||||
return try String(contentsOf: fileURL, encoding: .utf8) |
||||
} |
||||
throw FileError.documentDirectoryNotFound |
||||
} |
||||
|
||||
static func readFile(fileURL: URL) throws -> String { |
||||
return try String(contentsOf: fileURL, encoding: .utf8) |
||||
} |
||||
|
||||
static func writeToDocumentDirectory(content: String, fileName: String) throws -> URL { |
||||
|
||||
if let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { |
||||
let fileURL: URL = dir.appendingPathComponent(fileName) |
||||
try content.write(to: fileURL, atomically: false, encoding: .utf8) |
||||
return fileURL |
||||
} |
||||
throw FileError.documentDirectoryNotFound |
||||
} |
||||
|
||||
@discardableResult static func writeToDocumentDirectory(data: Data, fileName: String) throws -> URL { |
||||
|
||||
if let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { |
||||
let fileURL: URL = dir.appendingPathComponent(fileName) |
||||
try data.write(to: fileURL) |
||||
Logger.log("Wrote file = \(fileURL.absoluteString)") |
||||
return fileURL |
||||
} |
||||
throw FileError.documentDirectoryNotFound |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,39 @@ |
||||
// |
||||
// LogsView.swift |
||||
// LeCountdown |
||||
// |
||||
// Created by Laurent Morvillier on 10/04/2023. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct LogsView: View { |
||||
var body: some View { |
||||
List { |
||||
ForEach(FileLogger.main.logs) { log in |
||||
LogView(log: log) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
struct LogView: View { |
||||
|
||||
var log: Log |
||||
|
||||
var body: some View { |
||||
|
||||
VStack(alignment: .leading) { |
||||
Text(log.date, style: .time) |
||||
.foregroundColor(.gray) |
||||
Text(log.content) |
||||
}.font(.footnote) |
||||
} |
||||
} |
||||
|
||||
struct LogsView_Previews: PreviewProvider { |
||||
|
||||
static var previews: some View { |
||||
LogView(log: Log(date: Date(), file: "text.txt", line: 100, function: "test()", message: "crazy stuff here")) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue