Skip to content

Commit ad9460b

Browse files
jkmasseloguzkocer
andauthored
Add sqlite cache (#903)
* Get basic sqlite working with updates * Genuine Sync+Send, and injected delegate * Fix Swift test warning * Add Kotlin background update notifications test * Use `runTest` * Update native/kotlin/api/android/build.gradle.kts Co-authored-by: Oguz Kocer <oguzkocer@users.noreply.github.com> * Update native/kotlin/api/kotlin/src/integrationTest/kotlin/WordPressApiCacheTest.kt Co-authored-by: Oguz Kocer <oguzkocer@users.noreply.github.com> * Fix a migration number calculation bug in wp_api_cache * Address a minor detekt issue in WordPressApiCache.kt * Don’t compile notification testing on Linux * Use Xcode 26.0 in CI * Use v26 simulators * Create simulators if they don’t already exist * Fix iOS example app --------- Co-authored-by: Oguz Kocer <oguzkocer@users.noreply.github.com> Co-authored-by: Oguz Kocer <oguz.kocer@automattic.com>
1 parent 426d824 commit ad9460b

File tree

20 files changed

+570
-12
lines changed

20 files changed

+570
-12
lines changed

.buildkite/shared-pipeline-vars

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# This file is `source`'d before calling `buildkite-agent pipeline upload`, and can be used
44
# to set up some variables that will be interpolated in the `.yml` pipeline before uploading it.
55

6-
XCODE_VERSION="16.3-v2"
6+
XCODE_VERSION="26.0"
77
CI_TOOLKIT_PLUGIN_VERSION="3.7.1"
88
TEST_COLLECTOR_VERSION="v1.10.2"
99

.buildkite/swift-test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function build_for_real_device() {
1818
echo "--- :swift: Building for $platform device"
1919
export NSUnbufferedIO=YES
2020
xcodebuild -destination "generic/platform=$platform" \
21-
-scheme WordPressAPI \
21+
-scheme WordPressAPI-Package \
2222
-derivedDataPath DerivedData \
2323
-skipPackagePluginValidation \
2424
build | xcbeautify

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
/docs
77
/docs.tar.gz
88

9+
# Test DB Files
10+
kotlin.db
11+
swift.db
12+
913
# Ignore Gradle project-specific cache directory
1014
.gradle
1115

@@ -33,7 +37,6 @@ fastlane/report.xml
3337
libwordPressFFI.xcframework*
3438
/swift-docs.tar.gz
3539

36-
3740
# Auto-generated Swift Files
3841
native/swift/Sources/wordpress-api-wrapper/*.swift
3942

Cargo.lock

Lines changed: 56 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ _build-apple-%-tvos _build-apple-%-tvos-sim _build-apple-%-watchos _build-apple-
102102

103103
# Build the library for a specific target
104104
_build-apple-%:
105-
cargo $(CARGO_OPTS) $(cargo_config_library) build --target $* --features export-uncancellable-endpoints --package wp_api --profile $(CARGO_PROFILE)
105+
cargo $(CARGO_OPTS) $(cargo_config_library) build --target $* --features export-uncancellable-endpoints --package wp_api --profile $(CARGO_PROFILE) --no-default-features
106106
./scripts/swift-bindings.sh target/$*/$(CARGO_PROFILE_DIRNAME)/libwp_api.a
107107

108108
# Build the library for one single platform, including real device and simulator.
@@ -153,7 +153,8 @@ swift-example-app-mac:
153153
xcodebuild -project native/swift/Example/Example.xcodeproj -scheme Example -destination 'platform=macOS,arch=arm64' -skipPackagePluginValidation build
154154

155155
swift-example-app-ios:
156-
bundle exec fastlane run run_tests project:native/swift/Example/Example.xcodeproj scheme:Example build_for_testing:true ensure_devices_found:true device:"iPhone 16 (18.4)" xcargs:"-skipPackagePluginValidation"
156+
xcrun simctl create "iPhone 17 Pro Test Device" "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro"
157+
bundle exec fastlane run run_tests project:native/swift/Example/Example.xcodeproj scheme:Example build_for_testing:true ensure_devices_found:true device:"iPhone 17 Pro Test Device (26.0)" xcargs:"-skipPackagePluginValidation"
157158

158159
test-swift:
159160
$(MAKE) test-swift-$(uname)
@@ -170,13 +171,13 @@ test-swift-darwin: xcframework
170171
test-swift-macOS: test-swift-darwin
171172

172173
test-swift-iOS: xcframework
173-
scripts/xcodebuild-test.sh iOS-18-4
174+
scripts/xcodebuild-test.sh iOS-26-0
174175

175176
test-swift-tvOS: xcframework
176-
scripts/xcodebuild-test.sh tvOS-18-4
177+
scripts/xcodebuild-test.sh tvOS-26-0
177178

178179
test-swift-watchOS: xcframework
179-
scripts/xcodebuild-test.sh watchOS-11-4
180+
scripts/xcodebuild-test.sh watchOS-26-0
180181

181182
test-rust-lib:
182183
$(rust_docker_run) cargo test --lib -- --nocapture

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ var package = Package(
2626
.library(
2727
name: "WordPressAPI",
2828
targets: ["WordPressAPI"]
29+
),
30+
.library(
31+
name: "WordPressApiCache",
32+
targets: ["WordPressApiCache"]
2933
)
3034
],
3135
dependencies: [
@@ -56,6 +60,13 @@ var package = Package(
5660
.swiftLanguageMode(.v5)
5761
]
5862
),
63+
.target(
64+
name: "WordPressApiCache",
65+
dependencies: [
66+
.target(name: "WordPressAPIInternal")
67+
],
68+
path: "native/swift/Sources/wordpress-api-cache"
69+
),
5970
libwordpressFFI,
6071
.testTarget(
6172
name: "WordPressAPITests",
@@ -68,6 +79,14 @@ var package = Package(
6879
swiftSettings: [
6980
.define("PROGRESS_REPORTING_ENABLED", .when(platforms: [.iOS, .macOS, .tvOS, .watchOS]))
7081
]
82+
),
83+
.testTarget(
84+
name: "WordPressApiCacheTests",
85+
dependencies: [
86+
.target(name: "WordPressApiCache"),
87+
.target(name: "WordPressAPIInternal")
88+
],
89+
path: "native/swift/Tests/wordpress-api-cache"
7190
)
7291
].addingIntegrationTests()
7392
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import kotlinx.coroutines.test.runTest
2+
import org.junit.jupiter.api.Test
3+
import org.junit.jupiter.api.parallel.Execution
4+
import org.junit.jupiter.api.parallel.ExecutionMode
5+
import rs.wordpress.cache.kotlin.WordPressApiCache
6+
import rs.wordpress.cache.kotlin.WordPressApiCacheDelegate
7+
import kotlin.test.assertEquals
8+
9+
@Execution(ExecutionMode.CONCURRENT)
10+
class WordPressApiCacheTest {
11+
12+
@Test
13+
fun testThatMigrationsWork() = runTest {
14+
assertEquals(2, WordPressApiCache().performMigrations())
15+
}
16+
17+
@Test
18+
fun testBackgroundUpdateNotificationsWork() = runTest {
19+
var updateCount = 0
20+
val delegate = WordPressApiCacheDelegate(
21+
callback = { updateHook ->
22+
updateCount += 1
23+
}
24+
)
25+
26+
val cache = WordPressApiCache(delegate = delegate)
27+
cache.startListeningForUpdates()
28+
29+
val migrationCount = cache.performMigrations()
30+
assertEquals(updateCount, migrationCount)
31+
}
32+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package rs.wordpress.cache.kotlin
2+
3+
import kotlinx.coroutines.asCoroutineDispatcher
4+
import kotlinx.coroutines.withContext
5+
import uniffi.wp_api.DatabaseDelegate
6+
import uniffi.wp_api.UpdateHook
7+
import uniffi.wp_api.WpApiCache
8+
import java.nio.file.Path
9+
import java.util.concurrent.Executors
10+
11+
class WordPressApiCacheLoggingDelegate : DatabaseDelegate {
12+
override fun didUpdate(updateHook: UpdateHook) {
13+
println("Received update: $updateHook")
14+
}
15+
}
16+
class WordPressApiCacheDelegate(
17+
private val callback: (updateHook: UpdateHook) -> Unit
18+
) : DatabaseDelegate {
19+
20+
override fun didUpdate(updateHook: UpdateHook) {
21+
callback(updateHook)
22+
}
23+
}
24+
25+
class WordPressApiCache {
26+
private val cache: WpApiCache
27+
private val internalDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
28+
private val delegate: DatabaseDelegate?
29+
30+
// Creates a new in-memory cache
31+
constructor(delegate: WordPressApiCacheDelegate? = null) : this(":memory:", delegate)
32+
33+
// Creates a new cache at the specified file system URL
34+
constructor(path: Path, delegate: WordPressApiCacheDelegate? = null) : this(path.toString(), delegate)
35+
36+
// Creates a new cache at the specified path
37+
constructor(string: String, delegate: WordPressApiCacheDelegate? = null) {
38+
this.cache = WpApiCache(string)
39+
this.delegate = delegate
40+
}
41+
42+
suspend fun performMigrations(): Int = withContext(internalDispatcher) {
43+
cache.performMigrations().toInt()
44+
}
45+
fun startListeningForUpdates() {
46+
if (this.delegate != null) {
47+
this.cache.startListeningForUpdates(this.delegate)
48+
}
49+
}
50+
51+
fun stopListeningForUpdates() {
52+
this.cache.stopListeningForUpdates()
53+
}
54+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import Foundation
2+
import WordPressAPIInternal
3+
4+
public actor WordPressApiCache {
5+
6+
private let cache: WpApiCache
7+
private let delegate: any DatabaseDelegate
8+
9+
public struct Notifications {
10+
public static let cacheDidUpdate = Notification.Name("WordPressApiCache.cacheDidUpdate")
11+
12+
public static func name(for table: String) -> Notification.Name {
13+
Notification.Name(rawValue: "WordPressApiCache.cacheDidUpdate.\(table)")
14+
}
15+
}
16+
17+
final public class ApiCacheDelegate: DatabaseDelegate {
18+
public init() {}
19+
20+
public func didUpdate(updateHook: WordPressAPIInternal.UpdateHook) {
21+
let name = Notifications.name(for: updateHook.tableName)
22+
NotificationCenter.default.post(name: name, object: updateHook)
23+
}
24+
}
25+
26+
/// Creates a new in-memory cache
27+
public init(delegate: DatabaseDelegate = ApiCacheDelegate()) throws {
28+
try self.init(path: ":memory:", delegate: delegate)
29+
}
30+
31+
/// Creates a new cache at the specified file system URL
32+
public init(url: URL, delegate: DatabaseDelegate = ApiCacheDelegate()) throws {
33+
try self.init(path: url.absoluteString, delegate: delegate)
34+
}
35+
36+
/// Creates a new cache at the specified path
37+
public init(path: String, delegate: DatabaseDelegate = ApiCacheDelegate()) throws {
38+
self.cache = try WpApiCache(path: path)
39+
self.delegate = delegate
40+
}
41+
42+
public func performMigrations() async throws -> Int {
43+
return Int(try self.cache.performMigrations())
44+
}
45+
46+
public func startListeningForUpdates() {
47+
self.cache.startListeningForUpdates(delegate: self.delegate)
48+
}
49+
50+
public func stopListeningForUpdates() {
51+
self.cache.stopListeningForUpdates()
52+
}
53+
54+
deinit {
55+
self.cache.stopListeningForUpdates()
56+
}
57+
}

0 commit comments

Comments
 (0)