-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add EventStorage Co-authored-by: Nicklas Lundin <[email protected]> Co-authored-by: vahid torkaman <[email protected]>
- Loading branch information
1 parent
b223804
commit fdc7543
Showing
8 changed files
with
180 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import Foundation | ||
import os | ||
|
||
internal protocol EventStorage { | ||
func startNewBatch() throws | ||
func writeEvent(event: Event) throws | ||
func batchReadyIds() throws -> [String] | ||
func eventsFrom(id: String) throws -> [Event] | ||
func remove(id: String) throws | ||
} | ||
|
||
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? | ||
private var currentFileHandle: FileHandle? | ||
|
||
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 { | ||
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() | ||
} | ||
} | ||
|
||
func writeEvent(event: Event) throws { | ||
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) | ||
} | ||
} | ||
|
||
|
||
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 }) | ||
Check warning on line 59 in Sources/Confidence/EventStorage.swift GitHub Actions / SwiftLint
Check warning on line 59 in Sources/Confidence/EventStorage.swift GitHub Actions / SwiftLint
|
||
} | ||
} | ||
|
||
func eventsFrom(id: String) throws -> [Event] { | ||
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") | ||
Check warning on line 69 in Sources/Confidence/EventStorage.swift GitHub Actions / SwiftLint
|
||
.filter({ events in !events.isEmpty }) | ||
.map({ eventString in try decoder.decode(Event.self, from: eventString.data(using: .utf8)!) }) ?? [] | ||
} | ||
} | ||
|
||
func remove(id: String) throws { | ||
try storageQueue.sync { | ||
let fileUrl = folderURL.appendingPathComponent(id) | ||
try FileManager.default.removeItem(at: fileUrl) | ||
} | ||
} | ||
|
||
private func getLastWritingFile() throws -> URL? { | ||
let files = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil) | ||
for fileUrl in files { | ||
if fileUrl.pathExtension != READYTOSENDEXTENSION { | ||
return fileUrl | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
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 { | ||
let fileUrl = folderURL.appendingPathComponent(String(Date().timeIntervalSince1970)) | ||
FileManager.default.createFile(atPath: fileUrl.path, contents: nil) | ||
self.currentFileUrl = fileUrl | ||
self.currentFileHandle = try FileHandle(forWritingTo: fileUrl) | ||
} | ||
} | ||
|
||
internal static func getFolderURL() throws -> URL { | ||
guard | ||
let applicationSupportUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) | ||
.last | ||
else { | ||
throw ConfidenceError.cacheError(message: "Could not get URL for application directory") | ||
} | ||
|
||
guard let bundleIdentifier = Bundle.main.bundleIdentifier else { | ||
throw ConfidenceError.cacheError(message: "Unable to get bundle identifier") | ||
} | ||
|
||
return applicationSupportUrl.backport.appending( | ||
components: "com.confidence.events.storage", "\(bundleIdentifier)", "events") | ||
} | ||
} | ||
|
||
struct Event: Encodable, Equatable, Decodable { | ||
let name: String | ||
let payload: [String: ConfidenceValue] | ||
let eventTime: Date | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import Foundation | ||
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) { | ||
try! FileManager.default.removeItem(at: folderURL) | ||
} | ||
} | ||
|
||
func testCreateNewBatch() throws { | ||
let eventStorage = try EventStorageImpl() | ||
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].name, "some event") | ||
XCTAssertEqual(events[1].name, "some event 2") | ||
} | ||
|
||
func testContinueWritingToOldBatch() throws { | ||
let eventStorage = try EventStorageImpl() | ||
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(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].name, "some event") | ||
XCTAssertEqual(events[1].name, "some event 2") | ||
} | ||
|
||
func testRemoveFile() throws { | ||
let eventStorage = try EventStorageImpl() | ||
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) | ||
} | ||
} |