From 1e1e752de10e5fdcd549dc277778625ec01dd9dd Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Fri, 5 Apr 2024 12:12:34 +0200 Subject: [PATCH 01/25] feat: add EventStorage --- Sources/Confidence/EventStorage.swift | 88 +++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Sources/Confidence/EventStorage.swift diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift new file mode 100644 index 00000000..a632efb7 --- /dev/null +++ b/Sources/Confidence/EventStorage.swift @@ -0,0 +1,88 @@ +import Foundation + +internal protocol EventStorage { + func startNewBatch() throws + func writeEvent(event: Event) throws + func batchReadyFiles() throws -> [URL] + func eventsFrom(fileURL: URL) throws -> [Event] +} + +internal class EventStorageImpl: EventStorage { + let DIRECTORY = "events" + let READYTOSENDEXTENSION = ".ready" + let encoder = JSONEncoder() + let decoder = JSONDecoder() + var folderURL: URL = URL(string: "")! + var fileURL: URL = URL(string: "")! + var currentBatch: [Event] = [] + + init() throws { + folderURL = URL(fileURLWithPath: try getFolderURL()) + fileURL = folderURL.appendingPathComponent("events-\(Date().currentTime)") + } + + func startNewBatch() throws { + let urlString = "\(fileURL)"+"\(READYTOSENDEXTENSION)" + let newPath = URL(fileURLWithPath: urlString) + try FileManager.default.moveItem(at: fileURL, to: newPath) + fileURL = folderURL.appendingPathComponent("events-\(Date().currentTime)") + currentBatch = [] + } + + func writeEvent(event: Event) throws { + currentBatch.append(event) + let data = try encoder.encode(currentBatch) + try data.write(to: fileURL, options: .atomic) + } + + func batchReadyFiles() throws -> [URL] { + var readyFilesList: [URL] = [] + let directoryContents = try FileManager.default.contentsOfDirectory(atPath: folderURL.absoluteString) + for file in directoryContents { + if file.hasSuffix(READYTOSENDEXTENSION) { + readyFilesList.append(URL(string: file)!) + } + } + return readyFilesList + } + + func eventsFrom(fileURL: URL) throws -> [Event] { + let data = try Data(contentsOf: fileURL) + let events = try decoder.decode([Event].self, from: data) + return events + } + + private func getFolderURL() throws -> String { + let rootFolderURL = try FileManager.default.url( + for: .cachesDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: true + ) + var nestedFolderURL: String + if #available(iOS 16.0, *) { + nestedFolderURL = rootFolderURL.appending(path: DIRECTORY).absoluteString + } else { + nestedFolderURL = rootFolderURL.appendingPathComponent(DIRECTORY, isDirectory: true).absoluteString + } + return nestedFolderURL + } +} + +struct Event: Codable { + let eventDefinition: String + let eventTime: Date + // TODO: fix this to be ConfidenceValue + let payload: [String] + let context: [String] +} + + +extension Date { + var currentTime: String { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .short + return dateFormatter.string(from: self) + } +} + From fcc0d158801320ee62fd7add4c24470694082ba1 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Mon, 8 Apr 2024 11:52:58 +0200 Subject: [PATCH 02/25] refactor: make getFolderURL() static --- Sources/Confidence/EventStorage.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index a632efb7..f7da3464 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -8,8 +8,8 @@ internal protocol EventStorage { } internal class EventStorageImpl: EventStorage { - let DIRECTORY = "events" - let READYTOSENDEXTENSION = ".ready" + static let DIRECTORY = "events" + static let READYTOSENDEXTENSION = ".ready" let encoder = JSONEncoder() let decoder = JSONDecoder() var folderURL: URL = URL(string: "")! @@ -17,12 +17,12 @@ internal class EventStorageImpl: EventStorage { var currentBatch: [Event] = [] init() throws { - folderURL = URL(fileURLWithPath: try getFolderURL()) + folderURL = URL(fileURLWithPath: try EventStorageImpl.getFolderURL()) fileURL = folderURL.appendingPathComponent("events-\(Date().currentTime)") } func startNewBatch() throws { - let urlString = "\(fileURL)"+"\(READYTOSENDEXTENSION)" + let urlString = "\(fileURL)"+"\(EventStorageImpl.READYTOSENDEXTENSION)" let newPath = URL(fileURLWithPath: urlString) try FileManager.default.moveItem(at: fileURL, to: newPath) fileURL = folderURL.appendingPathComponent("events-\(Date().currentTime)") @@ -39,7 +39,7 @@ internal class EventStorageImpl: EventStorage { var readyFilesList: [URL] = [] let directoryContents = try FileManager.default.contentsOfDirectory(atPath: folderURL.absoluteString) for file in directoryContents { - if file.hasSuffix(READYTOSENDEXTENSION) { + if file.hasSuffix(EventStorageImpl.READYTOSENDEXTENSION) { readyFilesList.append(URL(string: file)!) } } @@ -52,7 +52,7 @@ internal class EventStorageImpl: EventStorage { return events } - private func getFolderURL() throws -> String { + private static func getFolderURL() throws -> String { let rootFolderURL = try FileManager.default.url( for: .cachesDirectory, in: .userDomainMask, From e846b2fa86e93327057cc77a58afa61e1eb8615a Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Mon, 8 Apr 2024 13:56:18 +0200 Subject: [PATCH 03/25] fix: initialize URLs in init() --- Sources/Confidence/EventStorage.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index f7da3464..747c9a27 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -12,8 +12,8 @@ internal class EventStorageImpl: EventStorage { static let READYTOSENDEXTENSION = ".ready" let encoder = JSONEncoder() let decoder = JSONDecoder() - var folderURL: URL = URL(string: "")! - var fileURL: URL = URL(string: "")! + let folderURL: URL + var fileURL: URL var currentBatch: [Event] = [] init() throws { From e04cfd32958171ece3d78435faec28d8481e5b36 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Tue, 9 Apr 2024 11:10:37 +0200 Subject: [PATCH 04/25] feat: extend EventStorage API --- Sources/Confidence/EventStorage.swift | 28 +++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 747c9a27..68873f99 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -1,10 +1,12 @@ import Foundation +import os internal protocol EventStorage { func startNewBatch() throws func writeEvent(event: Event) throws - func batchReadyFiles() throws -> [URL] - func eventsFrom(fileURL: URL) throws -> [Event] + func batchReadyFiles() throws -> [String] + func eventsFrom(id: String) throws -> [Event] + func remove(id: String) } internal class EventStorageImpl: EventStorage { @@ -35,23 +37,33 @@ internal class EventStorageImpl: EventStorage { try data.write(to: fileURL, options: .atomic) } - func batchReadyFiles() throws -> [URL] { - var readyFilesList: [URL] = [] + func batchReadyFiles() throws -> [String] { + var readyFilesList: [String] = [] let directoryContents = try FileManager.default.contentsOfDirectory(atPath: folderURL.absoluteString) for file in directoryContents { if file.hasSuffix(EventStorageImpl.READYTOSENDEXTENSION) { - readyFilesList.append(URL(string: file)!) + readyFilesList.append(file) } } return readyFilesList } - func eventsFrom(fileURL: URL) throws -> [Event] { - let data = try Data(contentsOf: fileURL) - let events = try decoder.decode([Event].self, from: data) + func eventsFrom(id: String) throws -> [Event] { + let currentData = try Data(contentsOf: fileURL) + + let events = try decoder.decode([Event].self, from: currentData) return events } + func remove(id: String) { + do { + try FileManager.default.removeItem(atPath: id) + } catch { + Logger(subsystem: "com.confidence.eventstorage", category: "storage").error( + "Error when trying to delete an event batch: \(error)") + } + } + private static func getFolderURL() throws -> String { let rootFolderURL = try FileManager.default.url( for: .cachesDirectory, From 8a1f1f08054baf8934b69b3892aa99d9002f81cc Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Tue, 9 Apr 2024 11:49:10 +0200 Subject: [PATCH 05/25] refactor: add error handling --- Sources/Confidence/EventStorage.swift | 53 +++++++++++++++++++-------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 68873f99..cc7d64e0 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -2,10 +2,10 @@ import Foundation import os internal protocol EventStorage { - func startNewBatch() throws - func writeEvent(event: Event) throws - func batchReadyFiles() throws -> [String] - func eventsFrom(id: String) throws -> [Event] + func startNewBatch() + func writeEvent(event: Event) + func batchReadyIds() -> [String] + func eventsFrom(id: String) -> [Event] func remove(id: String) } @@ -23,23 +23,39 @@ internal class EventStorageImpl: EventStorage { fileURL = folderURL.appendingPathComponent("events-\(Date().currentTime)") } - func startNewBatch() throws { + func startNewBatch() { let urlString = "\(fileURL)"+"\(EventStorageImpl.READYTOSENDEXTENSION)" let newPath = URL(fileURLWithPath: urlString) - try FileManager.default.moveItem(at: fileURL, to: newPath) + do { + try FileManager.default.moveItem(at: fileURL, to: newPath) + } catch { + Logger(subsystem: "com.confidence.eventsender", category: "storage").error( + "Error when trying to start a new batch: \(error)") + } fileURL = folderURL.appendingPathComponent("events-\(Date().currentTime)") currentBatch = [] } - func writeEvent(event: Event) throws { + func writeEvent(event: Event) { currentBatch.append(event) - let data = try encoder.encode(currentBatch) - try data.write(to: fileURL, options: .atomic) + do { + let data = try encoder.encode(currentBatch) + try data.write(to: fileURL, options: .atomic) + } catch { + Logger(subsystem: "com.confidence.eventsender", category: "storage").error( + "Error when trying to write to disk: \(error)") + } } - func batchReadyFiles() throws -> [String] { + func batchReadyIds() -> [String] { var readyFilesList: [String] = [] - let directoryContents = try FileManager.default.contentsOfDirectory(atPath: folderURL.absoluteString) + var directoryContents: [String] = [] + do { + directoryContents = try FileManager.default.contentsOfDirectory(atPath: folderURL.absoluteString) + } catch { + Logger(subsystem: "com.confidence.eventsender", category: "storage").error( + "Error when trying to read contents of directory on disk: \(error)") + } for file in directoryContents { if file.hasSuffix(EventStorageImpl.READYTOSENDEXTENSION) { readyFilesList.append(file) @@ -48,10 +64,15 @@ internal class EventStorageImpl: EventStorage { return readyFilesList } - func eventsFrom(id: String) throws -> [Event] { - let currentData = try Data(contentsOf: fileURL) - - let events = try decoder.decode([Event].self, from: currentData) + func eventsFrom(id: String) -> [Event] { + var events: [Event] = [] + do { + let currentData = try Data(contentsOf: URL(string: id)!) + events = try decoder.decode([Event].self, from: currentData) + } catch { + Logger(subsystem: "com.confidence.eventsender", category: "storage").error( + "Error when trying to get events at path: \(error)") + } return events } @@ -59,7 +80,7 @@ internal class EventStorageImpl: EventStorage { do { try FileManager.default.removeItem(atPath: id) } catch { - Logger(subsystem: "com.confidence.eventstorage", category: "storage").error( + Logger(subsystem: "com.confidence.eventsender", category: "storage").error( "Error when trying to delete an event batch: \(error)") } } From 605cae7a52db53787a59147e11afcb80f95be1fa Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Tue, 9 Apr 2024 14:17:18 +0200 Subject: [PATCH 06/25] fix: handle continuing writing to batch from disk --- Sources/Confidence/EventStorage.swift | 50 +++++++++++++++++++-------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index cc7d64e0..dc38104d 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -10,29 +10,35 @@ internal protocol EventStorage { } internal class EventStorageImpl: EventStorage { - static let DIRECTORY = "events" - static let READYTOSENDEXTENSION = ".ready" - let encoder = JSONEncoder() - let decoder = JSONDecoder() - let folderURL: URL - var fileURL: URL - var currentBatch: [Event] = [] + private static let DIRECTORY = "events" + private static let READYTOSENDEXTENSION = ".ready" + private let encoder = JSONEncoder() + private let decoder = JSONDecoder() + private let currentFolderURL: URL + private var currentFileURL: URL + private var currentBatch: [Event] = [] init() throws { - folderURL = URL(fileURLWithPath: try EventStorageImpl.getFolderURL()) - fileURL = folderURL.appendingPathComponent("events-\(Date().currentTime)") + currentFolderURL = URL(fileURLWithPath: try EventStorageImpl.getFolderURL()) + currentFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") } func startNewBatch() { - let urlString = "\(fileURL)"+"\(EventStorageImpl.READYTOSENDEXTENSION)" + let latestWriteFile = latestWriteFile() + if latestWriteFile != nil { + currentFileURL = latestWriteFile! + currentBatch = eventsFrom(id: latestWriteFile!.absoluteString) + return + } + let urlString = "\(currentFileURL)"+"\(EventStorageImpl.READYTOSENDEXTENSION)" let newPath = URL(fileURLWithPath: urlString) do { - try FileManager.default.moveItem(at: fileURL, to: newPath) + try FileManager.default.moveItem(at: currentFileURL, to: newPath) } catch { Logger(subsystem: "com.confidence.eventsender", category: "storage").error( "Error when trying to start a new batch: \(error)") } - fileURL = folderURL.appendingPathComponent("events-\(Date().currentTime)") + currentFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") currentBatch = [] } @@ -40,7 +46,7 @@ internal class EventStorageImpl: EventStorage { currentBatch.append(event) do { let data = try encoder.encode(currentBatch) - try data.write(to: fileURL, options: .atomic) + try data.write(to: currentFileURL, options: .atomic) } catch { Logger(subsystem: "com.confidence.eventsender", category: "storage").error( "Error when trying to write to disk: \(error)") @@ -51,7 +57,7 @@ internal class EventStorageImpl: EventStorage { var readyFilesList: [String] = [] var directoryContents: [String] = [] do { - directoryContents = try FileManager.default.contentsOfDirectory(atPath: folderURL.absoluteString) + directoryContents = try FileManager.default.contentsOfDirectory(atPath: currentFolderURL.absoluteString) } catch { Logger(subsystem: "com.confidence.eventsender", category: "storage").error( "Error when trying to read contents of directory on disk: \(error)") @@ -100,6 +106,22 @@ internal class EventStorageImpl: EventStorage { } return nestedFolderURL } + + private func latestWriteFile() -> URL? { + var directoryContents: [String] = [] + do { + directoryContents = try FileManager.default.contentsOfDirectory(atPath: EventStorageImpl.getFolderURL()) + } catch { + Logger(subsystem: "com.confidence.eventsender", category: "storage").error( + "Error when trying to read contents of directory on disk: \(error)") + } + for file in directoryContents { + if !file.hasSuffix(EventStorageImpl.READYTOSENDEXTENSION) { + return URL(string: file) + } + } + return nil + } } struct Event: Codable { From 34339067c47e3efb5db3b0daf692e05f534d8e6e Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Tue, 9 Apr 2024 14:48:42 +0200 Subject: [PATCH 07/25] fix: handle continuing writing to batch from disk, remove force unwrapping --- Sources/Confidence/EventStorage.swift | 31 +++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index dc38104d..24b5a97b 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -20,26 +20,25 @@ internal class EventStorageImpl: EventStorage { init() throws { currentFolderURL = URL(fileURLWithPath: try EventStorageImpl.getFolderURL()) - currentFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") + currentFileURL = EventStorageImpl.latestWriteFile() ?? currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") } func startNewBatch() { - let latestWriteFile = latestWriteFile() - if latestWriteFile != nil { - currentFileURL = latestWriteFile! - currentBatch = eventsFrom(id: latestWriteFile!.absoluteString) + guard let latestWriteFile = EventStorageImpl.latestWriteFile() else { + let urlString = "\(currentFileURL)\(EventStorageImpl.READYTOSENDEXTENSION)" + let newPath = URL(fileURLWithPath: urlString) + do { + try FileManager.default.moveItem(at: currentFileURL, to: newPath) + } catch { + Logger(subsystem: "com.confidence.eventsender", category: "storage").error( + "Error when trying to start a new batch: \(error)") + } + currentFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") + currentBatch = [] return } - let urlString = "\(currentFileURL)"+"\(EventStorageImpl.READYTOSENDEXTENSION)" - let newPath = URL(fileURLWithPath: urlString) - do { - try FileManager.default.moveItem(at: currentFileURL, to: newPath) - } catch { - Logger(subsystem: "com.confidence.eventsender", category: "storage").error( - "Error when trying to start a new batch: \(error)") - } - currentFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") - currentBatch = [] + currentFileURL = latestWriteFile + currentBatch = eventsFrom(id: latestWriteFile.absoluteString) } func writeEvent(event: Event) { @@ -107,7 +106,7 @@ internal class EventStorageImpl: EventStorage { return nestedFolderURL } - private func latestWriteFile() -> URL? { + private static func latestWriteFile() -> URL? { var directoryContents: [String] = [] do { directoryContents = try FileManager.default.contentsOfDirectory(atPath: EventStorageImpl.getFolderURL()) From 220bead3f72fdc91ecb35c53caf5ae9999d84e6e Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Tue, 9 Apr 2024 15:09:45 +0200 Subject: [PATCH 08/25] fix: simplify batching --- Sources/Confidence/EventStorage.swift | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 24b5a97b..3fe575b6 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -24,21 +24,17 @@ internal class EventStorageImpl: EventStorage { } func startNewBatch() { - guard let latestWriteFile = EventStorageImpl.latestWriteFile() else { - let urlString = "\(currentFileURL)\(EventStorageImpl.READYTOSENDEXTENSION)" - let newPath = URL(fileURLWithPath: urlString) - do { - try FileManager.default.moveItem(at: currentFileURL, to: newPath) - } catch { - Logger(subsystem: "com.confidence.eventsender", category: "storage").error( - "Error when trying to start a new batch: \(error)") - } - currentFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") - currentBatch = [] - return + let urlString = "\(currentFileURL)\(EventStorageImpl.READYTOSENDEXTENSION)" + let newPath = URL(fileURLWithPath: urlString) + do { + try FileManager.default.moveItem(at: currentFileURL, to: newPath) + } catch { + Logger(subsystem: "com.confidence.eventsender", category: "storage").error( + "Error when trying to start a new batch: \(error)") } - currentFileURL = latestWriteFile - currentBatch = eventsFrom(id: latestWriteFile.absoluteString) + currentFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") + currentBatch = [] + return } func writeEvent(event: Event) { From 8b83092b4b737b5c8278e9354bbb61d174a2546d Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Wed, 10 Apr 2024 11:58:11 +0200 Subject: [PATCH 09/25] refactor: change handling of file creation --- Sources/Confidence/EventStorage.swift | 34 ++++++++++++++++++--------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 3fe575b6..c7951e7c 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -19,8 +19,8 @@ internal class EventStorageImpl: EventStorage { private var currentBatch: [Event] = [] init() throws { - currentFolderURL = URL(fileURLWithPath: try EventStorageImpl.getFolderURL()) - currentFileURL = EventStorageImpl.latestWriteFile() ?? currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") + currentFolderURL = URL(fileURLWithPath: try getFolderURL()) + currentFileURL = getOrCreatelatestWriteFile(inFolder: currentFolderURL) } func startNewBatch() { @@ -86,7 +86,7 @@ internal class EventStorageImpl: EventStorage { } } - private static func getFolderURL() throws -> String { + private func getFolderURL() throws -> String { let rootFolderURL = try FileManager.default.url( for: .cachesDirectory, in: .userDomainMask, @@ -95,27 +95,39 @@ internal class EventStorageImpl: EventStorage { ) var nestedFolderURL: String if #available(iOS 16.0, *) { - nestedFolderURL = rootFolderURL.appending(path: DIRECTORY).absoluteString + nestedFolderURL = rootFolderURL.appending(path: EventStorageImpl.DIRECTORY).absoluteString } else { - nestedFolderURL = rootFolderURL.appendingPathComponent(DIRECTORY, isDirectory: true).absoluteString + nestedFolderURL = rootFolderURL.appendingPathComponent(EventStorageImpl.DIRECTORY, isDirectory: true).absoluteString + } + if !FileManager.default.fileExists(atPath: nestedFolderURL) { + do { + try FileManager.default.createDirectory( + atPath: nestedFolderURL, + withIntermediateDirectories: true, + attributes: nil) + } catch { + print(error.localizedDescription) + } } return nestedFolderURL } - private static func latestWriteFile() -> URL? { + private func getOrCreatelatestWriteFile(inFolder: URL) -> URL { var directoryContents: [String] = [] do { - directoryContents = try FileManager.default.contentsOfDirectory(atPath: EventStorageImpl.getFolderURL()) + directoryContents = try FileManager.default.contentsOfDirectory(atPath: inFolder.absoluteString) } catch { Logger(subsystem: "com.confidence.eventsender", category: "storage").error( - "Error when trying to read contents of directory on disk: \(error)") + "No previous batch file found \(error)") } for file in directoryContents { if !file.hasSuffix(EventStorageImpl.READYTOSENDEXTENSION) { - return URL(string: file) + return URL(string: file)! } } - return nil + let newFileURL = inFolder.appendingPathComponent("events-\(Date().currentTime)") + FileManager.default.createFile(atPath: newFileURL.absoluteString, contents: nil) + return newFileURL } } @@ -131,7 +143,7 @@ struct Event: Codable { extension Date { var currentTime: String { let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .short + dateFormatter.dateStyle = .long return dateFormatter.string(from: self) } } From c13212b6dabfdc793cf9b787777667516dbf5f59 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Wed, 10 Apr 2024 16:01:40 +0200 Subject: [PATCH 10/25] refactor: create a new file if no file left after previous session --- Sources/Confidence/EventStorage.swift | 34 ++++++++++++++++++--------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index c7951e7c..444753b1 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -19,11 +19,19 @@ internal class EventStorageImpl: EventStorage { private var currentBatch: [Event] = [] init() throws { - currentFolderURL = URL(fileURLWithPath: try getFolderURL()) - currentFileURL = getOrCreatelatestWriteFile(inFolder: currentFolderURL) + currentFolderURL = URL(fileURLWithPath: try EventStorageImpl.getFolderURL()) + print("events-\(Date().currentTime)") + let possibleFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") + EventStorageImpl.createNewFile(path: possibleFileURL) + currentFileURL = possibleFileURL } func startNewBatch() { + // + if FileManager.default.fileExists(atPath: currentFileURL.absoluteString) { + print("😍 file exists") + } + // let urlString = "\(currentFileURL)\(EventStorageImpl.READYTOSENDEXTENSION)" let newPath = URL(fileURLWithPath: urlString) do { @@ -33,6 +41,7 @@ internal class EventStorageImpl: EventStorage { "Error when trying to start a new batch: \(error)") } currentFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") + EventStorageImpl.createNewFile(path: currentFileURL) currentBatch = [] return } @@ -86,7 +95,7 @@ internal class EventStorageImpl: EventStorage { } } - private func getFolderURL() throws -> String { + private static func getFolderURL() throws -> String { let rootFolderURL = try FileManager.default.url( for: .cachesDirectory, in: .userDomainMask, @@ -112,22 +121,24 @@ internal class EventStorageImpl: EventStorage { return nestedFolderURL } - private func getOrCreatelatestWriteFile(inFolder: URL) -> URL { + private static func latestWriteFile() throws -> URL? { var directoryContents: [String] = [] do { - directoryContents = try FileManager.default.contentsOfDirectory(atPath: inFolder.absoluteString) + directoryContents = try FileManager.default.contentsOfDirectory(atPath: EventStorageImpl.getFolderURL()) } catch { Logger(subsystem: "com.confidence.eventsender", category: "storage").error( "No previous batch file found \(error)") } for file in directoryContents { if !file.hasSuffix(EventStorageImpl.READYTOSENDEXTENSION) { - return URL(string: file)! + return URL(fileURLWithPath: try EventStorageImpl.getFolderURL()).appendingPathComponent(file) } } - let newFileURL = inFolder.appendingPathComponent("events-\(Date().currentTime)") - FileManager.default.createFile(atPath: newFileURL.absoluteString, contents: nil) - return newFileURL + return nil + } + + private static func createNewFile(path: URL) { + FileManager.default.createFile(atPath: path.absoluteString, contents: nil) } } @@ -143,8 +154,9 @@ struct Event: Codable { extension Date { var currentTime: String { let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .long - return dateFormatter.string(from: self) + dateFormatter.dateStyle = .medium + dateFormatter.dateFormat = "YY-MM-dd-HH-mm-ss" + return dateFormatter.string(from: self).replacingOccurrences(of: "%", with: "") } } From 4635e2840d8b50e5ff3713f801125effd08ea769 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Wed, 10 Apr 2024 17:11:39 +0200 Subject: [PATCH 11/25] test: test EventStorage --- Tests/ConfidenceTests/EventStorageTests.swift | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Tests/ConfidenceTests/EventStorageTests.swift diff --git a/Tests/ConfidenceTests/EventStorageTests.swift b/Tests/ConfidenceTests/EventStorageTests.swift new file mode 100644 index 00000000..8522f030 --- /dev/null +++ b/Tests/ConfidenceTests/EventStorageTests.swift @@ -0,0 +1,45 @@ +import Foundation +import XCTest + +@testable import Confidence + +class EventStorageTest: XCTestCase { + + func testCreateNewBatch() throws { + let eventStorage = try! EventStorageImpl() + eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + eventStorage.startNewBatch() + print("βœ…",eventStorage.batchReadyIds()) + //timestamp isnt in the filename + XCTAssertEqual(eventStorage.batchReadyIds().count, 1) + } + + func testContinueWritingToOldBatch() { + + } + + func testRolloverToNewBatchWhenBatchIsFull() { + + } + + func testGetReadyFilesToSend() { + + } + + func testGetEventsFromFile() { + + } + + func testRemoveFile() { + + } +} + + +//struct Event: Codable { +// let eventDefinition: String +// let eventTime: Date +// // TODO: fix this to be ConfidenceValue +// let payload: [String] +// let context: [String] +//} From c522918c5d246fcd4d58100dc443aad57b7a3caa Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 10 Apr 2024 18:51:59 +0200 Subject: [PATCH 12/25] fix creating file, folder and use file handler, fix appending events to the storage. fix tests --- .../Utils => Confidence}/Backport.swift | 12 +- Sources/Confidence/EventStorage.swift | 178 +++++++++--------- .../Cache/DefaultStorage.swift | 2 +- Tests/ConfidenceTests/EventStorageTests.swift | 12 +- 4 files changed, 99 insertions(+), 105 deletions(-) rename Sources/{ConfidenceProvider/Utils => Confidence}/Backport.swift (90%) diff --git a/Sources/ConfidenceProvider/Utils/Backport.swift b/Sources/Confidence/Backport.swift similarity index 90% rename from Sources/ConfidenceProvider/Utils/Backport.swift rename to Sources/Confidence/Backport.swift index 28fa33e5..69fa6103 100644 --- a/Sources/ConfidenceProvider/Utils/Backport.swift +++ b/Sources/Confidence/Backport.swift @@ -1,10 +1,10 @@ import Foundation -extension URL { +public extension URL { struct Backport { var base: URL - init(base: URL) { + public init(base: URL) { self.base = base } } @@ -14,7 +14,7 @@ extension URL { } } -extension URL.Backport { +public extension URL.Backport { var path: String { if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) { return self.base.path(percentEncoded: false) @@ -36,14 +36,14 @@ extension URL.Backport { } } -extension Date { +public extension Date { struct Backport { } static var backport: Backport.Type { Backport.self } } -extension Date.Backport { +public extension Date.Backport { static var now: Date { if #available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) { return Date.now @@ -60,7 +60,7 @@ extension Date.Backport { } } - static public func toISOString(date: Date) -> String { + static func toISOString(date: Date) -> String { if #available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) { return date.ISO8601Format() } else { diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 444753b1..4eb33a1b 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -2,136 +2,131 @@ import Foundation import os internal protocol EventStorage { - func startNewBatch() - func writeEvent(event: Event) - func batchReadyIds() -> [String] - func eventsFrom(id: String) -> [Event] + func startNewBatch() throws + func writeEvent(event: Event) throws + func batchReadyIds() throws -> [String] + func eventsFrom(id: String) throws -> [Event] func remove(id: String) } internal class EventStorageImpl: EventStorage { - private static let DIRECTORY = "events" - private static let READYTOSENDEXTENSION = ".ready" + private let resolverCacheBundleId = "com.confidence.cache" + private let filePath = "events" + private let READYTOSENDEXTENSION = "READY" private let encoder = JSONEncoder() private let decoder = JSONDecoder() - private let currentFolderURL: URL - private var currentFileURL: URL + private var currentFileUrl: URL? = nil + private var currentFileHandle: FileHandle? = nil private var currentBatch: [Event] = [] init() throws { - currentFolderURL = URL(fileURLWithPath: try EventStorageImpl.getFolderURL()) - print("events-\(Date().currentTime)") - let possibleFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") - EventStorageImpl.createNewFile(path: possibleFileURL) - currentFileURL = possibleFileURL + let folderUrl = try getFolderURL() + try FileManager.default.removeItem(at: folderUrl) + if(!FileManager.default.fileExists(atPath: folderUrl.backport.path)) { + try FileManager.default.createDirectory(at: folderUrl, withIntermediateDirectories: true) + } + + try resetCurrentFile() } - func startNewBatch() { - // - if FileManager.default.fileExists(atPath: currentFileURL.absoluteString) { - print("😍 file exists") + func startNewBatch() throws { + guard let currentFileName = self.currentFileUrl else { + return } - // - let urlString = "\(currentFileURL)\(EventStorageImpl.READYTOSENDEXTENSION)" - let newPath = URL(fileURLWithPath: urlString) - do { - try FileManager.default.moveItem(at: currentFileURL, to: newPath) - } catch { - Logger(subsystem: "com.confidence.eventsender", category: "storage").error( - "Error when trying to start a new batch: \(error)") - } - currentFileURL = currentFolderURL.appendingPathComponent("events-\(Date().currentTime)") - EventStorageImpl.createNewFile(path: currentFileURL) - currentBatch = [] - return + + try FileManager.default.moveItem(at: currentFileName, to: currentFileName.appendingPathExtension(READYTOSENDEXTENSION)) + try resetCurrentFile() } - func writeEvent(event: Event) { - currentBatch.append(event) - do { - let data = try encoder.encode(currentBatch) - try data.write(to: currentFileURL, options: .atomic) - } catch { - Logger(subsystem: "com.confidence.eventsender", category: "storage").error( - "Error when trying to write to disk: \(error)") + func writeEvent(event: Event) throws { + guard let currentFileHandle = currentFileHandle else { + return } - } - - func batchReadyIds() -> [String] { - var readyFilesList: [String] = [] - var directoryContents: [String] = [] - do { - directoryContents = try FileManager.default.contentsOfDirectory(atPath: currentFolderURL.absoluteString) - } catch { - Logger(subsystem: "com.confidence.eventsender", category: "storage").error( - "Error when trying to read contents of directory on disk: \(error)") + let encoder = JSONEncoder() + let serialied = try encoder.encode(event) + let delimiter = "\n".data(using: .utf8) + guard let delimiter else { + return } - for file in directoryContents { - if file.hasSuffix(EventStorageImpl.READYTOSENDEXTENSION) { - readyFilesList.append(file) + try currentFileHandle.write(contentsOf: delimiter) + try currentFileHandle.write(contentsOf: serialied) + } + + private func currentWritingFile() throws -> URL? { + let files = try FileManager.default.contentsOfDirectory(at: getFolderURL(), includingPropertiesForKeys: nil) + for fileUrl in files { + if fileUrl.pathExtension != READYTOSENDEXTENSION { + return fileUrl } } - return readyFilesList + return nil } - - func eventsFrom(id: String) -> [Event] { - var events: [Event] = [] - do { - let currentData = try Data(contentsOf: URL(string: id)!) - events = try decoder.decode([Event].self, from: currentData) - } catch { - Logger(subsystem: "com.confidence.eventsender", category: "storage").error( - "Error when trying to get events at path: \(error)") + + private func resetCurrentFile() throws { + if let currentFile = try currentWritingFile() { + self.currentFileUrl = currentFile + self.currentFileHandle = try FileHandle(forWritingTo: currentFile) + } else { + let fileUrl = try getFolderURL().appendingPathComponent(Date().currentTime) + FileManager.default.createFile(atPath: fileUrl.path, contents: nil) + self.currentFileUrl = fileUrl + self.currentFileHandle = try FileHandle(forWritingTo: fileUrl) } - return events + } + + + func batchReadyIds() throws -> [String]{ + let folderUrl = try FileManager.default.contentsOfDirectory(at: getFolderURL(), includingPropertiesForKeys: nil) + return folderUrl.filter({ url in url.pathExtension == READYTOSENDEXTENSION}).map({ url in url.lastPathComponent }) + } + + func eventsFrom(id: String) throws -> [Event] { + let decoder = JSONDecoder() + let fileUrl = try getFolderURL().appendingPathComponent(id) + let data = try Data(contentsOf: fileUrl) + let dataString = String(data: data, encoding: .utf8) + return try dataString?.components(separatedBy: "\n") + .filter({ events in !events.isEmpty }) + .map({eventString in try decoder.decode(Event.self, from: eventString.data(using: .utf8)!)}) ?? [] } func remove(id: String) { do { - try FileManager.default.removeItem(atPath: id) + let fileUrl = try getFolderURL().appendingPathComponent(id) + try FileManager.default.removeItem(at: fileUrl) } catch { Logger(subsystem: "com.confidence.eventsender", category: "storage").error( "Error when trying to delete an event batch: \(error)") } } - private static func getFolderURL() throws -> String { - let rootFolderURL = try FileManager.default.url( - for: .cachesDirectory, - in: .userDomainMask, - appropriateFor: nil, - create: true - ) - var nestedFolderURL: String - if #available(iOS 16.0, *) { - nestedFolderURL = rootFolderURL.appending(path: EventStorageImpl.DIRECTORY).absoluteString - } else { - nestedFolderURL = rootFolderURL.appendingPathComponent(EventStorageImpl.DIRECTORY, isDirectory: true).absoluteString + private func getFolderURL() throws -> URL { + guard + let applicationSupportUrl: URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) + .last + else { + throw ConfidenceError.cacheError(message: "Could not get URL for application directory") } - if !FileManager.default.fileExists(atPath: nestedFolderURL) { - do { - try FileManager.default.createDirectory( - atPath: nestedFolderURL, - withIntermediateDirectories: true, - attributes: nil) - } catch { - print(error.localizedDescription) - } + + guard let bundleIdentifier = Bundle.main.bundleIdentifier else { + throw ConfidenceError.cacheError(message: "Unable to get bundle identifier") } - return nestedFolderURL + + return applicationSupportUrl.backport.appending( + components: resolverCacheBundleId, "\(bundleIdentifier)", filePath) } - private static func latestWriteFile() throws -> URL? { - var directoryContents: [String] = [] + private func latestWriteFile() throws -> URL? { + var directoryContents: [URL] = [] do { - directoryContents = try FileManager.default.contentsOfDirectory(atPath: EventStorageImpl.getFolderURL()) + directoryContents = try FileManager.default.contentsOfDirectory(at: self.getFolderURL(), includingPropertiesForKeys: nil, options: []) } catch { Logger(subsystem: "com.confidence.eventsender", category: "storage").error( "No previous batch file found \(error)") } - for file in directoryContents { - if !file.hasSuffix(EventStorageImpl.READYTOSENDEXTENSION) { - return URL(fileURLWithPath: try EventStorageImpl.getFolderURL()).appendingPathComponent(file) + for fileUrl in directoryContents { + if fileUrl.pathExtension.lowercased() == READYTOSENDEXTENSION { + return fileUrl } } return nil @@ -159,4 +154,3 @@ extension Date { return dateFormatter.string(from: self).replacingOccurrences(of: "%", with: "") } } - diff --git a/Sources/ConfidenceProvider/Cache/DefaultStorage.swift b/Sources/ConfidenceProvider/Cache/DefaultStorage.swift index ddf5ed4d..2482a41c 100644 --- a/Sources/ConfidenceProvider/Cache/DefaultStorage.swift +++ b/Sources/ConfidenceProvider/Cache/DefaultStorage.swift @@ -84,7 +84,7 @@ public class DefaultStorage: Storage { func getConfigUrl() throws -> URL { guard - let applicationSupportUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) + let applicationSupportUrl: URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) .last else { throw ConfidenceError.cacheError(message: "Could not get URL for application directory") diff --git a/Tests/ConfidenceTests/EventStorageTests.swift b/Tests/ConfidenceTests/EventStorageTests.swift index 8522f030..c856159d 100644 --- a/Tests/ConfidenceTests/EventStorageTests.swift +++ b/Tests/ConfidenceTests/EventStorageTests.swift @@ -6,12 +6,12 @@ import XCTest class EventStorageTest: XCTestCase { func testCreateNewBatch() throws { - let eventStorage = try! EventStorageImpl() - eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) - eventStorage.startNewBatch() - print("βœ…",eventStorage.batchReadyIds()) - //timestamp isnt in the filename - XCTAssertEqual(eventStorage.batchReadyIds().count, 1) + let eventStorage = try EventStorageImpl() + try eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + try eventStorage.startNewBatch() + try XCTAssertEqual(eventStorage.batchReadyIds().count, 1) + let events = try eventStorage.eventsFrom(id: try eventStorage.batchReadyIds()[0]) + try XCTAssertEqual(events[0].eventDefinition, "some event") } func testContinueWritingToOldBatch() { From e2858b0b2c301b850e25a8767f512792e6561b7c Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 10 Apr 2024 19:00:15 +0200 Subject: [PATCH 13/25] always seek to the end of the file to append --- Sources/Confidence/EventStorage.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 4eb33a1b..2aa59774 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -48,6 +48,7 @@ internal class EventStorageImpl: EventStorage { guard let delimiter else { return } + currentFileHandle.seekToEndOfFile() try currentFileHandle.write(contentsOf: delimiter) try currentFileHandle.write(contentsOf: serialied) } From f58ba7f4450c5feedce5008438280b1533c9f857 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 10 Apr 2024 19:01:10 +0200 Subject: [PATCH 14/25] close file handler before moving it to ready --- Sources/Confidence/EventStorage.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 2aa59774..1a96b8d9 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -34,6 +34,7 @@ internal class EventStorageImpl: EventStorage { return } + try currentFileHandle?.close() try FileManager.default.moveItem(at: currentFileName, to: currentFileName.appendingPathExtension(READYTOSENDEXTENSION)) try resetCurrentFile() } From 5bacd29be6af3a21b5a30eb1832df147088a3154 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 10 Apr 2024 19:03:14 +0200 Subject: [PATCH 15/25] move private funcs down --- Sources/Confidence/EventStorage.swift | 44 +++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 1a96b8d9..63e048f8 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -54,28 +54,6 @@ internal class EventStorageImpl: EventStorage { try currentFileHandle.write(contentsOf: serialied) } - private func currentWritingFile() throws -> URL? { - let files = try FileManager.default.contentsOfDirectory(at: getFolderURL(), includingPropertiesForKeys: nil) - for fileUrl in files { - if fileUrl.pathExtension != READYTOSENDEXTENSION { - return fileUrl - } - } - return nil - } - - private func resetCurrentFile() throws { - if let currentFile = try currentWritingFile() { - self.currentFileUrl = currentFile - self.currentFileHandle = try FileHandle(forWritingTo: currentFile) - } else { - let fileUrl = try getFolderURL().appendingPathComponent(Date().currentTime) - FileManager.default.createFile(atPath: fileUrl.path, contents: nil) - self.currentFileUrl = fileUrl - self.currentFileHandle = try FileHandle(forWritingTo: fileUrl) - } - } - func batchReadyIds() throws -> [String]{ let folderUrl = try FileManager.default.contentsOfDirectory(at: getFolderURL(), includingPropertiesForKeys: nil) @@ -102,6 +80,28 @@ internal class EventStorageImpl: EventStorage { } } + private func currentWritingFile() throws -> URL? { + let files = try FileManager.default.contentsOfDirectory(at: getFolderURL(), includingPropertiesForKeys: nil) + for fileUrl in files { + if fileUrl.pathExtension != READYTOSENDEXTENSION { + return fileUrl + } + } + return nil + } + + private func resetCurrentFile() throws { + if let currentFile = try currentWritingFile() { + self.currentFileUrl = currentFile + self.currentFileHandle = try FileHandle(forWritingTo: currentFile) + } else { + let fileUrl = try getFolderURL().appendingPathComponent(Date().currentTime) + FileManager.default.createFile(atPath: fileUrl.path, contents: nil) + self.currentFileUrl = fileUrl + self.currentFileHandle = try FileHandle(forWritingTo: fileUrl) + } + } + private func getFolderURL() throws -> URL { guard let applicationSupportUrl: URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) From c9c45cbbb3898df5a9559a70abd37603f7137756 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 10 Apr 2024 19:10:38 +0200 Subject: [PATCH 16/25] cache folder url --- Sources/Confidence/EventStorage.swift | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 63e048f8..816b7de9 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -10,20 +10,19 @@ internal protocol EventStorage { } internal class EventStorageImpl: EventStorage { - private let resolverCacheBundleId = "com.confidence.cache" - private let filePath = "events" private let READYTOSENDEXTENSION = "READY" private let encoder = JSONEncoder() private let decoder = JSONDecoder() + private var folderURL: URL private var currentFileUrl: URL? = nil private var currentFileHandle: FileHandle? = nil private var currentBatch: [Event] = [] init() throws { - let folderUrl = try getFolderURL() - try FileManager.default.removeItem(at: folderUrl) - if(!FileManager.default.fileExists(atPath: folderUrl.backport.path)) { - try FileManager.default.createDirectory(at: folderUrl, withIntermediateDirectories: true) + self.folderURL = try EventStorageImpl.getFolderURL() + try FileManager.default.removeItem(at: folderURL) + if(!FileManager.default.fileExists(atPath: folderURL.backport.path)) { + try FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true) } try resetCurrentFile() @@ -56,13 +55,13 @@ internal class EventStorageImpl: EventStorage { func batchReadyIds() throws -> [String]{ - let folderUrl = try FileManager.default.contentsOfDirectory(at: getFolderURL(), includingPropertiesForKeys: nil) + let folderUrl = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil) return folderUrl.filter({ url in url.pathExtension == READYTOSENDEXTENSION}).map({ url in url.lastPathComponent }) } func eventsFrom(id: String) throws -> [Event] { let decoder = JSONDecoder() - let fileUrl = try getFolderURL().appendingPathComponent(id) + let fileUrl = folderURL.appendingPathComponent(id) let data = try Data(contentsOf: fileUrl) let dataString = String(data: data, encoding: .utf8) return try dataString?.components(separatedBy: "\n") @@ -72,7 +71,7 @@ internal class EventStorageImpl: EventStorage { func remove(id: String) { do { - let fileUrl = try getFolderURL().appendingPathComponent(id) + let fileUrl = folderURL.appendingPathComponent(id) try FileManager.default.removeItem(at: fileUrl) } catch { Logger(subsystem: "com.confidence.eventsender", category: "storage").error( @@ -81,7 +80,7 @@ internal class EventStorageImpl: EventStorage { } private func currentWritingFile() throws -> URL? { - let files = try FileManager.default.contentsOfDirectory(at: getFolderURL(), includingPropertiesForKeys: nil) + let files = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil) for fileUrl in files { if fileUrl.pathExtension != READYTOSENDEXTENSION { return fileUrl @@ -95,14 +94,14 @@ internal class EventStorageImpl: EventStorage { self.currentFileUrl = currentFile self.currentFileHandle = try FileHandle(forWritingTo: currentFile) } else { - let fileUrl = try getFolderURL().appendingPathComponent(Date().currentTime) + let fileUrl = folderURL.appendingPathComponent(Date().currentTime) FileManager.default.createFile(atPath: fileUrl.path, contents: nil) self.currentFileUrl = fileUrl self.currentFileHandle = try FileHandle(forWritingTo: fileUrl) } } - private func getFolderURL() throws -> URL { + private static func getFolderURL() throws -> URL { guard let applicationSupportUrl: URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) .last @@ -115,13 +114,13 @@ internal class EventStorageImpl: EventStorage { } return applicationSupportUrl.backport.appending( - components: resolverCacheBundleId, "\(bundleIdentifier)", filePath) + components: "com.confidence.cache", "\(bundleIdentifier)", "events") } private func latestWriteFile() throws -> URL? { var directoryContents: [URL] = [] do { - directoryContents = try FileManager.default.contentsOfDirectory(at: self.getFolderURL(), includingPropertiesForKeys: nil, options: []) + directoryContents = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil, options: []) } catch { Logger(subsystem: "com.confidence.eventsender", category: "storage").error( "No previous batch file found \(error)") From c3a15705baeab0d23f698bf78f1a786a8bb4e7d5 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 10 Apr 2024 19:12:32 +0200 Subject: [PATCH 17/25] test appending events --- Tests/ConfidenceTests/EventStorageTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/ConfidenceTests/EventStorageTests.swift b/Tests/ConfidenceTests/EventStorageTests.swift index c856159d..edd010e5 100644 --- a/Tests/ConfidenceTests/EventStorageTests.swift +++ b/Tests/ConfidenceTests/EventStorageTests.swift @@ -8,10 +8,12 @@ class EventStorageTest: XCTestCase { func testCreateNewBatch() throws { let eventStorage = try EventStorageImpl() try eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + try eventStorage.writeEvent(event: Event(eventDefinition: "some event 2", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) try eventStorage.startNewBatch() try XCTAssertEqual(eventStorage.batchReadyIds().count, 1) let events = try eventStorage.eventsFrom(id: try eventStorage.batchReadyIds()[0]) - try XCTAssertEqual(events[0].eventDefinition, "some event") + XCTAssertEqual(events[0].eventDefinition, "some event") + XCTAssertEqual(events[1].eventDefinition, "some event 2") } func testContinueWritingToOldBatch() { From 09eb85b118f3ba48733ee29498e76bfae161ab75 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 10 Apr 2024 19:21:19 +0200 Subject: [PATCH 18/25] tear down test --- Sources/Confidence/EventStorage.swift | 5 ++--- Tests/ConfidenceTests/EventStorageTests.swift | 7 +++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 816b7de9..97d9ba24 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -20,7 +20,6 @@ internal class EventStorageImpl: EventStorage { init() throws { self.folderURL = try EventStorageImpl.getFolderURL() - try FileManager.default.removeItem(at: folderURL) if(!FileManager.default.fileExists(atPath: folderURL.backport.path)) { try FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true) } @@ -94,14 +93,14 @@ internal class EventStorageImpl: EventStorage { self.currentFileUrl = currentFile self.currentFileHandle = try FileHandle(forWritingTo: currentFile) } else { - let fileUrl = folderURL.appendingPathComponent(Date().currentTime) + let fileUrl = folderURL.appendingPathComponent(String(Date().timeIntervalSince1970)) FileManager.default.createFile(atPath: fileUrl.path, contents: nil) self.currentFileUrl = fileUrl self.currentFileHandle = try FileHandle(forWritingTo: fileUrl) } } - private static func getFolderURL() throws -> URL { + internal static func getFolderURL() throws -> URL { guard let applicationSupportUrl: URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) .last diff --git a/Tests/ConfidenceTests/EventStorageTests.swift b/Tests/ConfidenceTests/EventStorageTests.swift index edd010e5..d9e6b034 100644 --- a/Tests/ConfidenceTests/EventStorageTests.swift +++ b/Tests/ConfidenceTests/EventStorageTests.swift @@ -35,6 +35,13 @@ class EventStorageTest: XCTestCase { func testRemoveFile() { } + + override func setUp() { + let folderURL = try! EventStorageImpl.getFolderURL() + if FileManager.default.fileExists(atPath: folderURL.path) { + try! FileManager.default.removeItem(at: folderURL) + } + } } From c05e21a73942b9d54614b98881a5270a0ec32139 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Thu, 11 Apr 2024 13:34:05 +0200 Subject: [PATCH 19/25] fix: update .gitignore --- .gitignore | 3 +- .../xcschemes/Confidence-Package.xcscheme | 123 ------------------ .../xcschemes/Confidence.xcscheme | 66 ---------- 3 files changed, 2 insertions(+), 190 deletions(-) delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme diff --git a/.gitignore b/.gitignore index f9658ead..ad8ce721 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ DerivedData/ .netrc .build .mockingbird -project.json \ No newline at end of file +project.json +.swiftpm \ No newline at end of file diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme deleted file mode 100644 index 4f466f8b..00000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme deleted file mode 100644 index 0e8ddb01..00000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - From 9450360a363aad4c005d47a8d24257e62e7d77cd Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Thu, 11 Apr 2024 16:39:53 +0200 Subject: [PATCH 20/25] refactor: refactor EventStorage --- Sources/Confidence/EventStorage.swift | 115 ++++++++++---------------- 1 file changed, 43 insertions(+), 72 deletions(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 97d9ba24..c718c71a 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -6,79 +6,80 @@ internal protocol EventStorage { func writeEvent(event: Event) throws func batchReadyIds() throws -> [String] func eventsFrom(id: String) throws -> [Event] - func remove(id: String) + func remove(id: String) throws } internal class EventStorageImpl: EventStorage { private let READYTOSENDEXTENSION = "READY" - private let encoder = JSONEncoder() - private let decoder = JSONDecoder() + private let storageQueue = DispatchQueue(label: "com.confidence.events.storage") private var folderURL: URL private var currentFileUrl: URL? = nil private var currentFileHandle: FileHandle? = nil - private var currentBatch: [Event] = [] init() throws { self.folderURL = try EventStorageImpl.getFolderURL() if(!FileManager.default.fileExists(atPath: folderURL.backport.path)) { try FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true) } - try resetCurrentFile() } func startNewBatch() throws { - guard let currentFileName = self.currentFileUrl else { - return + try storageQueue.sync { + guard let currentFileName = self.currentFileUrl else { + return + } + try currentFileHandle?.close() + try FileManager.default.moveItem(at: currentFileName, to: currentFileName.appendingPathExtension(READYTOSENDEXTENSION)) + try resetCurrentFile() } - - try currentFileHandle?.close() - try FileManager.default.moveItem(at: currentFileName, to: currentFileName.appendingPathExtension(READYTOSENDEXTENSION)) - try resetCurrentFile() } func writeEvent(event: Event) throws { - guard let currentFileHandle = currentFileHandle else { - return - } - let encoder = JSONEncoder() - let serialied = try encoder.encode(event) - let delimiter = "\n".data(using: .utf8) - guard let delimiter else { - return + try storageQueue.sync { + guard let currentFileHandle = currentFileHandle else { + return + } + let encoder = JSONEncoder() + let serialied = try encoder.encode(event) + let delimiter = "\n".data(using: .utf8) + guard let delimiter else { + return + } + currentFileHandle.seekToEndOfFile() + try currentFileHandle.write(contentsOf: delimiter) + try currentFileHandle.write(contentsOf: serialied) } - currentFileHandle.seekToEndOfFile() - try currentFileHandle.write(contentsOf: delimiter) - try currentFileHandle.write(contentsOf: serialied) } - func batchReadyIds() throws -> [String]{ - let folderUrl = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil) - return folderUrl.filter({ url in url.pathExtension == READYTOSENDEXTENSION}).map({ url in url.lastPathComponent }) + func batchReadyIds() throws -> [String] { + try storageQueue.sync { + let fileUrls = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil) + return fileUrls.filter({ url in url.pathExtension == READYTOSENDEXTENSION }).map({ url in url.lastPathComponent }) + } } func eventsFrom(id: String) throws -> [Event] { - let decoder = JSONDecoder() - let fileUrl = folderURL.appendingPathComponent(id) - let data = try Data(contentsOf: fileUrl) - let dataString = String(data: data, encoding: .utf8) - return try dataString?.components(separatedBy: "\n") - .filter({ events in !events.isEmpty }) - .map({eventString in try decoder.decode(Event.self, from: eventString.data(using: .utf8)!)}) ?? [] + try storageQueue.sync { + let decoder = JSONDecoder() + let fileUrl = folderURL.appendingPathComponent(id) + let data = try Data(contentsOf: fileUrl) + let dataString = String(data: data, encoding: .utf8) + return try dataString?.components(separatedBy: "\n") + .filter({ events in !events.isEmpty }) + .map({ eventString in try decoder.decode(Event.self, from: eventString.data(using: .utf8)!)}) ?? [] + } } - func remove(id: String) { - do { - let fileUrl = folderURL.appendingPathComponent(id) - try FileManager.default.removeItem(at: fileUrl) - } catch { - Logger(subsystem: "com.confidence.eventsender", category: "storage").error( - "Error when trying to delete an event batch: \(error)") + func remove(id: String) throws { + try storageQueue.sync { + let fileUrl = folderURL.appendingPathComponent(id) + try FileManager.default.removeItem(at: fileUrl) } } - private func currentWritingFile() throws -> URL? { + private func getLastWritingFile() throws -> URL? { let files = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil) for fileUrl in files { if fileUrl.pathExtension != READYTOSENDEXTENSION { @@ -89,7 +90,7 @@ internal class EventStorageImpl: EventStorage { } private func resetCurrentFile() throws { - if let currentFile = try currentWritingFile() { + if let currentFile = try getLastWritingFile() { self.currentFileUrl = currentFile self.currentFileHandle = try FileHandle(forWritingTo: currentFile) } else { @@ -102,7 +103,7 @@ internal class EventStorageImpl: EventStorage { internal static func getFolderURL() throws -> URL { guard - let applicationSupportUrl: URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) + let applicationSupportUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) .last else { throw ConfidenceError.cacheError(message: "Could not get URL for application directory") @@ -115,26 +116,6 @@ internal class EventStorageImpl: EventStorage { return applicationSupportUrl.backport.appending( components: "com.confidence.cache", "\(bundleIdentifier)", "events") } - - private func latestWriteFile() throws -> URL? { - var directoryContents: [URL] = [] - do { - directoryContents = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil, options: []) - } catch { - Logger(subsystem: "com.confidence.eventsender", category: "storage").error( - "No previous batch file found \(error)") - } - for fileUrl in directoryContents { - if fileUrl.pathExtension.lowercased() == READYTOSENDEXTENSION { - return fileUrl - } - } - return nil - } - - private static func createNewFile(path: URL) { - FileManager.default.createFile(atPath: path.absoluteString, contents: nil) - } } struct Event: Codable { @@ -144,13 +125,3 @@ struct Event: Codable { let payload: [String] let context: [String] } - - -extension Date { - var currentTime: String { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .medium - dateFormatter.dateFormat = "YY-MM-dd-HH-mm-ss" - return dateFormatter.string(from: self).replacingOccurrences(of: "%", with: "") - } -} From 8ea8cb4ceb846a64e33dcdddd26abd5658404e06 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Thu, 11 Apr 2024 16:40:11 +0200 Subject: [PATCH 21/25] test: test EventStorage --- Tests/ConfidenceTests/EventStorageTests.swift | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/Tests/ConfidenceTests/EventStorageTests.swift b/Tests/ConfidenceTests/EventStorageTests.swift index d9e6b034..dd0f3a5e 100644 --- a/Tests/ConfidenceTests/EventStorageTests.swift +++ b/Tests/ConfidenceTests/EventStorageTests.swift @@ -5,6 +5,13 @@ import XCTest class EventStorageTest: XCTestCase { + override func setUp() async throws { + let folderURL = try! EventStorageImpl.getFolderURL() + if FileManager.default.fileExists(atPath: folderURL.path) { + try! FileManager.default.removeItem(at: folderURL) + } + } + func testCreateNewBatch() throws { let eventStorage = try EventStorageImpl() try eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) @@ -16,39 +23,25 @@ class EventStorageTest: XCTestCase { XCTAssertEqual(events[1].eventDefinition, "some event 2") } - func testContinueWritingToOldBatch() { - - } - - func testRolloverToNewBatchWhenBatchIsFull() { - - } - - func testGetReadyFilesToSend() { - - } - - func testGetEventsFromFile() { - - } - - func testRemoveFile() { - + func testContinueWritingToOldBatch() throws { + let eventStorage = try EventStorageImpl() + try eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + // user stops using app, new session after this + let eventStorageNew = try EventStorageImpl() + try eventStorageNew.writeEvent(event: Event(eventDefinition: "some event 2", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + try eventStorageNew.startNewBatch() + try XCTAssertEqual(eventStorageNew.batchReadyIds().count, 1) + let events = try eventStorageNew.eventsFrom(id: try eventStorageNew.batchReadyIds()[0]) + XCTAssertEqual(events[0].eventDefinition, "some event") + XCTAssertEqual(events[1].eventDefinition, "some event 2") } - override func setUp() { - let folderURL = try! EventStorageImpl.getFolderURL() - if FileManager.default.fileExists(atPath: folderURL.path) { - try! FileManager.default.removeItem(at: folderURL) - } + func testRemoveFile() throws { + let eventStorage = try EventStorageImpl() + try eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + try eventStorage.writeEvent(event: Event(eventDefinition: "some event 2", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + try eventStorage.startNewBatch() + try eventStorage.remove(id: eventStorage.batchReadyIds()[0]) + try XCTAssertEqual(eventStorage.batchReadyIds().count, 0) } } - - -//struct Event: Codable { -// let eventDefinition: String -// let eventTime: Date -// // TODO: fix this to be ConfidenceValue -// let payload: [String] -// let context: [String] -//} From ff96d259a771cf4bcfa5fd8eecc4874fb65f771e Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Thu, 11 Apr 2024 16:46:33 +0200 Subject: [PATCH 22/25] fix: unalignment with main --- .gitignore | 3 +- .../xcschemes/Confidence-Package.xcscheme | 123 ++++++++++++++++++ .../xcschemes/Confidence.xcscheme | 66 ++++++++++ 3 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme diff --git a/.gitignore b/.gitignore index ad8ce721..f9658ead 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,4 @@ DerivedData/ .netrc .build .mockingbird -project.json -.swiftpm \ No newline at end of file +project.json \ No newline at end of file diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme new file mode 100644 index 00000000..4f466f8b --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence-Package.xcscheme @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme new file mode 100644 index 00000000..0e8ddb01 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Confidence.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From c94bf0c3139de71f3b2d609941ee4a02206824ba Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Fri, 12 Apr 2024 13:35:02 +0200 Subject: [PATCH 23/25] fix: fix lint issues --- .../ConfidenceDemoTests.swift | 10 ---------- .../ConfidenceDemoUITests.swift | 5 ----- Sources/Confidence/EventStorage.swift | 14 +++++++------- Tests/ConfidenceTests/EventStorageTests.swift | 1 - 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/ConfidenceDemoApp/ConfidenceDemoAppTests/ConfidenceDemoTests.swift b/ConfidenceDemoApp/ConfidenceDemoAppTests/ConfidenceDemoTests.swift index 9da9608e..3f155efe 100644 --- a/ConfidenceDemoApp/ConfidenceDemoAppTests/ConfidenceDemoTests.swift +++ b/ConfidenceDemoApp/ConfidenceDemoAppTests/ConfidenceDemoTests.swift @@ -2,16 +2,6 @@ import XCTest @testable import ConfidenceDemoApp final class ConfidenceDemoTests: XCTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - try super.tearDownWithError() - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - func testExample() throws { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. diff --git a/ConfidenceDemoApp/ConfidenceDemoAppUITests/ConfidenceDemoUITests.swift b/ConfidenceDemoApp/ConfidenceDemoAppUITests/ConfidenceDemoUITests.swift index 1c1d8c7a..59ec3be2 100644 --- a/ConfidenceDemoApp/ConfidenceDemoAppUITests/ConfidenceDemoUITests.swift +++ b/ConfidenceDemoApp/ConfidenceDemoAppUITests/ConfidenceDemoUITests.swift @@ -12,11 +12,6 @@ final class ConfidenceDemoUITests: XCTestCase { // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } - override func tearDownWithError() throws { - try super.tearDownWithError() - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - func testExample() throws { // UI tests must launch the application that they test. let app = XCUIApplication() diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index c718c71a..8b4371aa 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -13,12 +13,12 @@ internal class EventStorageImpl: EventStorage { private let READYTOSENDEXTENSION = "READY" private let storageQueue = DispatchQueue(label: "com.confidence.events.storage") private var folderURL: URL - private var currentFileUrl: URL? = nil - private var currentFileHandle: FileHandle? = nil + private var currentFileUrl: URL? + private var currentFileHandle: FileHandle? init() throws { self.folderURL = try EventStorageImpl.getFolderURL() - if(!FileManager.default.fileExists(atPath: folderURL.backport.path)) { + if !FileManager.default.fileExists(atPath: folderURL.backport.path) { try FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true) } try resetCurrentFile() @@ -34,7 +34,7 @@ internal class EventStorageImpl: EventStorage { try resetCurrentFile() } } - + func writeEvent(event: Event) throws { try storageQueue.sync { guard let currentFileHandle = currentFileHandle else { @@ -56,10 +56,10 @@ internal class EventStorageImpl: EventStorage { func batchReadyIds() throws -> [String] { try storageQueue.sync { let fileUrls = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil) - return fileUrls.filter({ url in url.pathExtension == READYTOSENDEXTENSION }).map({ url in url.lastPathComponent }) + return fileUrls.filter({ url in url.pathExtension == READYTOSENDEXTENSION }).map({ url in url.lastPathComponent }) } } - + func eventsFrom(id: String) throws -> [Event] { try storageQueue.sync { let decoder = JSONDecoder() @@ -68,7 +68,7 @@ internal class EventStorageImpl: EventStorage { let dataString = String(data: data, encoding: .utf8) return try dataString?.components(separatedBy: "\n") .filter({ events in !events.isEmpty }) - .map({ eventString in try decoder.decode(Event.self, from: eventString.data(using: .utf8)!)}) ?? [] + .map({ eventString in try decoder.decode(Event.self, from: eventString.data(using: .utf8)!) }) ?? [] } } diff --git a/Tests/ConfidenceTests/EventStorageTests.swift b/Tests/ConfidenceTests/EventStorageTests.swift index dd0f3a5e..e9bc77e3 100644 --- a/Tests/ConfidenceTests/EventStorageTests.swift +++ b/Tests/ConfidenceTests/EventStorageTests.swift @@ -4,7 +4,6 @@ import XCTest @testable import Confidence class EventStorageTest: XCTestCase { - override func setUp() async throws { let folderURL = try! EventStorageImpl.getFolderURL() if FileManager.default.fileExists(atPath: folderURL.path) { From 2ce9c7711d010db09718674f9cf3cc3de43eb165 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Mon, 15 Apr 2024 11:51:34 +0200 Subject: [PATCH 24/25] fix: align links and add comments --- Sources/Confidence/EventStorage.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 8b4371aa..7e1ef3e6 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -90,10 +90,12 @@ internal class EventStorageImpl: EventStorage { } private func resetCurrentFile() throws { + // Handling already existing file from previous session if let currentFile = try getLastWritingFile() { self.currentFileUrl = currentFile self.currentFileHandle = try FileHandle(forWritingTo: currentFile) } else { + // Create a brand new file let fileUrl = folderURL.appendingPathComponent(String(Date().timeIntervalSince1970)) FileManager.default.createFile(atPath: fileUrl.path, contents: nil) self.currentFileUrl = fileUrl @@ -114,7 +116,7 @@ internal class EventStorageImpl: EventStorage { } return applicationSupportUrl.backport.appending( - components: "com.confidence.cache", "\(bundleIdentifier)", "events") + components: "com.confidence.events.storage", "\(bundleIdentifier)", "events") } } From c3582d3c48346faa937a530f01e414ed55f1dec6 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Mon, 15 Apr 2024 14:11:40 +0200 Subject: [PATCH 25/25] fix: align with main --- Sources/Confidence/EventSenderEngine.swift | 8 +------- Sources/Confidence/EventSenderStorage.swift | 8 -------- Sources/Confidence/EventStorage.swift | 9 +++------ Tests/ConfidenceTests/EventStorageTests.swift | 20 +++++++++---------- 4 files changed, 14 insertions(+), 31 deletions(-) diff --git a/Sources/Confidence/EventSenderEngine.swift b/Sources/Confidence/EventSenderEngine.swift index 3cebf628..8e975642 100644 --- a/Sources/Confidence/EventSenderEngine.swift +++ b/Sources/Confidence/EventSenderEngine.swift @@ -5,12 +5,6 @@ protocol EventsUploader { func upload(request: [Event]) async -> Bool } -struct Event: Encodable, Equatable { - let name: String - let payload: [String: ConfidenceValue] - let eventTime: Date -} - protocol FlushPolicy { func reset() func hit(event: Event) @@ -72,7 +66,7 @@ final class EventSenderEngineImpl: EventSenderEngine { do { guard let self = self else { return } try self.storage.startNewBatch() - let ids = storage.batchReadyIds() + let ids = try storage.batchReadyIds() for id in ids { let events = try self.storage.eventsFrom(id: id) let shouldCleanup = await self.uploader.upload(request: events) diff --git a/Sources/Confidence/EventSenderStorage.swift b/Sources/Confidence/EventSenderStorage.swift index d25333bf..e0b07357 100644 --- a/Sources/Confidence/EventSenderStorage.swift +++ b/Sources/Confidence/EventSenderStorage.swift @@ -5,11 +5,3 @@ struct EventBatchRequest: Encodable { let sendTime: Date let events: [Event] } - -internal protocol EventStorage { - func startNewBatch() throws - func writeEvent(event: Event) throws - func batchReadyIds() -> [String] - func eventsFrom(id: String) throws -> [Event] - func remove(id: String) throws -} diff --git a/Sources/Confidence/EventStorage.swift b/Sources/Confidence/EventStorage.swift index 7e1ef3e6..02bf7efa 100644 --- a/Sources/Confidence/EventStorage.swift +++ b/Sources/Confidence/EventStorage.swift @@ -95,7 +95,6 @@ internal class EventStorageImpl: EventStorage { self.currentFileUrl = currentFile self.currentFileHandle = try FileHandle(forWritingTo: currentFile) } else { - // Create a brand new file let fileUrl = folderURL.appendingPathComponent(String(Date().timeIntervalSince1970)) FileManager.default.createFile(atPath: fileUrl.path, contents: nil) self.currentFileUrl = fileUrl @@ -120,10 +119,8 @@ internal class EventStorageImpl: EventStorage { } } -struct Event: Codable { - let eventDefinition: String +struct Event: Encodable, Equatable, Decodable { + let name: String + let payload: [String: ConfidenceValue] let eventTime: Date - // TODO: fix this to be ConfidenceValue - let payload: [String] - let context: [String] } diff --git a/Tests/ConfidenceTests/EventStorageTests.swift b/Tests/ConfidenceTests/EventStorageTests.swift index e9bc77e3..8a07a0e9 100644 --- a/Tests/ConfidenceTests/EventStorageTests.swift +++ b/Tests/ConfidenceTests/EventStorageTests.swift @@ -13,32 +13,32 @@ class EventStorageTest: XCTestCase { func testCreateNewBatch() throws { let eventStorage = try EventStorageImpl() - try eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) - try eventStorage.writeEvent(event: Event(eventDefinition: "some event 2", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + try eventStorage.writeEvent(event: Event(name: "some event", payload: ["pants": ConfidenceValue(string: "green")], eventTime: Date().self)) + try eventStorage.writeEvent(event: Event(name: "some event 2", payload: ["pants": ConfidenceValue(string: "red")], eventTime: Date().self)) try eventStorage.startNewBatch() try XCTAssertEqual(eventStorage.batchReadyIds().count, 1) let events = try eventStorage.eventsFrom(id: try eventStorage.batchReadyIds()[0]) - XCTAssertEqual(events[0].eventDefinition, "some event") - XCTAssertEqual(events[1].eventDefinition, "some event 2") + XCTAssertEqual(events[0].name, "some event") + XCTAssertEqual(events[1].name, "some event 2") } func testContinueWritingToOldBatch() throws { let eventStorage = try EventStorageImpl() - try eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + try eventStorage.writeEvent(event: Event(name: "some event", payload: ["pants": ConfidenceValue(string: "green")], eventTime: Date().self)) // user stops using app, new session after this let eventStorageNew = try EventStorageImpl() - try eventStorageNew.writeEvent(event: Event(eventDefinition: "some event 2", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + try eventStorageNew.writeEvent(event: Event(name: "some event 2", payload: ["pants": ConfidenceValue(string: "red")], eventTime: Date().self)) try eventStorageNew.startNewBatch() try XCTAssertEqual(eventStorageNew.batchReadyIds().count, 1) let events = try eventStorageNew.eventsFrom(id: try eventStorageNew.batchReadyIds()[0]) - XCTAssertEqual(events[0].eventDefinition, "some event") - XCTAssertEqual(events[1].eventDefinition, "some event 2") + XCTAssertEqual(events[0].name, "some event") + XCTAssertEqual(events[1].name, "some event 2") } func testRemoveFile() throws { let eventStorage = try EventStorageImpl() - try eventStorage.writeEvent(event: Event(eventDefinition: "some event", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) - try eventStorage.writeEvent(event: Event(eventDefinition: "some event 2", eventTime: Date().self, payload: ["pants"], context: ["pants context"])) + try eventStorage.writeEvent(event: Event(name: "some event", payload: ["pants": ConfidenceValue(string: "green")], eventTime: Date().self)) + try eventStorage.writeEvent(event: Event(name: "some event 2", payload: ["pants": ConfidenceValue(string: "red")], eventTime: Date().self)) try eventStorage.startNewBatch() try eventStorage.remove(id: eventStorage.batchReadyIds()[0]) try XCTAssertEqual(eventStorage.batchReadyIds().count, 0)