Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions Sources/Customization/DefaultEventDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,8 @@ public enum DataStoreType {

open class DefaultEventDispatcher: BackgroundingCallbacks, OPTEventDispatcher {

#if os(tvOS)
static let sharedInstance =
DefaultEventDispatcher(backingStore: .memory)
#else
static let sharedInstance =
static let sharedInstance =
DefaultEventDispatcher()
#endif

// timer-interval for batching (0 = no batching, negative = use default)
var timerInterval: TimeInterval
Expand Down
33 changes: 29 additions & 4 deletions Sources/Implementation/Datastore/DataStoreFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,46 @@ public class DataStoreFile<T>: OPTDataStore where T: Codable {
self.async = async
dataStoreName = storeName
lock = DispatchQueue(label: storeName)
if let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
#if os(tvOS)
let directory = FileManager.SearchPathDirectory.cachesDirectory
#else
let directory = FileManager.SearchPathDirectory.documentDirectory
#endif
if let url = FileManager.default.urls(for: directory, in: .userDomainMask).first {
self.url = url.appendingPathComponent(storeName, isDirectory: false)
} else {
self.url = URL(fileURLWithPath: storeName)
}

}

func isArray() -> Bool {
let t = "\(type(of: T.self))"
return t.hasPrefix("Array") || t.hasPrefix("Swift.Array") || t.hasPrefix("__C.NSArray") || t.hasPrefix("NSArray")
}
public func getItem(forKey: String) -> Any? {
var returnItem: T?

lock.sync {
do {

if !FileManager.default.fileExists(atPath: self.url.path) {
return
}

let contents = try Data(contentsOf: self.url)

if type(of: T.self) == type(of: Data.self) {
returnItem = contents as? T
} else {
let item = try JSONDecoder().decode(T.self, from: contents)
returnItem = item
if isArray() {
let item = try JSONDecoder().decode(T.self, from: contents)
returnItem = item
}
else {
let item = try JSONDecoder().decode([T].self, from: contents)
returnItem = item.first
}
}
} catch let e as NSError {
if e.code != 260 {
Expand Down Expand Up @@ -78,9 +100,12 @@ public class DataStoreFile<T>: OPTDataStore where T: Codable {
// don't bother to convert... otherwise, do
if let value = value as? Data {
data = value
} else {
} else if (value as? NSArray) != nil {
data = try JSONEncoder().encode(value)
}
else {
data = try JSONEncoder().encode([value])
}
if let data = data {
try data.write(to: self.url, options: .atomic)
}
Expand Down
25 changes: 13 additions & 12 deletions Sources/Implementation/Datastore/DataStoreMemory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ public class DataStoreMemory<T>: BackgroundingCallbacks, OPTDataStore where T: C
let lock: DispatchQueue
var data: T?
var backupDataStore: OPTDataStore
public enum BackingStore { case UserDefaults, File }
lazy var logger: OPTLogger? = OPTLoggerFactory.getLogger()

init(storeName: String, backupStore: OPTDataStore = DataStoreUserDefaults()) {
init(storeName: String, backupStore:BackingStore = .File) {
dataStoreName = storeName
lock = DispatchQueue(label: storeName)
backupDataStore = backupStore
switch backupStore {
case .File:
self.backupDataStore = DataStoreFile<T>(storeName: storeName, async: false)
case .UserDefaults:
self.backupDataStore = DataStoreUserDefaults()
}
load(forKey: dataStoreName)
subscribe()
}
Expand All @@ -48,13 +54,8 @@ public class DataStoreMemory<T>: BackgroundingCallbacks, OPTDataStore where T: C

public func load(forKey: String) {
lock.sync {
do {
if let contents = backupDataStore.getItem(forKey: dataStoreName) as? Data {
let item = try JSONDecoder().decode(T.self, from: contents)
self.data = item
}
} catch let error {
self.logger?.e(error.localizedDescription)
if let contents = backupDataStore.getItem(forKey: dataStoreName) as? T {
self.data = contents
}
}
}
Expand Down Expand Up @@ -82,12 +83,12 @@ public class DataStoreMemory<T>: BackgroundingCallbacks, OPTDataStore where T: C
}

@objc func applicationDidEnterBackground() {
if let data = data {
save(forKey: dataStoreName, value: data as Any)
if let data = self.data {
self.save(forKey: dataStoreName, value: data as Any)
}
}

@objc func applicationDidBecomeActive() {
load(forKey: dataStoreName)
self.load(forKey: dataStoreName)
}
}
6 changes: 0 additions & 6 deletions Sources/Implementation/DefaultDatafileHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,15 +260,9 @@ class DefaultDatafileHandler: OPTDatafileHandler {
if let cache = datafileCache[sdkKey] {
return cache
} else {
#if os(tvOS)
let store = DataStoreUserDefaults()
datafileCache[sdkKey] = store
return store
#else
let store = DataStoreFile<Data>(storeName: sdkKey)
datafileCache[sdkKey] = store
return store
#endif
}
}

Expand Down
99 changes: 91 additions & 8 deletions Tests/OptimizelyTests-Common/DataStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,38 +57,119 @@ class DataStoreTests: XCTestCase {

XCTAssert(v2 == value)
}

func testUserDefaults2() {
let ds = DataStoreUserDefaults()
let data = "{}".data(using: .utf8)

ds.saveItem(forKey: "item", value: [data])

var item = ds.getItem(forKey: "item") as? [Data]

XCTAssertNotNil(item)

ds.removeItem(forKey: "item")

item = ds.getItem(forKey: "item") as? [Data]

XCTAssertNil(item)
}

func testBackgroundSave() {
let datastore = DataStoreMemory<String>(storeName: "testingBackgroundSave")

datastore.saveItem(forKey: "testString1", value: "value")
let datastore = DataStoreMemory<[String]>(storeName: "testBackgroundSave")

let key = "testBackgroundSave"
datastore.saveItem(forKey: key, value: ["value"])
print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")

datastore.applicationDidEnterBackground()
datastore.saveItem(forKey: key, value:["v"])
print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")

datastore.applicationDidBecomeActive()

print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")
XCTAssertNotNil(datastore.data)

datastore.load(forKey: key)

print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")
XCTAssertEqual(datastore.data, ["value"])

datastore.removeItem(forKey: key)
}

func testBackgroundSaveUserDefaults() {
let datastore = DataStoreMemory<String>(storeName: "testBackgroundSaveUserDefaults",backupStore: DataStoreMemory.BackingStore.UserDefaults)

let key = "testBackgroundSaveUserDefaults"
datastore.saveItem(forKey: key, value: "value")
print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")

datastore.applicationDidEnterBackground()

datastore.saveItem(forKey: key, value:"v")
print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")

datastore.applicationDidBecomeActive()

print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")
XCTAssertNotNil(datastore.data)

datastore.save(forKey: "testString1", value: 100)

datastore.load(forKey: "testingBackgroundSave")
datastore.load(forKey: key)

print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")
XCTAssertEqual(datastore.data, "value")

datastore.removeItem(forKey: key)
}

func testFileStore() {
// simple file store test

let datastore = DataStoreFile<[String]>(storeName: "testingDataStoreFile")
let datastore = DataStoreFile<[String]>(storeName: "testFileStore")

datastore.saveItem(forKey: "testString", value: ["value"])

let vj = datastore.getItem(forKey: "testString") as! [String]

XCTAssert(vj.first == "value")

datastore.removeItem(forKey: "testString")

}

func testFileStoreString() {
let datastore = DataStoreFile<String>(storeName: "testFileStoreString")

let key = "testFileStoreString"
datastore.saveItem(forKey: key, value: "value")
print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")

print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")
let item = datastore.getItem(forKey:key) as? String

XCTAssertEqual(item, "value")

datastore.removeItem(forKey: key)
}

func testFileStoreInt() {
let datastore = DataStoreFile<Int>(storeName: "testFileStoreInt")

let key = "testFileStoreInt"
datastore.saveItem(forKey: key, value: 5)
print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")

print("[DataStoreTest] \(String(describing: datastore.getItem(forKey: key)))")
let item = datastore.getItem(forKey:key) as? Int

XCTAssertEqual(item, 5)

datastore.removeItem(forKey: key)
}



func testUserDefaults() {
// simple user defaults test

Expand All @@ -100,6 +181,8 @@ class DataStoreTests: XCTestCase {

XCTAssert(value == "value")

datastore.removeItem(forKey: "testString")

}

func testUserDefaultsTooBig() {
Expand Down
8 changes: 5 additions & 3 deletions Tests/OptimizelyTests-Common/DatafileHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -524,18 +524,20 @@ class DatafileHandlerTests: XCTestCase {
let datafileData = datafileString.data(using: .utf8)!

#if os(tvOS)
UserDefaults.standard.set(datafileData, forKey: testSDKKey)
UserDefaults.standard.synchronize()
var url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
#else
var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
#endif
url = url.appendingPathComponent(testSDKKey, isDirectory: false)
try! datafileData.write(to: url, options: .atomic)
#endif

// verify that a new datafileHandler can read an existing datafile cache

let datafileFromCache = DefaultDatafileHandler().loadSavedDatafile(sdkKey: testSDKKey)
XCTAssert(datafileFromCache == datafileData, "failed to support old datafile cached data format")

let project = try! JSONDecoder().decode(Project.self, from: datafileFromCache!)
XCTAssert(project.revision == "241")
}

}
19 changes: 11 additions & 8 deletions Tests/OptimizelyTests-Common/EventDispatcherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ class EventDispatcherTests: XCTestCase {

override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
if let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
#if os(tvOS)
let directory = FileManager.SearchPathDirectory.cachesDirectory
#else
let directory = FileManager.SearchPathDirectory.documentDirectory
#endif

if let url = FileManager.default.urls(for: directory, in: .userDomainMask).first {
if (!FileManager.default.fileExists(atPath: url.path)) {
do {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
Expand Down Expand Up @@ -293,20 +299,17 @@ class EventDispatcherTests: XCTestCase {
let saveFormat = try! JSONEncoder().encode(events)

#if os(tvOS)
let dispatcher = MockEventDispatcher(backingStore: .memory)
let memoryStore: DataStoreMemory<[Data]> = dispatcher.dataStore.dataStore as! DataStoreMemory
UserDefaults.standard.set(saveFormat, forKey: queueName)
UserDefaults.standard.synchronize()
memoryStore.load(forKey: queueName)
var url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
#else
let dispatcher = MockEventDispatcher()
var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
#endif
url = url.appendingPathComponent(queueName, isDirectory: false)
try! saveFormat.write(to: url, options: .atomic)
#endif

// verify that a new dataStore can read an existing queue items

let dispatcher = MockEventDispatcher()

XCTAssert(dispatcher.dataStore.count == 2)
dispatcher.flushEvents()
dispatcher.dispatcher.sync {}
Expand Down