Initial implementation of the Kotlin Sync Manager API
This commit is contained in:
Родитель
ca6a6a90b7
Коммит
9f6fcedc85
|
@ -57,6 +57,13 @@ projects:
|
|||
- name: sync15
|
||||
type: aar
|
||||
description: Shared Sync types for Kotlin.
|
||||
syncmanager:
|
||||
path: components/sync_manager/android
|
||||
artifactId: syncmanager
|
||||
publications:
|
||||
- name: syncmanager
|
||||
type: aar
|
||||
description: Sync manager implementation
|
||||
lockbox-megazord:
|
||||
uploadSymbols: true
|
||||
path: megazords/lockbox/android
|
||||
|
|
|
@ -623,6 +623,7 @@ dependencies = [
|
|||
"places-ffi 0.1.0",
|
||||
"push-ffi 0.1.0",
|
||||
"rc_log_ffi 0.1.0",
|
||||
"sync_manager_ffi 0.1.0",
|
||||
"viaduct 0.1.0",
|
||||
]
|
||||
|
||||
|
@ -1028,6 +1029,7 @@ dependencies = [
|
|||
"fxaclient_ffi 0.1.0",
|
||||
"logins_ffi 0.1.0",
|
||||
"rc_log_ffi 0.1.0",
|
||||
"sync_manager_ffi 0.1.0",
|
||||
"viaduct 0.1.0",
|
||||
]
|
||||
|
||||
|
@ -1107,6 +1109,7 @@ dependencies = [
|
|||
"places-ffi 0.1.0",
|
||||
"push-ffi 0.1.0",
|
||||
"rc_log_ffi 0.1.0",
|
||||
"sync_manager_ffi 0.1.0",
|
||||
"viaduct 0.1.0",
|
||||
]
|
||||
|
||||
|
@ -2351,6 +2354,37 @@ dependencies = [
|
|||
"viaduct 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_manager"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"error-support 0.1.0",
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ffi-support 0.3.5",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"logins 0.1.0",
|
||||
"places 0.1.0",
|
||||
"prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prost-build 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prost-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sync15 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_manager_ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ffi-support 0.3.5",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"logins_ffi 0.1.0",
|
||||
"places-ffi 0.1.0",
|
||||
"prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sync15 0.1.0",
|
||||
"sync_manager 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.10.2"
|
||||
|
|
|
@ -20,6 +20,8 @@ members = [
|
|||
"components/support/rc_crypto/nss/nss_sys",
|
||||
"components/viaduct",
|
||||
"components/sync15",
|
||||
"components/sync_manager",
|
||||
"components/sync_manager/ffi",
|
||||
"components/rc_log",
|
||||
"megazords/fenix",
|
||||
"megazords/full",
|
||||
|
|
|
@ -29,6 +29,17 @@ class DatabaseLoginsStorage(private val dbPath: String) : AutoCloseable, LoginsS
|
|||
return handle
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw handle used to reference this logins database.
|
||||
*
|
||||
* Generally should only be used to pass the handle into `SyncManager.setLogins`.
|
||||
*
|
||||
* Note: handles do not remain valid after locking / unlocking the logins database.
|
||||
*/
|
||||
fun getHandle(): Long {
|
||||
return this.raw.get()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@Throws(LoginsStorageException::class)
|
||||
override fun lock() {
|
||||
|
|
|
@ -14,9 +14,13 @@ use ffi_support::{
|
|||
};
|
||||
use logins::{Login, PasswordEngine, Result};
|
||||
use std::os::raw::c_char;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ENGINES: ConcurrentHandleMap<PasswordEngine> = ConcurrentHandleMap::new();
|
||||
// TODO: this is basically a RwLock<HandleMap<Mutex<Arc<Mutex<...>>>>.
|
||||
// but could just be a `RwLock<HandleMap<Arc<Mutex<...>>>>`.
|
||||
// Find a way to express this cleanly in ffi_support?
|
||||
pub static ref ENGINES: ConcurrentHandleMap<Arc<Mutex<PasswordEngine>>> = ConcurrentHandleMap::new();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -26,10 +30,10 @@ pub extern "C" fn sync15_passwords_state_new(
|
|||
error: &mut ExternError,
|
||||
) -> u64 {
|
||||
log::debug!("sync15_passwords_state_new");
|
||||
ENGINES.insert_with_result(error, || {
|
||||
ENGINES.insert_with_result(error, || -> logins::Result<_> {
|
||||
let path = db_path.as_str();
|
||||
let key = encryption_key.as_str();
|
||||
PasswordEngine::new(path, Some(key))
|
||||
Ok(Arc::new(Mutex::new(PasswordEngine::new(path, Some(key))?)))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -65,12 +69,15 @@ pub unsafe extern "C" fn sync15_passwords_state_new_with_hex_key(
|
|||
error: &mut ExternError,
|
||||
) -> u64 {
|
||||
log::debug!("sync15_passwords_state_new_with_hex_key");
|
||||
ENGINES.insert_with_result(error, || {
|
||||
ENGINES.insert_with_result(error, || -> logins::Result<_> {
|
||||
let path = db_path.as_str();
|
||||
let key = bytes_to_key_string(encryption_key, encryption_key_len as usize);
|
||||
// We have a Option<String>, but need an Option<&str>...
|
||||
let opt_key_ref = key.as_ref().map(String::as_str);
|
||||
PasswordEngine::new(path, opt_key_ref)
|
||||
Ok(Arc::new(Mutex::new(PasswordEngine::new(
|
||||
path,
|
||||
opt_key_ref,
|
||||
)?)))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -83,7 +90,7 @@ fn parse_url(url: &str) -> sync15::Result<url::Url> {
|
|||
pub extern "C" fn sync15_passwords_disable_mem_security(handle: u64, error: &mut ExternError) {
|
||||
log::debug!("sync15_passwords_disable_mem_security");
|
||||
ENGINES.call_with_result(error, handle, |state| -> Result<()> {
|
||||
state.disable_mem_security()
|
||||
state.lock().unwrap().disable_mem_security()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -98,7 +105,7 @@ pub extern "C" fn sync15_passwords_sync(
|
|||
) -> *mut c_char {
|
||||
log::debug!("sync15_passwords_sync");
|
||||
ENGINES.call_with_result(error, handle, |state| -> Result<_> {
|
||||
let ping = state.sync(
|
||||
let ping = state.lock().unwrap().sync(
|
||||
&sync15::Sync15StorageClientInit {
|
||||
key_id: key_id.into_string(),
|
||||
access_token: access_token.into_string(),
|
||||
|
@ -113,7 +120,9 @@ pub extern "C" fn sync15_passwords_sync(
|
|||
#[no_mangle]
|
||||
pub extern "C" fn sync15_passwords_touch(handle: u64, id: FfiStr<'_>, error: &mut ExternError) {
|
||||
log::debug!("sync15_passwords_touch");
|
||||
ENGINES.call_with_result(error, handle, |state| state.touch(id.as_str()))
|
||||
ENGINES.call_with_result(error, handle, |state| {
|
||||
state.lock().unwrap().touch(id.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -123,25 +132,27 @@ pub extern "C" fn sync15_passwords_delete(
|
|||
error: &mut ExternError,
|
||||
) -> u8 {
|
||||
log::debug!("sync15_passwords_delete");
|
||||
ENGINES.call_with_result(error, handle, |state| state.delete(id.as_str()))
|
||||
ENGINES.call_with_result(error, handle, |state| {
|
||||
state.lock().unwrap().delete(id.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sync15_passwords_wipe(handle: u64, error: &mut ExternError) {
|
||||
log::debug!("sync15_passwords_wipe");
|
||||
ENGINES.call_with_result(error, handle, |state| state.wipe())
|
||||
ENGINES.call_with_result(error, handle, |state| state.lock().unwrap().wipe())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sync15_passwords_wipe_local(handle: u64, error: &mut ExternError) {
|
||||
log::debug!("sync15_passwords_wipe_local");
|
||||
ENGINES.call_with_result(error, handle, |state| state.wipe_local())
|
||||
ENGINES.call_with_result(error, handle, |state| state.lock().unwrap().wipe_local())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sync15_passwords_reset(handle: u64, error: &mut ExternError) {
|
||||
log::debug!("sync15_passwords_reset");
|
||||
ENGINES.call_with_result(error, handle, |state| state.reset())
|
||||
ENGINES.call_with_result(error, handle, |state| state.lock().unwrap().reset())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -150,7 +161,9 @@ pub extern "C" fn sync15_passwords_new_interrupt_handle(
|
|||
error: &mut ExternError,
|
||||
) -> *mut sql_support::SqlInterruptHandle {
|
||||
log::debug!("sync15_passwords_new_interrupt_handle");
|
||||
ENGINES.call_with_output(error, handle, |state| state.new_interrupt_handle())
|
||||
ENGINES.call_with_output(error, handle, |state| {
|
||||
state.lock().unwrap().new_interrupt_handle()
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -166,7 +179,7 @@ pub extern "C" fn sync15_passwords_interrupt(
|
|||
pub extern "C" fn sync15_passwords_get_all(handle: u64, error: &mut ExternError) -> *mut c_char {
|
||||
log::debug!("sync15_passwords_get_all");
|
||||
ENGINES.call_with_result(error, handle, |state| -> Result<String> {
|
||||
let all_passwords = state.list()?;
|
||||
let all_passwords = state.lock().unwrap().list()?;
|
||||
let result = serde_json::to_string(&all_passwords)?;
|
||||
Ok(result)
|
||||
})
|
||||
|
@ -179,7 +192,9 @@ pub extern "C" fn sync15_passwords_get_by_id(
|
|||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("sync15_passwords_get_by_id");
|
||||
ENGINES.call_with_result(error, handle, |state| state.get(id.as_str()))
|
||||
ENGINES.call_with_result(error, handle, |state| {
|
||||
state.lock().unwrap().get(id.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -196,7 +211,7 @@ pub extern "C" fn sync15_passwords_add(
|
|||
parsed["id"] = serde_json::Value::String(String::default());
|
||||
}
|
||||
let login: Login = serde_json::from_value(parsed)?;
|
||||
state.add(login)
|
||||
state.lock().unwrap().add(login)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -222,7 +237,7 @@ pub extern "C" fn sync15_passwords_update(
|
|||
log::debug!("sync15_passwords_update");
|
||||
ENGINES.call_with_result(error, handle, |state| {
|
||||
let parsed: Login = serde_json::from_str(record_json.as_str())?;
|
||||
state.update(parsed)
|
||||
state.lock().unwrap().update(parsed)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,15 @@ class PlacesApi(path: String) : PlacesManager, AutoCloseable {
|
|||
private const val READ_WRITE: Int = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw handle used to reference this PlacesApi.
|
||||
*
|
||||
* Generally should only be used to pass the handle into `SyncManager.setPlaces`
|
||||
*/
|
||||
fun getHandle(): Long {
|
||||
return this.handle.get()
|
||||
}
|
||||
|
||||
override fun openReader(): PlacesReaderConnection {
|
||||
val connHandle = rustCall(this) { error ->
|
||||
LibPlacesFFI.INSTANCE.places_connection_new(handle.get(), READ_ONLY, error)
|
||||
|
|
|
@ -30,7 +30,7 @@ fn parse_url(url: &str) -> places::Result<url::Url> {
|
|||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref APIS: ConcurrentHandleMap<Arc<PlacesApi>> = ConcurrentHandleMap::new();
|
||||
pub static ref APIS: ConcurrentHandleMap<Arc<PlacesApi>> = ConcurrentHandleMap::new();
|
||||
static ref CONNECTIONS: ConcurrentHandleMap<PlacesDb> = ConcurrentHandleMap::new();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "sync_manager"
|
||||
version = "0.1.0"
|
||||
authors = ["application-services <application-services@mozilla.com>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
sync15 = { path = "../sync15" }
|
||||
places = { path = "../places", optional = true }
|
||||
logins = { path = "../logins", optional = true }
|
||||
ffi-support = { path = "../support/ffi" }
|
||||
failure = "0.1.5"
|
||||
error-support = { path = "../support/error" }
|
||||
prost = "0.5.0"
|
||||
prost-derive = "0.5.0"
|
||||
bytes = "0.4.12"
|
||||
lazy_static = "1.3.0"
|
||||
log = "0.4.7"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.5.0"
|
|
@ -0,0 +1,120 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'com.google.protobuf'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.build.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.build['minSdkVersion']
|
||||
targetSdkVersion rootProject.ext.build['targetSdkVersion']
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
buildConfigField("String", "LIBRARY_VERSION", "\"${rootProject.ext.library.version}\"")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
consumerProguardFiles "$rootDir/proguard-rules-consumer-jna.pro"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
test.resources.srcDirs += "$buildDir/rustJniLibs/desktop"
|
||||
test.resources.srcDirs += "${project(':full-megazord').buildDir}/rustJniLibs/desktop"
|
||||
|
||||
main {
|
||||
proto {
|
||||
srcDir '../src'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
// There's an interaction between Gradle's resolution of dependencies with different types
|
||||
// (@jar, @aar) for `implementation` and `testImplementation` and with Android Studio's built-in
|
||||
// JUnit test runner. The runtime classpath in the built-in JUnit test runner gets the
|
||||
// dependency from the `implementation`, which is type @aar, and therefore the JNA dependency
|
||||
// doesn't provide the JNI dispatch libraries in the correct Java resource directories. I think
|
||||
// what's happening is that @aar type in `implementation` resolves to the @jar type in
|
||||
// `testImplementation`, and that it wins the dependency resolution battle.
|
||||
//
|
||||
// A workaround is to add a new configuration which depends on the @jar type and to reference
|
||||
// the underlying JAR file directly in `testImplementation`. This JAR file doesn't resolve to
|
||||
// the @aar type in `implementation`. This works when invoked via `gradle`, but also sets the
|
||||
// correct runtime classpath when invoked with Android Studio's built-in JUnit test runner.
|
||||
// Success!
|
||||
jnaForTest
|
||||
}
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = 'com.google.protobuf:protoc:3.0.0'
|
||||
}
|
||||
plugins {
|
||||
javalite {
|
||||
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
all().each { task ->
|
||||
task.builtins {
|
||||
remove java
|
||||
}
|
||||
task.plugins {
|
||||
javalite { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Part of the public API.
|
||||
api project(':sync15')
|
||||
|
||||
jnaForTest "net.java.dev.jna:jna:$jna_version@jar"
|
||||
implementation "net.java.dev.jna:jna:$jna_version@aar"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation 'com.google.protobuf:protobuf-lite:3.0.0'
|
||||
api project(":full-megazord")
|
||||
implementation project(":native-support")
|
||||
|
||||
// For reasons unknown, resolving the jnaForTest configuration directly
|
||||
// trips a nasty issue with the Android-Gradle plugin 3.2.1, like `Cannot
|
||||
// change attributes of configuration ':PROJECT:kapt' after it has been
|
||||
// resolved`. I think that the configuration is being made a
|
||||
// super-configuration of the testImplementation and then the `.files` is
|
||||
// causing it to be resolved. Cloning first dissociates the configuration,
|
||||
// avoiding other configurations from being resolved. Tricky!
|
||||
testImplementation files(configurations.jnaForTest.copyRecursive().files)
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.robolectric:robolectric:3.8'
|
||||
testImplementation 'org.mockito:mockito-core:2.21.0'
|
||||
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
// The `cargoBuild` task isn't available until after evaluation.
|
||||
android.libraryVariants.all { variant ->
|
||||
def productFlavor = ""
|
||||
variant.productFlavors.each {
|
||||
productFlavor += "${it.name.capitalize()}"
|
||||
}
|
||||
def buildType = "${variant.buildType.name.capitalize()}"
|
||||
tasks["generate${productFlavor}${buildType}Assets"].dependsOn(project(':full-megazord').tasks["cargoBuild"])
|
||||
|
||||
// For unit tests.
|
||||
tasks["process${productFlavor}${buildType}UnitTestJavaRes"].dependsOn(project(':full-megazord').tasks["cargoBuild"])
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "$rootDir/publish.gradle"
|
||||
|
||||
ext.configurePublish()
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,2 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.mozilla.appservices.syncmanager" />
|
|
@ -0,0 +1,41 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.syncmanager
|
||||
|
||||
/**
|
||||
* Base class for sync manager errors. Generally should not
|
||||
* have concrete instances.
|
||||
*/
|
||||
open class SyncManagerException(msg: String) : Exception(msg)
|
||||
|
||||
/**
|
||||
* General catch-all error, generally indicating API misuse of some sort.
|
||||
*/
|
||||
open class UnexpectedError(msg: String) : SyncManagerException(msg)
|
||||
|
||||
/**
|
||||
* The sync manager paniced. Please report these.
|
||||
*/
|
||||
open class InternalPanic(msg: String) : SyncManagerException(msg)
|
||||
|
||||
/**
|
||||
* We were asked to sync an engine which is either unknown, or which the sync
|
||||
* manager was not compiled with support for (message will elaborate).
|
||||
*/
|
||||
open class UnsupportedEngine(msg: String) : SyncManagerException(msg)
|
||||
|
||||
/**
|
||||
* We were asked to sync an engine but we couldn't because the connection
|
||||
*
|
||||
* Note: When not syncing, the manager holds a weak reference to connection
|
||||
* objects, and so performing something like: `SyncManager.setLogins(handle)`,
|
||||
* closing/locking the logins connection, and then trying to sync logins will
|
||||
* produce this error.
|
||||
*
|
||||
* TODO: Should this be an error reported in SyncResult and not cause the sync
|
||||
* to fail? It's probably a bug in the caller (they should call `setBlah` first)...
|
||||
*/
|
||||
open class ClosedEngine(msg: String) : SyncManagerException(msg)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.syncmanager
|
||||
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Pointer
|
||||
import com.sun.jna.PointerType
|
||||
import com.sun.jna.StringArray
|
||||
import mozilla.appservices.support.native.RustBuffer
|
||||
import mozilla.appservices.support.native.loadIndirect
|
||||
import org.mozilla.appservices.syncmanager.BuildConfig
|
||||
|
||||
@Suppress("FunctionNaming", "FunctionParameterNaming", "LongParameterList", "TooGenericExceptionThrown")
|
||||
internal interface LibSyncManagerFFI : Library {
|
||||
companion object {
|
||||
internal var INSTANCE: LibSyncManagerFFI =
|
||||
loadIndirect(componentName = "syncmanager", componentVersion = BuildConfig.LIBRARY_VERSION)
|
||||
}
|
||||
fun sync_manager_set_places(handle: PlacesApiHandle, error: RustError.ByReference)
|
||||
fun sync_manager_set_logins(handle: LoginsDbHandle, error: RustError.ByReference)
|
||||
fun sync_manager_disconnect(error: RustError.ByReference)
|
||||
|
||||
fun sync_manager_sync(data: Pointer, len: Int, error: RustError.ByReference): RustBuffer.ByValue
|
||||
|
||||
fun sync_manager_destroy_string(s: Pointer)
|
||||
fun sync_manager_destroy_bytebuffer(bb: RustBuffer.ByValue)
|
||||
}
|
||||
|
||||
internal typealias PlacesApiHandle = Long
|
||||
internal typealias LoginsDbHandle = Long
|
|
@ -0,0 +1,69 @@
|
|||
/* Copyright 2018 Mozilla
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
* this file except in compliance with the License. You may obtain a copy of the
|
||||
* License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License. */
|
||||
package mozilla.appservices.syncmanager
|
||||
|
||||
import com.sun.jna.Pointer
|
||||
import com.sun.jna.Structure
|
||||
|
||||
@Structure.FieldOrder("code", "message")
|
||||
internal open class RustError : Structure() {
|
||||
|
||||
class ByReference : RustError(), Structure.ByReference
|
||||
|
||||
@JvmField var code: Int = 0
|
||||
@JvmField var message: Pointer? = null
|
||||
|
||||
fun isSuccess(): Boolean {
|
||||
return code == 0
|
||||
}
|
||||
|
||||
fun isFailure(): Boolean {
|
||||
return code != 0
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "ReturnCount", "TooGenericExceptionThrown")
|
||||
fun intoException(): SyncManagerException {
|
||||
if (!isFailure()) {
|
||||
// It's probably a bad idea to throw here! We're probably leaking something if this is
|
||||
// ever hit! (But we shouldn't ever hit it?)
|
||||
throw RuntimeException("[Bug] intoException called on non-failure!")
|
||||
}
|
||||
val message = this.consumeErrorMessage()
|
||||
when (code) {
|
||||
2 -> return UnsupportedEngine(message)
|
||||
3 -> return ClosedEngine(message)
|
||||
-1 -> return InternalPanic(message)
|
||||
// Note: `1` is used as a generic catch all, but we
|
||||
// might as well handle the others the same way.
|
||||
else -> return UnexpectedError(message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and consume the error message, or null if there is none.
|
||||
*/
|
||||
fun consumeErrorMessage(): String {
|
||||
val result = this.getMessage()
|
||||
if (this.message != null) {
|
||||
LibSyncManagerFFI.INSTANCE.sync_manager_destroy_string(this.message!!)
|
||||
this.message = null
|
||||
}
|
||||
if (result == null) {
|
||||
throw NullPointerException("consumeErrorMessage called with null message!")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error message or null if there is none.
|
||||
*/
|
||||
fun getMessage(): String? {
|
||||
return this.message?.getString(0, "utf8")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.syncmanager
|
||||
|
||||
import com.sun.jna.Native
|
||||
import mozilla.appservices.support.native.toNioDirectBuffer
|
||||
|
||||
object SyncManager {
|
||||
|
||||
/**
|
||||
* Point the manager at the implementation of `PlacesApi` to use.
|
||||
*
|
||||
* @param placesApiHandle A value returned by `PlacesApi.getHandle()`
|
||||
* @throws [UnsupportedEngine] If the manager was not compiled with places support.
|
||||
*/
|
||||
fun setPlaces(placesApiHandle: Long) {
|
||||
rustCall { err ->
|
||||
LibSyncManagerFFI.INSTANCE.sync_manager_set_places(placesApiHandle, err)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Point the manager at the implementation of `DatabaseLoginsStorage` to use.
|
||||
*
|
||||
* @param loginsDbHandle A value returned by `DatabaseLoginsStorage.getHandle()`
|
||||
* @throws [UnsupportedEngine] If the manager was not compiled with logins support.
|
||||
*/
|
||||
fun setLogins(loginsDbHandle: Long) {
|
||||
rustCall { err ->
|
||||
LibSyncManagerFFI.INSTANCE.sync_manager_set_logins(loginsDbHandle, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect this device from sync. This essentially clears shared state having to do with
|
||||
* sync, as well as each engines sync-specific local state
|
||||
*/
|
||||
fun disconnect() {
|
||||
rustCall { err ->
|
||||
LibSyncManagerFFI.INSTANCE.sync_manager_disconnect(err)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Perform a sync.
|
||||
*/
|
||||
fun sync(params: SyncParams): SyncResult {
|
||||
val buf = params.toProtobuf()
|
||||
val (nioBuf, len) = buf.toNioDirectBuffer()
|
||||
val rustBuf = rustCall { err ->
|
||||
val ptr = Native.getDirectBufferPointer(nioBuf)
|
||||
LibSyncManagerFFI.INSTANCE.sync_manager_sync(ptr, len, err)
|
||||
}
|
||||
|
||||
try {
|
||||
val stream = rustBuf.asCodedInputStream()
|
||||
return SyncResult.fromProtobuf(MsgTypes.SyncResult.parseFrom(stream))
|
||||
} finally {
|
||||
LibSyncManagerFFI.INSTANCE.sync_manager_destroy_bytebuffer(rustBuf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <U> rustCall(callback: (RustError.ByReference) -> U): U {
|
||||
val e = RustError.ByReference()
|
||||
val ret: U = callback(e)
|
||||
if (e.isFailure()) {
|
||||
throw e.intoException()
|
||||
} else {
|
||||
return ret
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.syncmanager
|
||||
|
||||
/**
|
||||
* Reason for syncing.
|
||||
*/
|
||||
enum class SyncReason {
|
||||
/**
|
||||
* This is a scheduled sync
|
||||
*/
|
||||
SCHEDULED,
|
||||
/**
|
||||
* This is a manually triggered sync invoked by the user.
|
||||
*/
|
||||
USER,
|
||||
/**
|
||||
* This is a sync that is running optimistically before
|
||||
* the device goes to sleep / is backgrounded.
|
||||
*/
|
||||
PRE_SLEEP,
|
||||
/**
|
||||
* This is a sync that is run on application startup.
|
||||
*/
|
||||
STARTUP,
|
||||
/**
|
||||
* This is a sync that is being performed simply to update the
|
||||
* enabled state of one or more engines.
|
||||
*/
|
||||
ENABLED_CHANGE,
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for providing the auth-related information needed to sync.
|
||||
*/
|
||||
data class SyncAuthInfo(
|
||||
val kid: String,
|
||||
val fxaAccessToken: String,
|
||||
val syncKey: String,
|
||||
val tokenserverURL: String
|
||||
)
|
||||
|
||||
/**
|
||||
* Parameters to use for syncing.
|
||||
*/
|
||||
data class SyncParams(
|
||||
/**
|
||||
* The reason we're syncing.
|
||||
*/
|
||||
val reason: SyncReason,
|
||||
/**
|
||||
* The list of engines to sync.
|
||||
*
|
||||
* Engine names are lowercase, and refer to the server-side engine name, e.g.
|
||||
* "passwords" (not "logins"!), "bookmarks", "history", etc.
|
||||
*
|
||||
* Requesting that we sync an unknown engine type will result in a
|
||||
* [UnsupportedEngine] error.
|
||||
*
|
||||
* Passing `null` here is used to indicate that all known engines
|
||||
* should be synced.
|
||||
*/
|
||||
val engines: List<String>?,
|
||||
|
||||
/**
|
||||
* A map of engine name to new-enabled-state. That is,
|
||||
*
|
||||
* - The map should be empty to indicate "no changes"
|
||||
*
|
||||
* - The map should have `enginename: true` if an engine named
|
||||
* `enginename` should be enabled.
|
||||
*
|
||||
* - The map should have `enginename: false` if an engine named
|
||||
* `enginename` should be disabled.
|
||||
*/
|
||||
val enabledChanges: Map<String, Boolean>,
|
||||
|
||||
/**
|
||||
* The information used to authenticate with the sync server.
|
||||
*/
|
||||
val authInfo: SyncAuthInfo,
|
||||
|
||||
/**
|
||||
* The previously persisted sync state (from `SyncResult.persistedState`),
|
||||
* if any exists.
|
||||
*/
|
||||
val persistedState: String?
|
||||
) {
|
||||
internal fun toProtobuf(): MsgTypes.SyncParams {
|
||||
val builder = MsgTypes.SyncParams.newBuilder()
|
||||
|
||||
this.engines?.let {
|
||||
builder.addAllEnginesToSync(it)
|
||||
builder.syncAllEngines = false
|
||||
} ?: run {
|
||||
// Null `engines`, sync everything.
|
||||
builder.syncAllEngines = true
|
||||
}
|
||||
|
||||
builder.reason = when (this.reason) {
|
||||
SyncReason.SCHEDULED -> MsgTypes.SyncReason.SCHEDULED
|
||||
SyncReason.USER -> MsgTypes.SyncReason.USER
|
||||
SyncReason.PRE_SLEEP -> MsgTypes.SyncReason.PRE_SLEEP
|
||||
SyncReason.STARTUP -> MsgTypes.SyncReason.STARTUP
|
||||
SyncReason.ENABLED_CHANGE -> MsgTypes.SyncReason.ENABLED_CHANGE
|
||||
}
|
||||
|
||||
builder.putAllEnginesToChangeState(this.enabledChanges)
|
||||
|
||||
builder.acctAccessToken = this.authInfo.fxaAccessToken
|
||||
builder.acctSyncKey = this.authInfo.syncKey
|
||||
builder.acctKeyId = this.authInfo.kid
|
||||
builder.acctTokenserverUrl = this.authInfo.tokenserverURL
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.syncmanager
|
||||
|
||||
import mozilla.appservices.sync15.SyncTelemetryPing
|
||||
|
||||
/**
|
||||
* Indicates, at a high level whether the sync succeeded or failed.
|
||||
*/
|
||||
enum class SyncServiceStatus {
|
||||
/**
|
||||
* The sync did not fail.
|
||||
*/
|
||||
OK,
|
||||
|
||||
/**
|
||||
* The sync failed due to network problems.
|
||||
*/
|
||||
NETWORK_ERROR,
|
||||
|
||||
/**
|
||||
* The sync failed due to some apparent error with the servers.
|
||||
*/
|
||||
SERVICE_ERROR,
|
||||
|
||||
/**
|
||||
* The auth information we were provided was somehow invalid. Refreshing it
|
||||
* with FxA may resolve this issue.
|
||||
*/
|
||||
AUTH_ERROR,
|
||||
|
||||
/**
|
||||
* Indicates that we declined to sync because the server had requested a
|
||||
* backoff which has not yet expired.
|
||||
*/
|
||||
BACKED_OFF,
|
||||
|
||||
/**
|
||||
* Some other error occurred.
|
||||
*/
|
||||
OTHER_ERROR,
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a sync.
|
||||
*/
|
||||
data class SyncResult(
|
||||
/**
|
||||
* The general health.
|
||||
*/
|
||||
val status: SyncServiceStatus,
|
||||
|
||||
/**
|
||||
* For engines which failed to sync, contains a string
|
||||
* description of the error.
|
||||
*
|
||||
* The error strings are mostly provided for local debugging,
|
||||
* and more robust information is present in e.g. telemetry.
|
||||
*/
|
||||
val failures: Map<String, String>,
|
||||
|
||||
/**
|
||||
* The list of engines which synced without any errors.
|
||||
*/
|
||||
val successful: List<String>,
|
||||
|
||||
/**
|
||||
* The state string that should be persisted by the caller, and
|
||||
* used as the value for `SyncParams.persistedState` in subsequent
|
||||
* calls to `SyncManager.sync`.
|
||||
*/
|
||||
val persistedState: String,
|
||||
|
||||
/**
|
||||
* The list of engines which have been declined by the user.
|
||||
*
|
||||
* Null if we didn't make it far enough to know.
|
||||
*/
|
||||
val declined: List<String>?,
|
||||
|
||||
/**
|
||||
* The next time we're allowed to sync, in milliseconds since
|
||||
* the unix epoch.
|
||||
*
|
||||
* If this value is in the future, then there's some kind of back-off.
|
||||
* Note that it's not necessary for the app to enforce this, but should
|
||||
* probably be used as an input in the application's sync scheduling logic.
|
||||
*
|
||||
* Syncs before this passes will generally fail with a BACKED_OFF error,
|
||||
* unless they are syncs that were manually requested by the user (that
|
||||
* is, they have the reason `SyncReason.USER`).
|
||||
*/
|
||||
val nextSyncAllowedAt: Long?,
|
||||
|
||||
/**
|
||||
* A bundle of telemetry information recorded during this sync.
|
||||
*/
|
||||
val telemetry: SyncTelemetryPing?
|
||||
) {
|
||||
companion object {
|
||||
internal fun fromProtobuf(pb: MsgTypes.SyncResult): SyncResult {
|
||||
val nextSyncAllowedAt = if (pb.hasNextSyncAllowedAt()) {
|
||||
pb.nextSyncAllowedAt
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val declined = if (pb.haveDeclined) {
|
||||
pb.declinedList
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val successful = pb.resultsMap.entries
|
||||
.filter { it.value.isEmpty() }
|
||||
.map { it.key }
|
||||
.toList()
|
||||
|
||||
val failures = pb.resultsMap.filter { it.value.isNotEmpty() }
|
||||
|
||||
val telemetry = if (pb.hasTelemetryJson()) {
|
||||
SyncTelemetryPing.fromJSONString(pb.telemetryJson)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val status = when (pb.status) {
|
||||
MsgTypes.ServiceStatus.OK -> SyncServiceStatus.OK
|
||||
MsgTypes.ServiceStatus.NETWORK_ERROR -> SyncServiceStatus.NETWORK_ERROR
|
||||
MsgTypes.ServiceStatus.SERVICE_ERROR -> SyncServiceStatus.SERVICE_ERROR
|
||||
MsgTypes.ServiceStatus.AUTH_ERROR -> SyncServiceStatus.AUTH_ERROR
|
||||
MsgTypes.ServiceStatus.BACKED_OFF -> SyncServiceStatus.BACKED_OFF
|
||||
MsgTypes.ServiceStatus.OTHER_ERROR -> SyncServiceStatus.OTHER_ERROR
|
||||
else -> SyncServiceStatus.OTHER_ERROR // impossible *sigh*
|
||||
}
|
||||
|
||||
return SyncResult(
|
||||
status = status,
|
||||
failures = failures,
|
||||
successful = successful,
|
||||
declined = declined,
|
||||
telemetry = telemetry,
|
||||
nextSyncAllowedAt = nextSyncAllowedAt,
|
||||
persistedState = pb.persistedState
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=src/manager_msg_types.proto");
|
||||
prost_build::compile_protos(&["src/manager_msg_types.proto"], &["src/"]).unwrap();
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "sync_manager_ffi"
|
||||
version = "0.1.0"
|
||||
authors = ["application-services <application-services@mozilla.com>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[features]
|
||||
logins = ["sync_manager/logins", "logins_ffi"]
|
||||
places = ["sync_manager/places", "places-ffi"]
|
||||
|
||||
[dependencies]
|
||||
sync_manager = { path = ".." }
|
||||
sync15 = { path = "../../sync15" }
|
||||
ffi-support = { path = "../../support/ffi" }
|
||||
places-ffi = { path = "../../places/ffi", optional = true }
|
||||
logins_ffi = { path = "../../logins/ffi", optional = true }
|
||||
prost = "0.5.0"
|
||||
log = "0.4.7"
|
|
@ -0,0 +1,84 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![allow(unknown_lints)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
// Let's allow these in the FFI code, since it's usually just a coincidence if
|
||||
// the closure is small.
|
||||
#![allow(clippy::redundant_closure)]
|
||||
|
||||
use ffi_support::{ExternError, HandleError};
|
||||
use sync_manager::Result as MgrResult;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sync_manager_set_places(_places_api_handle: u64, error: &mut ExternError) {
|
||||
ffi_support::call_with_result(error, || -> MgrResult<()> {
|
||||
#[cfg(feature = "places")]
|
||||
{
|
||||
let api = places_ffi::APIS
|
||||
.get_u64(_places_api_handle, |api| -> Result<_, HandleError> {
|
||||
Ok(std::sync::Arc::clone(api))
|
||||
})?;
|
||||
sync_manager::set_places(api);
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(feature = "places"))]
|
||||
{
|
||||
log::error!("Sync manager not compiled with places support");
|
||||
Err(sync_manager::ErrorKind::UnsupportedFeature("places".to_string()).into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sync_manager_set_logins(_logins_handle: u64, error: &mut ExternError) {
|
||||
ffi_support::call_with_result(error, || -> MgrResult<()> {
|
||||
#[cfg(feature = "logins")]
|
||||
{
|
||||
let api = logins_ffi::ENGINES
|
||||
.get_u64(_logins_handle, |api| -> Result<_, HandleError> {
|
||||
Ok(std::sync::Arc::clone(api))
|
||||
})?;
|
||||
sync_manager::set_logins(api);
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(feature = "logins"))]
|
||||
{
|
||||
log::error!("Sync manager not compiled with logins support");
|
||||
Err(sync_manager::ErrorKind::UnsupportedFeature("logins".to_string()).into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sync_manager_disconnect(error: &mut ExternError) {
|
||||
ffi_support::call_with_output(error, sync_manager::disconnect);
|
||||
}
|
||||
|
||||
unsafe fn get_buffer<'a>(data: *const u8, len: i32) -> &'a [u8] {
|
||||
assert!(len >= 0, "Bad buffer len: {}", len);
|
||||
if len == 0 {
|
||||
// This will still fail, but as a bad protobuf format.
|
||||
&[]
|
||||
} else {
|
||||
assert!(!data.is_null(), "Unexpected null data pointer");
|
||||
std::slice::from_raw_parts(data, len as usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sync_manager_sync(
|
||||
params_data: *const u8,
|
||||
params_len: i32,
|
||||
error: &mut ExternError,
|
||||
) -> ffi_support::ByteBuffer {
|
||||
ffi_support::call_with_result(error, || {
|
||||
let buffer = get_buffer(params_data, params_len);
|
||||
let params: sync_manager::msg_types::SyncParams = prost::Message::decode(buffer)?;
|
||||
sync_manager::sync(params)
|
||||
})
|
||||
}
|
||||
|
||||
ffi_support::define_string_destructor!(sync_manager_destroy_string);
|
||||
ffi_support::define_bytebuffer_destructor!(sync_manager_destroy_bytebuffer);
|
|
@ -0,0 +1,25 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
use failure::Fail;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum ErrorKind {
|
||||
#[fail(display = "Unknown engine: {}", _0)]
|
||||
UnknownEngine(String),
|
||||
#[fail(display = "Manager was compiled without support for {:?}", _0)]
|
||||
UnsupportedFeature(String),
|
||||
#[fail(display = "Database connection for '{}' is not open", _0)]
|
||||
ConnectionClosed(String),
|
||||
#[fail(display = "Handle is invalid: {}", _0)]
|
||||
InvalidHandle(#[fail(cause)] ffi_support::HandleError),
|
||||
#[fail(display = "Protobuf decode error: {}", _0)]
|
||||
ProtobufDecodeError(#[fail(cause)] prost::DecodeError),
|
||||
}
|
||||
|
||||
error_support::define_error! {
|
||||
ErrorKind {
|
||||
(InvalidHandle, ffi_support::HandleError),
|
||||
(ProtobufDecodeError, prost::DecodeError),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use ffi_support::{ErrorCode, ExternError};
|
||||
|
||||
pub mod error_codes {
|
||||
// Note: 0 (success) and -1 (panic) are reserved by ffi_support
|
||||
pub const UNEXPECTED: i32 = 1;
|
||||
|
||||
/// We were asked to sync an engine, but we either don't know what it is,
|
||||
/// or were compiled without support for it.
|
||||
pub const UNSUPPORTED_ENGINE: i32 = 2;
|
||||
|
||||
/// We were asked to sync an engine which is not open (e.g. Weak::upgrade
|
||||
/// returns None).
|
||||
pub const ENGINE_NOT_OPEN: i32 = 3;
|
||||
}
|
||||
|
||||
fn get_code(err: &Error) -> ErrorCode {
|
||||
match err.kind() {
|
||||
ErrorKind::UnknownEngine(e) => {
|
||||
log::error!("Unknown engine: {}", e);
|
||||
ErrorCode::new(error_codes::UNSUPPORTED_ENGINE)
|
||||
}
|
||||
ErrorKind::UnsupportedFeature(f) => {
|
||||
log::error!("Unsupported feature: {}", f);
|
||||
ErrorCode::new(error_codes::UNSUPPORTED_ENGINE)
|
||||
}
|
||||
ErrorKind::ConnectionClosed(e) => {
|
||||
log::error!("Connection closed: {}", e);
|
||||
ErrorCode::new(error_codes::ENGINE_NOT_OPEN)
|
||||
}
|
||||
err => {
|
||||
log::error!("Unexpected error: {}", err);
|
||||
ErrorCode::new(error_codes::UNEXPECTED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for ExternError {
|
||||
fn from(e: Error) -> ExternError {
|
||||
ExternError::new_error(get_code(&e), e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
ffi_support::implement_into_ffi_by_protobuf!(crate::msg_types::SyncResult);
|
||||
ffi_support::implement_into_ffi_by_protobuf!(crate::msg_types::SyncParams);
|
|
@ -0,0 +1,53 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![allow(unknown_lints)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
pub mod error;
|
||||
mod ffi;
|
||||
mod manager;
|
||||
|
||||
pub use error::{Error, ErrorKind, Result};
|
||||
|
||||
pub mod msg_types {
|
||||
include!(concat!(env!("OUT_DIR"), "/msg_types.rs"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "logins")]
|
||||
use logins::PasswordEngine;
|
||||
use manager::SyncManager;
|
||||
#[cfg(feature = "places")]
|
||||
use places::PlacesApi;
|
||||
#[cfg(any(feature = "places", feature = "logins"))]
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref MANAGER: Mutex<SyncManager> = Mutex::new(SyncManager::new());
|
||||
}
|
||||
|
||||
#[cfg(feature = "places")]
|
||||
pub fn set_places(places: Arc<PlacesApi>) {
|
||||
let mut manager = MANAGER.lock().unwrap();
|
||||
manager.set_places(places);
|
||||
}
|
||||
|
||||
#[cfg(feature = "logins")]
|
||||
pub fn set_logins(places: Arc<Mutex<PasswordEngine>>) {
|
||||
let mut manager = MANAGER.lock().unwrap();
|
||||
manager.set_logins(places);
|
||||
}
|
||||
|
||||
pub fn disconnect() {
|
||||
let mut manager = MANAGER.lock().unwrap();
|
||||
manager.disconnect();
|
||||
}
|
||||
|
||||
pub fn sync(params: msg_types::SyncParams) -> Result<msg_types::SyncResult> {
|
||||
let mut manager = MANAGER.lock().unwrap();
|
||||
// TODO: translate the protobuf message into something nicer to work with in
|
||||
// Rust.
|
||||
manager.sync(params)
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#[cfg(feature = "places")]
|
||||
use places::PlacesApi;
|
||||
// use sql_support::SqlInterruptHandle;
|
||||
use crate::error::*;
|
||||
use crate::msg_types::{SyncParams, SyncResult};
|
||||
#[cfg(feature = "logins")]
|
||||
use logins::PasswordEngine;
|
||||
#[cfg(feature = "logins")]
|
||||
use std::sync::Mutex;
|
||||
#[cfg(any(feature = "places", feature = "logins"))]
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
pub struct SyncManager {
|
||||
#[cfg(feature = "places")]
|
||||
places: Weak<PlacesApi>,
|
||||
#[cfg(feature = "logins")]
|
||||
logins: Weak<Mutex<PasswordEngine>>,
|
||||
}
|
||||
|
||||
impl SyncManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "places")]
|
||||
places: Weak::new(),
|
||||
#[cfg(feature = "logins")]
|
||||
logins: Weak::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "places")]
|
||||
pub fn set_places(&mut self, places: Arc<PlacesApi>) {
|
||||
self.places = Arc::downgrade(&places);
|
||||
}
|
||||
|
||||
#[cfg(feature = "logins")]
|
||||
pub fn set_logins(&mut self, logins: Arc<Mutex<PasswordEngine>>) {
|
||||
self.logins = Arc::downgrade(&logins);
|
||||
}
|
||||
|
||||
pub fn disconnect(&mut self) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
pub fn sync(&mut self, params: SyncParams) -> Result<SyncResult> {
|
||||
check_engine_list(¶ms.engines_to_sync)?;
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
fn check_engine_list(list: &[String]) -> Result<()> {
|
||||
for e in list {
|
||||
if e == "bookmarks" || e == "history" {
|
||||
if cfg!(not(feature = "places")) {
|
||||
return Err(ErrorKind::UnsupportedFeature(e.to_string()).into());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if e == "passwords" {
|
||||
if cfg!(not(feature = "logins")) {
|
||||
return Err(ErrorKind::UnsupportedFeature(e.to_string()).into());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return Err(ErrorKind::UnknownEngine(e.to_string()).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
syntax = "proto2";
|
||||
|
||||
// Note: this file name must be unique due to how the iOS megazord works :(
|
||||
|
||||
package msg_types;
|
||||
|
||||
option java_package = "mozilla.appservices.syncmanager";
|
||||
option java_outer_classname = "MsgTypes";
|
||||
|
||||
enum SyncReason {
|
||||
SCHEDULED = 1;
|
||||
USER = 2;
|
||||
PRE_SLEEP = 3;
|
||||
STARTUP = 4;
|
||||
ENABLED_CHANGE = 5;
|
||||
}
|
||||
|
||||
message SyncParams {
|
||||
repeated string engines_to_sync = 1;
|
||||
required bool sync_all_engines = 2;
|
||||
|
||||
required SyncReason reason = 3;
|
||||
|
||||
map<string, bool> engines_to_change_state = 4;
|
||||
|
||||
optional string persisted_state = 5;
|
||||
|
||||
// These conceptually are a nested type, but exposing them as such would add
|
||||
// needless complexity to the FFI.
|
||||
required string acct_key_id = 6;
|
||||
required string acct_access_token = 7;
|
||||
required string acct_tokenserver_url = 8;
|
||||
required string acct_sync_key = 9;
|
||||
}
|
||||
|
||||
enum ServiceStatus {
|
||||
OK = 1;
|
||||
NETWORK_ERROR = 2;
|
||||
SERVICE_ERROR = 3;
|
||||
AUTH_ERROR = 4;
|
||||
BACKED_OFF = 5;
|
||||
OTHER_ERROR = 6;
|
||||
}
|
||||
|
||||
message SyncResult {
|
||||
required ServiceStatus status = 1;
|
||||
map<string, string> results = 2; // empty string used for 'no error'
|
||||
|
||||
repeated string declined = 3;
|
||||
// false if we didn't manage to check declined.
|
||||
required bool have_declined = 4;
|
||||
|
||||
optional int64 next_sync_allowed_at = 5;
|
||||
required string persisted_state = 6;
|
||||
optional string telemetry_json = 7;
|
||||
}
|
|
@ -15,3 +15,4 @@ places-ffi = { path = "../../components/places/ffi" }
|
|||
push-ffi = { path = "../../components/push/ffi" }
|
||||
rc_log_ffi = { path = "../../components/rc_log" }
|
||||
viaduct = { path = "../../components/viaduct", default-features = false }
|
||||
sync_manager_ffi = { path = "../../components/sync_manager/ffi", features = ["places"] }
|
||||
|
|
|
@ -10,4 +10,5 @@ pub use logins_ffi;
|
|||
pub use places_ffi;
|
||||
pub use push_ffi;
|
||||
pub use rc_log_ffi;
|
||||
pub use sync_manager_ffi;
|
||||
pub use viaduct;
|
||||
|
|
|
@ -15,4 +15,5 @@ places-ffi = { path = "../../components/places/ffi" }
|
|||
push-ffi = { path = "../../components/push/ffi" }
|
||||
rc_log_ffi = { path = "../../components/rc_log" }
|
||||
viaduct = { path = "../../components/viaduct", default_features = false }
|
||||
sync_manager_ffi = { path = "../../components/sync_manager/ffi", features = ["logins", "places"] }
|
||||
lazy_static = "1.4.0"
|
||||
|
|
|
@ -13,6 +13,7 @@ pub use logins_ffi;
|
|||
pub use places_ffi;
|
||||
pub use push_ffi;
|
||||
pub use rc_log_ffi;
|
||||
pub use sync_manager_ffi;
|
||||
pub use viaduct;
|
||||
|
||||
/// In order to support the use case of consumers who don't know about megazords
|
||||
|
|
|
@ -13,3 +13,4 @@ fxaclient_ffi = { path = "../../components/fxa-client/ffi" }
|
|||
logins_ffi = { path = "../../components/logins/ffi" }
|
||||
rc_log_ffi = { path = "../../components/rc_log" }
|
||||
viaduct = { path = "../../components/viaduct", default-features = false }
|
||||
sync_manager_ffi = { path = "../../components/sync_manager/ffi", features = ["logins"] }
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
pub use fxaclient_ffi;
|
||||
pub use logins_ffi;
|
||||
pub use rc_log_ffi;
|
||||
pub use sync_manager_ffi;
|
||||
pub use viaduct;
|
||||
|
|
Загрузка…
Ссылка в новой задаче