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
16 changes: 8 additions & 8 deletions DatadogCore/Sources/Core/Storage/Directories.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ import DatadogInternal

/// Indicates the main directory for a given instance of the SDK.
/// Each instance of `DatadogCore` creates its own `CoreDirectory` to manage data for registered Features.
/// The core directory is created under `/Library/Caches` and uses a name that identifies the certain instance
/// of the SDK (`<sdk-instance-uuid>`):
/// The core directory is created under a caller-provided OS root (`osDirectory`) and uses a name that
/// identifies the certain instance of the SDK (`<sdk-instance-uuid>`):
///
/// ```
/// /Library/Cache/com.datadoghq/v2/<sdk-instance-uuid>/
/// <osDirectory>/com.datadoghq/v2/<sdk-instance-uuid>/
/// ```
///
/// Note: System may delete data in `/Library/Cache` to free up disk space which reduces the impact on devices working
/// under heavy space pressure. This is intentional for Datadog SDK to have its data purged when system needs more memory
/// for other apps.
/// The root is usually `/Library/Caches` for purgeable Feature data; the system may delete data there to free up
/// disk space, which is intentional for the Datadog SDK. Data that must survive such purges (e.g. remote
/// configuration) is created under `/Library/Application Support` instead.
internal struct CoreDirectory {
/// A known OS location the core directory is created within:`/Library/Cache`.
/// The OS location the core directory is created within (e.g. `/Library/Caches` or `/Library/Application Support`).
let osDirectory: Directory
/// The core directory specific to this instance of the SDK: `/Library/Cache/com.datadoghq/v2/<sdk-instance-uuid>`.
/// The core directory specific to this instance of the SDK: `<osDirectory>/com.datadoghq/v2/<sdk-instance-uuid>`.
let coreDirectory: Directory

/// Obtains subdirectories for managing batch files for given Feature (creates if don't exist).
Expand Down
12 changes: 12 additions & 0 deletions DatadogCore/Sources/Core/Storage/Files/Directory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,16 @@ extension Directory {
}
return Directory(url: cachesDirectoryURL)
}

/// Returns `Directory` pointing to `/Library/Application Support`.
/// - Unlike `/Library/Caches`, the system does not purge `/Library/Application Support` under disk pressure,
/// so it is suitable for data that must persist across launches (e.g. remote configuration).
/// - The directory is included in iTunes and iCloud backups by default.
/// - It is not guaranteed to exist; callers create the subdirectories they need.
static func applicationSupport() throws -> Directory {
Comment thread
maxep marked this conversation as resolved.
guard let applicationSupportDirectoryURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
throw InternalError(description: "Cannot obtain `/Library/Application Support/` url.")
}
return Directory(url: applicationSupportDirectoryURL)
}
}
15 changes: 11 additions & 4 deletions DatadogCore/Sources/Datadog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -424,11 +424,18 @@ extension DatadogCore {
)

let httpClient = configuration.httpClientFactory(configuration.proxyConfiguration)
let remoteConfigurationProvider = configuration.remoteConfigurationID.map {
RemoteConfigurationProvider(
id: $0,
let remoteConfigurationProvider = try configuration.remoteConfigurationID.map { id in
// Remote configuration must survive `/Library/Caches` purges, so it is stored under
// `/Library/Application Support` instead of the (purgeable) core directory.
let persistentDirectory = try CoreDirectory(
in: configuration.persistentDirectory(),
instanceName: instanceName,
site: configuration.site
)
return RemoteConfigurationProvider(
id: id,
site: configuration.site,
directory: directory.coreDirectory,
directory: persistentDirectory.coreDirectory,
Comment thread
maxep marked this conversation as resolved.
httpClient: httpClient,
notificationCenter: configuration.notificationCenter,
dateProvider: configuration.dateProvider
Expand Down
4 changes: 4 additions & 0 deletions DatadogCore/Sources/DatadogConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ extension Datadog {
/// All instances of the SDK use the same root folder, but each creates its own subdirectory.
internal var systemDirectory: () throws -> Directory = { try Directory.cache() }

/// Obtains OS directory where the SDK stores data that must survive `/Library/Caches` purges
/// (e.g. remote configuration). Backed by `/Library/Application Support`.
internal var persistentDirectory: () throws -> Directory = { try Directory.applicationSupport() }

/// Default process information.
internal var processInfo: ProcessInfo = .processInfo

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ class DirectoryTests: XCTestCase {
XCTAssertTrue(fileManager.fileExists(atPath: directory.url.path))
}

func testItObtainsApplicationSupportDirectory() throws {
let directory = try Directory.applicationSupport()
let expectedURL = try XCTUnwrap(fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first)
// Unlike `/Library/Caches`, `/Library/Application Support` is not guaranteed to exist,
// so we assert on the resolved URL rather than its existence on disk.
XCTAssertEqual(directory.url, expectedURL)
}

func testGivenSubdirectoryName_itCreatesIt() throws {
let directory = try Directory.cache().createSubdirectory(path: uniqueSubdirectoryName())
defer { directory.delete() }
Expand Down
27 changes: 27 additions & 0 deletions DatadogCore/Tests/Datadog/DatadogTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,33 @@ class DatadogTests: XCTestCase {
XCTAssertNotNil(core.remoteConfigurationProvider)
}

func testGivenRemoteConfigurationID_itIsStoredInPersistentDirectoryNotCaches() throws {
// Given — distinct injected locations for purgeable caches vs. persistent storage
let cachesDirectory = Directory(url: obtainUniqueTemporaryDirectory())
let persistentDirectory = Directory(url: obtainUniqueTemporaryDirectory())
var config = defaultConfig
config.remoteConfigurationID = "test-id"
config.systemDirectory = { cachesDirectory }
config.persistentDirectory = { persistentDirectory }
config.httpClientFactory = { _ in HTTPClientMock() }

// When
Datadog.initialize(with: config, trackingConsent: .granted)
defer { Datadog.flushAndDeinitialize() }

// Then — remote configuration must live under Application Support, never the purgeable caches
let core = try XCTUnwrap(CoreRegistry.default as? DatadogCore)
let rcPath = try XCTUnwrap(core.remoteConfigurationProvider?.directory.url.path)
XCTAssertTrue(
rcPath.hasPrefix(persistentDirectory.url.path),
"Remote configuration must be stored under the persistent (Application Support) directory"
)
XCTAssertFalse(
rcPath.hasPrefix(cachesDirectory.url.path),
"Remote configuration must not be stored under the purgeable caches directory"
)
}

func testCustomSDKInstance() throws {
// When
Datadog.initialize(
Expand Down