diff --git a/Examples/apps/SegmentUIKitExample/SegmentUIKitExample/AppDelegate.swift b/Examples/apps/SegmentUIKitExample/SegmentUIKitExample/AppDelegate.swift index 62182ffa..8786a92a 100644 --- a/Examples/apps/SegmentUIKitExample/SegmentUIKitExample/AppDelegate.swift +++ b/Examples/apps/SegmentUIKitExample/SegmentUIKitExample/AppDelegate.swift @@ -28,6 +28,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Analytics.support.track(name: "test event") + Analytics.main.screen(title: "home screen shown", category: nil, properties: nil) return true } diff --git a/Examples/other_plugins/CellularCarrier.swift b/Examples/other_plugins/CellularCarrier.swift index 3285bb87..31fc74ea 100644 --- a/Examples/other_plugins/CellularCarrier.swift +++ b/Examples/other_plugins/CellularCarrier.swift @@ -59,7 +59,7 @@ class CellularCarrier: Plugin { func execute(event: T?) -> T? { guard var workingEvent = event else { return event } - if let isCellular: Bool = workingEvent.context?[keyPath: "network"], + if let isCellular: Bool = workingEvent.context?[keyPath: "network.cellular"], isCellular, let carriers = self.carriers { diff --git a/Sources/Segment/Analytics.swift b/Sources/Segment/Analytics.swift index 4ebc6a85..1aaa7c88 100644 --- a/Sources/Segment/Analytics.swift +++ b/Sources/Segment/Analytics.swift @@ -157,3 +157,43 @@ extension Analytics { } } + +extension Analytics { + /// Determine if there are any events that have yet to be sent to Segment + public var hasUnsentEvents: Bool { + if let segmentDest = self.find(pluginType: SegmentDestination.self) { + if segmentDest.pendingUploads > 0 { + return true + } + if segmentDest.eventCount > 0 { + return true + } + } + + if let files = storage.read(Storage.Constants.events) { + if files.count > 0 { + return true + } + } + + return false + } + + /// Provides a list of finished, but unsent events. + public var pendingUploads: [URL]? { + return storage.read(Storage.Constants.events) + } + + /// Wait until the Analytics object has completed startup. + /// This method is primarily useful for command line utilities where + /// it's desirable to wait until the system is up and running + /// before executing commands. GUI apps could potentially use this via + /// a background thread if needed. + public func waitUntilStarted() { + if let startupQueue = find(pluginType: StartupQueue.self) { + while startupQueue.running != true { + RunLoop.main.run(until: Date.distantPast) + } + } + } +} diff --git a/Sources/Segment/Plugins/SegmentDestination.swift b/Sources/Segment/Plugins/SegmentDestination.swift index 5011323c..532cc787 100644 --- a/Sources/Segment/Plugins/SegmentDestination.swift +++ b/Sources/Segment/Plugins/SegmentDestination.swift @@ -47,7 +47,7 @@ public class SegmentDestination: DestinationPlugin { private var apiKey: String? = nil private var apiHost: String? = nil - @Atomic private var eventCount: Int = 0 + @Atomic internal var eventCount: Int = 0 internal var flushTimer: QueueTimer? = nil internal func initialSetup() { @@ -99,7 +99,6 @@ public class SegmentDestination: DestinationPlugin { storage.write(.events, value: event) eventCount += 1 if eventCount >= analytics.configuration.values.flushAt { - eventCount = 0 flush() } } @@ -112,6 +111,7 @@ public class SegmentDestination: DestinationPlugin { // Read events from file system guard let data = storage.read(Storage.Constants.events) else { return } + eventCount = 0 cleanupUploads() analytics.log(message: "Uploads in-progress: \(pendingUploads)") @@ -124,6 +124,7 @@ public class SegmentDestination: DestinationPlugin { switch result { case .success(_): storage.remove(file: url) + self.cleanupUploads() default: analytics.logFlush() } diff --git a/Sources/Segment/Plugins/StartupQueue.swift b/Sources/Segment/Plugins/StartupQueue.swift index fcc9a6a1..c29a08ed 100644 --- a/Sources/Segment/Plugins/StartupQueue.swift +++ b/Sources/Segment/Plugins/StartupQueue.swift @@ -8,14 +8,14 @@ import Foundation import Sovran -internal class StartupQueue: Plugin, Subscriber { +public class StartupQueue: Plugin, Subscriber { static let maxSize = 1000 - @Atomic var running: Bool = false + @Atomic public var running: Bool = false - let type: PluginType = .before + public let type: PluginType = .before - var analytics: Analytics? = nil { + public var analytics: Analytics? = nil { didSet { analytics?.store.subscribe(self, handler: runningUpdate) } @@ -26,7 +26,7 @@ internal class StartupQueue: Plugin, Subscriber { required init() { } - func execute(event: T?) -> T? { + public func execute(event: T?) -> T? { if running == false, let e = event { // timeline hasn't started, so queue it up. syncQueue.sync { diff --git a/Sources/Segment/Utilities/JSON.swift b/Sources/Segment/Utilities/JSON.swift index 6d35d76a..5f67ad48 100644 --- a/Sources/Segment/Utilities/JSON.swift +++ b/Sources/Segment/Utilities/JSON.swift @@ -397,7 +397,7 @@ extension JSON { do { result = try JSONDecoder().decode(T.self, from: jsonData) } catch { - Analytics.segmentLog(message: "Unable to decode object to a Codable: \(error)", kind: .error) + Analytics.segmentLog(message: "Unable to decode object (\(keyPath)) to a Codable: \(error)", kind: .error) } } if result == nil { diff --git a/Sources/Segment/Utilities/Storage.swift b/Sources/Segment/Utilities/Storage.swift index 0dda124d..2045ce56 100644 --- a/Sources/Segment/Utilities/Storage.swift +++ b/Sources/Segment/Utilities/Storage.swift @@ -285,6 +285,10 @@ extension Storage { } private func finish(file: URL) { + if self.fileHandle == nil { + self.fileHandle = try? FileHandle(forWritingTo: file) + } + guard let fileHandle = self.fileHandle else { // we haven't actually started a file yet and being told to flush // so ignore it and get out.