feat: improve mobile native impl (#14481)
fix #13529 #### PR Dependency Tree * **PR #14481** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Mobile blob caching with file-backed storage for faster loads and reduced network usage * Blob decoding with lazy refresh on token-read failures for improved reliability * Full-text search/indexing exposed to mobile apps * Document sync APIs and peer clock management for robust cross-device sync * **Tests** * Added unit tests covering payload decoding, cache safety, and concurrency * **Dependencies** * Added an LRU cache dependency and a new mobile-shared package for shared mobile logic <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -167,14 +167,23 @@ afterEvaluate {
|
||||
}
|
||||
def buildType = "${variant.buildType.name.capitalize()}"
|
||||
tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild"])
|
||||
def variantName = "${variant.name.substring(0,1).toUpperCase()}${variant.name.substring(1)}"
|
||||
def mergeNativeLibsTask = tasks.findByName("merge${variantName}NativeLibs")
|
||||
if (mergeNativeLibsTask != null) {
|
||||
mergeNativeLibsTask.dependsOn(tasks["cargoBuild"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def localProps = new Properties()
|
||||
file("${rootProject.projectDir}/local.properties").withInputStream { localProps.load(it) }
|
||||
def cargoPath = localProps.getProperty("rust.cargoCommand") ?: 'cargo'
|
||||
android.applicationVariants.configureEach { variant ->
|
||||
def t = tasks.register("generate${variant.name.capitalize()}UniFFIBindings", Exec) {
|
||||
def variantName = "${variant.name.substring(0,1).toUpperCase()}${variant.name.substring(1)}"
|
||||
def t = tasks.register("generate${variantName}UniFFIBindings", Exec) {
|
||||
workingDir "${project.projectDir}"
|
||||
// Runs the bindings generation, note that you must have uniffi-bindgen installed and in your PATH environment variable
|
||||
commandLine 'cargo', 'run', '--bin', 'uniffi-bindgen', 'generate', '--library', "${buildDir}/rustJniLibs/android/arm64-v8a/libaffine_mobile_native.so", '--language', 'kotlin', '--out-dir', "${project.projectDir}/src/main/java"
|
||||
commandLine "${cargoPath}", 'run', '--bin', 'uniffi-bindgen', 'generate', '--library', "${buildDir}/rustJniLibs/android/arm64-v8a/libaffine_mobile_native.so", '--language', 'kotlin', '--out-dir', "${project.projectDir}/src/main/java"
|
||||
dependsOn("cargoBuild")
|
||||
}
|
||||
variant.javaCompileProvider.get().dependsOn(t)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@affine/core": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@affine/mobile-shared": "workspace:*",
|
||||
"@affine/nbstore": "workspace:*",
|
||||
"@affine/track": "workspace:*",
|
||||
"@blocksuite/affine": "workspace:*",
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { CrawlResult, DocIndexedClock } from '@affine/nbstore';
|
||||
|
||||
export interface Blob {
|
||||
key: string;
|
||||
// base64 encoded data
|
||||
// base64 encoded data, or "__AFFINE_BLOB_FILE__:<absolutePath>" for large blobs
|
||||
data: string;
|
||||
mime: string;
|
||||
size: number;
|
||||
@@ -41,6 +41,7 @@ export interface NbStorePlugin {
|
||||
pushUpdate: (options: {
|
||||
id: string;
|
||||
docId: string;
|
||||
// base64 encoded data
|
||||
data: string;
|
||||
}) => Promise<{ timestamp: number }>;
|
||||
getDocSnapshot: (options: { id: string; docId: string }) => Promise<
|
||||
@@ -55,6 +56,7 @@ export interface NbStorePlugin {
|
||||
setDocSnapshot: (options: {
|
||||
id: string;
|
||||
docId: string;
|
||||
// base64 encoded data
|
||||
bin: string;
|
||||
timestamp: number;
|
||||
}) => Promise<{ success: boolean }>;
|
||||
|
||||
@@ -2,6 +2,10 @@ import {
|
||||
base64ToUint8Array,
|
||||
uint8ArrayToBase64,
|
||||
} from '@affine/core/modules/workspace-engine';
|
||||
import {
|
||||
decodePayload,
|
||||
MOBILE_BLOB_FILE_PREFIX,
|
||||
} from '@affine/mobile-shared/nbstore/payload';
|
||||
import {
|
||||
type BlobRecord,
|
||||
type CrawlResult,
|
||||
@@ -132,14 +136,23 @@ export const NbStoreNativeDBApis: NativeDBApis = {
|
||||
id,
|
||||
key,
|
||||
});
|
||||
return record
|
||||
? {
|
||||
data: base64ToUint8Array(record.data),
|
||||
key: record.key,
|
||||
mime: record.mime,
|
||||
createdAt: new Date(record.createdAt),
|
||||
}
|
||||
: null;
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let refreshedBlobPromise: ReturnType<typeof NbStore.getBlob> | undefined;
|
||||
|
||||
return {
|
||||
data: await decodePayload(record.data, MOBILE_BLOB_FILE_PREFIX, {
|
||||
onTokenReadFailure: async () => {
|
||||
refreshedBlobPromise ??= NbStore.getBlob({ id, key });
|
||||
return (await refreshedBlobPromise)?.data;
|
||||
},
|
||||
}),
|
||||
key: record.key,
|
||||
mime: record.mime,
|
||||
createdAt: new Date(record.createdAt),
|
||||
};
|
||||
},
|
||||
setBlob: async function (id: string, blob: BlobRecord): Promise<void> {
|
||||
await NbStore.setBlob({
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
{ "path": "../../core" },
|
||||
{ "path": "../../../common/env" },
|
||||
{ "path": "../../i18n" },
|
||||
{ "path": "../mobile-shared" },
|
||||
{ "path": "../../../common/nbstore" },
|
||||
{ "path": "../../track" },
|
||||
{ "path": "../../../../blocksuite/affine/all" },
|
||||
|
||||
Reference in New Issue
Block a user