mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-10 05:29:08 +08:00
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:
@@ -0,0 +1,86 @@
|
||||
use std::{
|
||||
fs,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use affine_nbstore::DocUpdate as NbDocUpdate;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::{UniffiError, cache, ffi_types::DocUpdate, storage::new_doc_storage_pool};
|
||||
|
||||
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
fn unique_id(prefix: &str) -> String {
|
||||
let counter = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("system clock before unix epoch")
|
||||
.as_nanos();
|
||||
format!("{prefix}-{now}-{counter}")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doc_update_roundtrip_base64() {
|
||||
let timestamp = DateTime::<Utc>::from_timestamp_millis(1_700_000_000_000)
|
||||
.unwrap()
|
||||
.naive_utc();
|
||||
let original = NbDocUpdate {
|
||||
doc_id: "doc-1".to_string(),
|
||||
timestamp,
|
||||
bin: vec![1, 2, 3, 4, 5],
|
||||
};
|
||||
|
||||
let encoded: DocUpdate = original.into();
|
||||
let decoded = NbDocUpdate::try_from(encoded).unwrap();
|
||||
|
||||
assert_eq!(decoded.doc_id, "doc-1");
|
||||
assert_eq!(decoded.timestamp, timestamp);
|
||||
assert_eq!(decoded.bin, vec![1, 2, 3, 4, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doc_update_rejects_invalid_base64() {
|
||||
let update = DocUpdate {
|
||||
doc_id: "doc-2".to_string(),
|
||||
timestamp: 0,
|
||||
bin: "not-base64!!".to_string(),
|
||||
};
|
||||
|
||||
let err = match NbDocUpdate::try_from(update) {
|
||||
Ok(_) => panic!("expected base64 decode error"),
|
||||
Err(err) => err,
|
||||
};
|
||||
match err {
|
||||
UniffiError::Base64DecodingError(_) => {}
|
||||
other => panic!("unexpected error: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn decode_blob_data_rejects_out_of_workspace_path() {
|
||||
let pool = new_doc_storage_pool();
|
||||
let universal_id = unique_id("mobile-doc-outside");
|
||||
pool
|
||||
.connect(universal_id.clone(), ":memory:".to_string())
|
||||
.await
|
||||
.expect("connect should succeed");
|
||||
|
||||
let outside_dir = std::env::temp_dir().join(unique_id("mobile-doc-outside-dir"));
|
||||
fs::create_dir_all(&outside_dir).expect("create outside dir");
|
||||
let outside_file = outside_dir.join("1234567890abcdef.blob");
|
||||
fs::write(&outside_file, b"outside").expect("write outside file");
|
||||
let token = format!("{}{}", cache::MOBILE_BLOB_FILE_PREFIX, outside_file.display());
|
||||
|
||||
let err = pool
|
||||
.decode_blob_data(&universal_id, &token)
|
||||
.await
|
||||
.expect_err("decode should reject out-of-workspace token");
|
||||
let UniffiError::Err(message) = err else {
|
||||
panic!("unexpected error kind");
|
||||
};
|
||||
assert!(message.contains("outside the workspace cache directory"));
|
||||
|
||||
pool.disconnect(universal_id).await.expect("disconnect should succeed");
|
||||
let _ = fs::remove_dir_all(outside_dir);
|
||||
}
|
||||
Reference in New Issue
Block a user