Skip to content
Open
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
2 changes: 2 additions & 0 deletions components/logins/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ dependencies {
api project(':init_rust_components')
api project(':sync15')

implementation libs.kotlin.coroutines
implementation libs.mozilla.glean
implementation project(':init_rust_components')

testImplementation libs.mozilla.glean.forUnitTests
testImplementation libs.kotlin.coroutines.test
testImplementation libs.androidx.test.core
testImplementation libs.androidx.work.testing
testImplementation project(":syncmanager")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,23 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.appservices.logins

/**
* Import some private Glean types, so that we can use them in type declarations.
*
* I do not like importing these private classes, but I do like the nice generic
* code they allow me to write! By agreement with the Glean team, we must not
* instantiate anything from these classes, and it's on us to fix any bustage
* on version updates.
*/
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.telemetry.glean.private.CounterMetricType
import mozilla.telemetry.glean.private.LabeledMetricType
import kotlin.coroutines.CoroutineContext
import org.mozilla.appservices.logins.GleanMetrics.LoginsStore as LoginsStoreMetrics

/**
* An artifact of the uniffi conversion - a thin-ish wrapper around a
* LoginStore.
*/

class DatabaseLoginsStorage(dbPath: String, keyManager: KeyManager) : AutoCloseable {
class DatabaseLoginsStorage(
dbPath: String,
keyManager: KeyManager,
private val coroutineContext: CoroutineContext = Dispatchers.IO,
) : AutoCloseable {
private var store: LoginStore

init {
Expand All @@ -30,94 +28,94 @@ class DatabaseLoginsStorage(dbPath: String, keyManager: KeyManager) : AutoClosea
}

@Throws(LoginsApiException::class)
fun reset() {
this.store.reset()
suspend fun reset(): Unit = withContext(coroutineContext) {
store.reset()
}

@Throws(LoginsApiException::class)
fun wipeLocal() {
this.store.wipeLocal()
suspend fun wipeLocal(): Unit = withContext(coroutineContext) {
store.wipeLocal()
}

@Throws(LoginsApiException::class)
fun delete(id: String): Boolean {
return writeQueryCounters.measure {
suspend fun delete(id: String): Boolean = withContext(coroutineContext) {
writeQueryCounters.measure {
store.delete(id)
}
}

@Throws(LoginsApiException::class)
fun get(id: String): Login? {
return readQueryCounters.measure {
suspend fun get(id: String): Login? = withContext(coroutineContext) {
readQueryCounters.measure {
store.get(id)
}
}

@Throws(LoginsApiException::class)
fun touch(id: String) {
suspend fun touch(id: String): Unit = withContext(coroutineContext) {
writeQueryCounters.measure {
store.touch(id)
}
}

@Throws(LoginsApiException::class)
fun isEmpty(): Boolean {
return readQueryCounters.measure {
suspend fun isEmpty(): Boolean = withContext(coroutineContext) {
readQueryCounters.measure {
store.isEmpty()
}
}

@Throws(LoginsApiException::class)
fun list(): List<Login> {
return readQueryCounters.measure {
suspend fun list(): List<Login> = withContext(coroutineContext) {
readQueryCounters.measure {
store.list()
}
}

@Throws(LoginsApiException::class)
fun hasLoginsByBaseDomain(baseDomain: String): Boolean {
return readQueryCounters.measure {
suspend fun hasLoginsByBaseDomain(baseDomain: String): Boolean = withContext(coroutineContext) {
readQueryCounters.measure {
store.hasLoginsByBaseDomain(baseDomain)
}
}

@Throws(LoginsApiException::class)
fun getByBaseDomain(baseDomain: String): List<Login> {
return readQueryCounters.measure {
suspend fun getByBaseDomain(baseDomain: String): List<Login> = withContext(coroutineContext) {
readQueryCounters.measure {
store.getByBaseDomain(baseDomain)
}
}

@Throws(LoginsApiException::class)
fun findLoginToUpdate(look: LoginEntry): Login? {
return readQueryCounters.measure {
suspend fun findLoginToUpdate(look: LoginEntry): Login? = withContext(coroutineContext) {
readQueryCounters.measure {
store.findLoginToUpdate(look)
}
}

@Throws(LoginsApiException::class)
fun add(entry: LoginEntry): Login {
return writeQueryCounters.measure {
suspend fun add(entry: LoginEntry): Login = withContext(coroutineContext) {
writeQueryCounters.measure {
store.add(entry)
}
}

@Throws(LoginsApiException::class)
fun update(id: String, entry: LoginEntry): Login {
return writeQueryCounters.measure {
suspend fun update(id: String, entry: LoginEntry): Login = withContext(coroutineContext) {
writeQueryCounters.measure {
store.update(id, entry)
}
}

@Throws(LoginsApiException::class)
fun addOrUpdate(entry: LoginEntry): Login {
return writeQueryCounters.measure {
suspend fun addOrUpdate(entry: LoginEntry): Login = withContext(coroutineContext) {
writeQueryCounters.measure {
store.addOrUpdate(entry)
}
}

fun registerWithSyncManager() {
return store.registerWithSyncManager()
store.registerWithSyncManager()
}

@Synchronized
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
package mozilla.appservices.logins

import androidx.test.core.app.ApplicationProvider
import kotlinx.coroutines.test.runTest
import mozilla.appservices.RustComponentsInitializer
import mozilla.appservices.syncmanager.SyncManager
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Rule
Expand Down Expand Up @@ -41,7 +41,7 @@ class DatabaseLoginsStorageTest {
return DatabaseLoginsStorage(dbPath = dbPath.absolutePath, keyManager = keyManager)
}

protected fun getTestStore(): DatabaseLoginsStorage {
protected suspend fun getTestStore(): DatabaseLoginsStorage {
val store = createTestStore()

store.add(
Expand Down Expand Up @@ -77,7 +77,7 @@ class DatabaseLoginsStorageTest {
}

@Test
fun testMetricsGathering() {
fun testMetricsGathering() = runTest {
val store = createTestStore()

assertNull(LoginsStoreMetrics.writeQueryCount.testGetValue())
Expand Down Expand Up @@ -132,7 +132,7 @@ class DatabaseLoginsStorageTest {
}

@Test
fun testTouch() {
fun testTouch() = runTest {
val store = getTestStore()
val login = store.list()[0]
// Wait 100ms so that touch is certain to change timeLastUsed.
Expand All @@ -145,13 +145,17 @@ class DatabaseLoginsStorageTest {
assertEquals(login.timesUsed + 1, updatedLogin!!.timesUsed)
assert(updatedLogin.timeLastUsed > login.timeLastUsed)

assertThrows(LoginsApiException.NoSuchRecord::class.java) { store.touch("abcdabcdabcd") }
try {
store.touch("abcdabcdabcd")
} catch (e: LoginsApiException.NoSuchRecord) {
// Expected error
}

finishAndClose(store)
}

@Test
fun testDelete() {
fun testDelete() = runTest {
val store = getTestStore()
val login = store.list()[0]

Expand All @@ -165,7 +169,7 @@ class DatabaseLoginsStorageTest {
}

@Test
fun testWipeLocal() {
fun testWipeLocal() = runTest {
val test = getTestStore()
val logins = test.list()
assertEquals(2, logins.size)
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protobuf-plugin = "0.9.5"
# Kotlin
kotlin-compiler = "2.1.21"
kotlin-coroutines = "1.10.2"
kotlin-coroutines-test = "1.10.2"

# Mozilla
android-components = "139.0.20250417022706"
Expand Down Expand Up @@ -58,6 +59,7 @@ protobuf-compiler = { group = "com.google.protobuf", name = "protoc", version.re
# Kotlin
kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin-compiler" }
kotlin-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlin-coroutines" }
kotlin-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlin-coroutines-test" }

# Mozilla
mozilla-concept-fetch = { group = "org.mozilla.components", name = "concept-fetch", version.ref = "android-components" }
Expand Down