Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: persistent storage, httpclient, eventpipeline #9

Merged
merged 7 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 7 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ identifier_name:
allowed_symbols: "_"
min_length: 1
cyclomatic_complexity: 25
nesting:
type_level:
warning: 3
error: 6
function_level:
warning: 5
error: 10
4 changes: 4 additions & 0 deletions Examples/Apps/IOSExample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## iOS Example
TODO: This is the location to put iOS Example.

However, got some problem when creating a new Swift Project here. Tried to convert the SDK Package to Project then delete the folder reference to only track file changes by git. Got some other problems when converting the Package to Project. Leave a comment here to revisit.
8 changes: 4 additions & 4 deletions Sources/Amplitude/Amplitude.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class Amplitude {
lazy var timeline: Timeline = {
return Timeline()
}()
lazy var logger: any Logger = {
lazy var logger: (any Logger)? = {
return self.configuration.loggerProvider
}()

Expand Down Expand Up @@ -108,7 +108,7 @@ public class Amplitude {
return self
}

func onEnterForeground(timestamp: Double) {
func onEnterForeground(timestamp: Int64) {
inForeground = true

let dummySessionStartEvent = BaseEvent(eventType: "session_start")
Expand All @@ -131,10 +131,10 @@ public class Amplitude {

private func process(event: BaseEvent) {
if configuration.optOut {
logger.log(message: "Skip event based on opt out configuration")
logger?.log(message: "Skip event based on opt out configuration")
return
}
event.timestamp = event.timestamp ?? NSDate().timeIntervalSince1970
event.timestamp = event.timestamp ?? Int64(NSDate().timeIntervalSince1970 * 1000)
timeline.process(event: event)
}
}
4 changes: 2 additions & 2 deletions Sources/Amplitude/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class Configuration {
flushIntervalMillis: Int = Constants.Configuration.FLUSH_INTERVAL_MILLIS,
instanceName: String = Constants.Configuration.DEFAULT_INSTANCE,
optOut: Bool = false,
storageProvider: any Storage = PersistentStorage(),
storageProvider: (any Storage)? = nil,
logLevel: LogLevelEnum = LogLevelEnum.WARN,
loggerProvider: any Logger = ConsoleLogger(),
minIdLength: Int? = nil,
Expand All @@ -63,7 +63,7 @@ public class Configuration {
self.flushIntervalMillis = flushIntervalMillis
self.instanceName = instanceName
self.optOut = optOut
self.storageProvider = storageProvider
self.storageProvider = storageProvider ?? PersistentStorage(apiKey: apiKey)
self.logLevel = logLevel
self.loggerProvider = loggerProvider
self.minIdLength = minIdLength
Expand Down
12 changes: 6 additions & 6 deletions Sources/Amplitude/Events/BaseEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ public class BaseEvent: EventOptions, Codable {
init(
userId: String? = nil,
deviceId: String? = nil,
timestamp: Double? = nil,
eventId: Double? = nil,
sessionId: Double? = -1,
timestamp: Int64? = nil,
eventId: Int64? = nil,
sessionId: Int64? = -1,
insertId: String? = nil,
locationLat: Double? = nil,
locationLng: Double? = nil,
Expand Down Expand Up @@ -200,9 +200,9 @@ public class BaseEvent: EventOptions, Codable {
super.init()
userId = try values.decode(String.self, forKey: .userId)
deviceId = try values.decode(String.self, forKey: .deviceId)
timestamp = try values.decode(Double.self, forKey: .timestamp)
eventId = try values.decode(Double.self, forKey: .eventId)
sessionId = try values.decode(Double.self, forKey: .sessionId)
timestamp = try values.decode(Int64.self, forKey: .timestamp)
eventId = try values.decode(Int64.self, forKey: .eventId)
sessionId = try values.decode(Int64.self, forKey: .sessionId)
locationLat = try values.decode(Double.self, forKey: .locationLat)
locationLng = try values.decode(Double.self, forKey: .locationLng)
appVersion = try values.decode(String.self, forKey: .appVersion)
Expand Down
14 changes: 7 additions & 7 deletions Sources/Amplitude/Events/EventOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import Foundation
public class EventOptions {
var userId: String?
var deviceId: String?
var timestamp: Double?
var eventId: Double?
var sessionId: Double? = -1
var timestamp: Int64?
var eventId: Int64?
var sessionId: Int64? = -1
var insertId: String?
var locationLat: Double?
var locationLng: Double?
Expand Down Expand Up @@ -45,14 +45,14 @@ public class EventOptions {
var extra: [String: Any]?
var callback: EventCallBack?
var partnerId: String?
private var attempts: Int
internal var attempts: Int

init(
userId: String? = nil,
deviceId: String? = nil,
timestamp: Double? = nil,
eventId: Double? = nil,
sessionId: Double? = -1,
timestamp: Int64? = nil,
eventId: Int64? = nil,
sessionId: Int64? = -1,
insertId: String? = nil,
locationLat: Double? = nil,
locationLng: Double? = nil,
Expand Down
4 changes: 2 additions & 2 deletions Sources/Amplitude/Mediator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ internal class Mediator {
var result: BaseEvent? = event
plugins.forEach { plugin in
if let r = result {
if plugin is DestinationPlugin {
_ = plugin.execute(event: r)
liuyang1520 marked this conversation as resolved.
Show resolved Hide resolved
if let p = plugin as? DestinationPlugin {
_ = p.process(event: r)
} else if let p = plugin as? EventPlugin {
result = p.execute(event: r)
if let rr = result {
Expand Down
1 change: 0 additions & 1 deletion Sources/Amplitude/Plugins/AmplitudeDestinationPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public class AmplitudeDestinationPlugin: DestinationPlugin {
logger?.error(message: "Event is invalid for missing information like userId and deviceId")
}
}

}

public func track(event: BaseEvent) -> BaseEvent? {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Amplitude/Plugins/ContextPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class ContextPlugin: Plugin {

internal func mergeContext(event: BaseEvent, context: [String: Any]) {
if event.timestamp == nil {
event.timestamp = NSDate().timeIntervalSince1970 * 1000
event.timestamp = Int64(NSDate().timeIntervalSince1970 * 1000)
}
if event.insertId == nil {
event.insertId = NSUUID().uuidString
Expand Down
2 changes: 1 addition & 1 deletion Sources/Amplitude/Plugins/Mac/MacOSLifecycleMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@

extension AmplitudeDestinationPlugin: MacOSLifecycle {
public func applicationDidBecomeActive() {
let timestamp = NSDate().timeIntervalSince1970
let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000)
self.amplitude?.onEnterForeground(timestamp: timestamp)
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Amplitude/Plugins/iOS/IOSLifecycleMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@

extension AmplitudeDestinationPlugin: IOSLifecycle {
public func applicationWillEnterForeground(application: UIApplication?) {
let timestamp = NSDate().timeIntervalSince1970
let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000)
self.amplitude?.onEnterForeground(timestamp: timestamp)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@

extension AmplitudeDestinationPlugin: WatchOSLifecycle {
public func applicationWillEnterForeground(watchExtension: WKExtension) {
let timestamp = NSDate().timeIntervalSince1970
let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000)
self.amplitude?.onEnterForeground(timestamp: timestamp)
}

Expand Down
14 changes: 6 additions & 8 deletions Sources/Amplitude/Storages/InMemoryStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ class InMemoryStorage: Storage {
func reset() async {

}
}

extension InMemoryStorage {
enum StorageKey: String, CaseIterable {
case LAST_EVENT_ID = "last_event_id"
case PREVIOUS_SESSION_ID = "previous_session_id"
case LAST_EVENT_TIME = "last_event_time"
case OPT_OUT = "opt_out"
case EVENTS = "events"
func rollover() async {

}

func getEventsString(eventBlock: Any) async -> String? {
return nil
}
}
48 changes: 32 additions & 16 deletions Sources/Amplitude/Storages/PersistentStorage.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// PersistentStorage.swift
//
//
// Created by Marvin Liu on 10/28/22.
Expand Down Expand Up @@ -47,6 +47,17 @@ actor PersistentStorage: Storage {
return result
}

func getEventsString(eventBlock: Any) async -> String? {
var content: String?
guard let eventBlock = eventBlock as? URL else { return content }
do {
content = try String(contentsOf: eventBlock, encoding: .utf8)
} catch {
amplitude?.logger?.error(message: error.localizedDescription)
}
return content
}

func reset() async {
let urls = getEventFiles(includeUnfinished: true)
let keys = userDefaults?.dictionaryRepresentation().keys
Expand All @@ -58,6 +69,19 @@ actor PersistentStorage: Storage {
}
}

func rollover() async {
let currentFile = getCurrentFile()
if fileManager?.fileExists(atPath: currentFile.path) == false {
return
}
if let attributes = try? fileManager?.attributesOfItem(atPath: currentFile.path),
let fileSize = attributes[FileAttributeKey.size] as? UInt64,
fileSize >= 0
{
finish(file: currentFile)
}
}

func isBasicType(value: Any?) -> Bool {
var result = false
if value == nil {
Expand All @@ -80,14 +104,6 @@ extension PersistentStorage {
static let MAX_FILE_SIZE = 975000 // 975KB
static let TEMP_FILE_EXTENSION = "tmp"

enum StorageKey: String, CaseIterable {
case LAST_EVENT_ID = "last_event_id"
case PREVIOUS_SESSION_ID = "previous_session_id"
case LAST_EVENT_TIME = "last_event_time"
case OPT_OUT = "opt_out"
case EVENTS = "events"
}

enum Exception: Error {
case unsupportedType
}
Expand Down Expand Up @@ -186,15 +202,15 @@ extension PersistentStorage {
let jsonString = event.toString()
do {
if outputStream == nil {
amplitude?.logger.error(message: "OutputStream is nil with file: \(storeFile)")
amplitude?.logger?.error(message: "OutputStream is nil with file: \(storeFile)")
}
if newFile == false {
// prepare for the next entry
try outputStream?.write(",")
}
try outputStream?.write(jsonString)
} catch {
amplitude?.logger.error(message: error.localizedDescription)
amplitude?.logger?.error(message: error.localizedDescription)
}
}

Expand All @@ -205,7 +221,7 @@ extension PersistentStorage {
try outputStream?.create()
try outputStream?.write(contents)
} catch {
amplitude?.logger.error(message: error.localizedDescription)
amplitude?.logger?.error(message: error.localizedDescription)
}
}

Expand All @@ -215,15 +231,15 @@ extension PersistentStorage {
do {
outputStream = try OutputFileStream(fileURL: file)
liuyang1520 marked this conversation as resolved.
Show resolved Hide resolved
} catch {
amplitude?.logger.error(message: error.localizedDescription)
amplitude?.logger?.error(message: error.localizedDescription)
}
}

if let outputStream = outputStream {
do {
try outputStream.open()
} catch {
amplitude?.logger.error(message: error.localizedDescription)
amplitude?.logger?.error(message: error.localizedDescription)
}
}
}
Expand All @@ -237,7 +253,7 @@ extension PersistentStorage {
do {
try outputStream.write(fileEnding)
yuhao900914 marked this conversation as resolved.
Show resolved Hide resolved
} catch {
amplitude?.logger.error(message: error.localizedDescription)
amplitude?.logger?.error(message: error.localizedDescription)
}
outputStream.close()
self.outputStream = nil
Expand All @@ -246,7 +262,7 @@ extension PersistentStorage {
do {
try fileManager?.moveItem(at: file, to: fileWithoutTemp)
} catch {
amplitude?.logger.error(message: "Unable to rename file: \(file.path)")
amplitude?.logger?.error(message: "Unable to rename file: \(file.path)")
}

let currentFileIndex: Int = (userDefaults?.integer(forKey: eventsFileKey) ?? 0) + 1
Expand Down
2 changes: 0 additions & 2 deletions Sources/Amplitude/Timeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public class Timeline {
let beforeResult = applyPlugin(pluginType: PluginType.before, event: event)
let enrichmentResult = applyPlugin(pluginType: PluginType.enrichment, event: beforeResult)
_ = applyPlugin(pluginType: PluginType.destination, event: enrichmentResult)

}

internal func applyPlugin(pluginType: PluginType, event: BaseEvent?) -> BaseEvent? {
Expand All @@ -30,7 +29,6 @@ public class Timeline {
result = mediator.execute(event: event!)
}
return result

}

internal func add(plugin: Plugin) {
Expand Down
11 changes: 10 additions & 1 deletion Sources/Amplitude/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,21 @@ public protocol EventCallBack {
}

public protocol Storage {
associatedtype StorageKey: RawRepresentable where StorageKey.RawValue: StringProtocol
func write(key: StorageKey, value: Any?) async throws
func read<T>(key: StorageKey) async -> T?
func getEventsString(eventBlock: Any) async -> String?
func rollover() async
func reset() async
}

public enum StorageKey: String, CaseIterable {
case LAST_EVENT_ID = "last_event_id"
case PREVIOUS_SESSION_ID = "previous_session_id"
case LAST_EVENT_TIME = "last_event_time"
case OPT_OUT = "opt_out"
case EVENTS = "events"
}

public protocol Logger {
associatedtype LogLevel: RawRepresentable
var logLevel: Int? { get set }
Expand Down
35 changes: 35 additions & 0 deletions Sources/Amplitude/Utilities/Atomic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Atomic.swift
//
//
// Created by Marvin Liu on 11/29/22.
//

import Foundation

@propertyWrapper
public struct Atomic<T> {
var value: T
private let lock = NSLock()

public init(wrappedValue value: T) {
self.value = value
}

public var wrappedValue: T {
get { return load() }
set { store(newValue: newValue) }
}

func load() -> T {
lock.lock()
defer { lock.unlock() }
return value
}

mutating func store(newValue: T) {
lock.lock()
defer { lock.unlock() }
value = newValue
}
}
Loading