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