mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-01 17:39:55 +08:00
c9bffc13b5
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 -->
183 lines
4.9 KiB
Rust
183 lines
4.9 KiB
Rust
use super::*;
|
|
|
|
#[uniffi::export(async_runtime = "tokio")]
|
|
impl DocStoragePool {
|
|
pub async fn get_blob(&self, universal_id: String, key: String) -> Result<Option<Blob>> {
|
|
#[cfg(any(target_os = "android", target_os = "ios", test))]
|
|
{
|
|
use affine_nbstore::Blob as NbBlob;
|
|
enum BlobEncodeOutcome {
|
|
Cached(Blob),
|
|
Inline(NbBlob),
|
|
}
|
|
|
|
let universal_id_for_cache = universal_id.clone();
|
|
let key_for_cache = key.clone();
|
|
if let Ok(Some(blob)) = self
|
|
.run_mobile_cache_io(
|
|
move |cache| Ok(cache.get_blob(&universal_id_for_cache, &key_for_cache)),
|
|
"Failed to read mobile blob cache",
|
|
)
|
|
.await
|
|
{
|
|
return Ok(Some(blob));
|
|
}
|
|
|
|
let Some(blob) = self
|
|
.inner
|
|
.get(universal_id.clone())
|
|
.await?
|
|
.get_blob(key.clone())
|
|
.await?
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
|
|
if !should_cache_payload_as_file(blob.data.len()) {
|
|
return Ok(Some(blob.into()));
|
|
}
|
|
|
|
let universal_id_for_cache = universal_id.clone();
|
|
let key_for_fallback = key.clone();
|
|
return match self
|
|
.run_mobile_cache_io(
|
|
move |cache| {
|
|
Ok(match cache.cache_blob(&universal_id_for_cache, &blob) {
|
|
Ok(cached) => BlobEncodeOutcome::Cached(cached),
|
|
Err(_) => BlobEncodeOutcome::Inline(blob),
|
|
})
|
|
},
|
|
"Failed to cache blob file",
|
|
)
|
|
.await
|
|
{
|
|
Ok(BlobEncodeOutcome::Cached(cached)) => Ok(Some(cached)),
|
|
Ok(BlobEncodeOutcome::Inline(blob)) => Ok(Some(blob.into())),
|
|
Err(_) => Ok(
|
|
self
|
|
.inner
|
|
.get(universal_id)
|
|
.await?
|
|
.get_blob(key_for_fallback)
|
|
.await?
|
|
.map(Into::into),
|
|
),
|
|
};
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "android", target_os = "ios", test)))]
|
|
{
|
|
Ok(self.inner.get(universal_id).await?.get_blob(key).await?.map(Into::into))
|
|
}
|
|
}
|
|
|
|
pub async fn set_blob(&self, universal_id: String, blob: SetBlob) -> Result<()> {
|
|
#[cfg(any(target_os = "android", target_os = "ios", test))]
|
|
let key = blob.key.clone();
|
|
let blob = NbSetBlob {
|
|
key: blob.key,
|
|
data: Into::<Data>::into(self.decode_blob_data(&universal_id, &blob.data).await?),
|
|
mime: blob.mime,
|
|
};
|
|
self.inner.get(universal_id.clone()).await?.set_blob(blob).await?;
|
|
#[cfg(any(target_os = "android", target_os = "ios", test))]
|
|
{
|
|
let universal_id_for_cache = universal_id;
|
|
let _ = self
|
|
.run_mobile_cache_io(
|
|
move |cache| {
|
|
cache.invalidate_blob(&universal_id_for_cache, &key);
|
|
Ok(())
|
|
},
|
|
"Failed to invalidate mobile blob cache entry",
|
|
)
|
|
.await;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn delete_blob(&self, universal_id: String, key: String, permanently: bool) -> Result<()> {
|
|
self
|
|
.inner
|
|
.get(universal_id.clone())
|
|
.await?
|
|
.delete_blob(key.clone(), permanently)
|
|
.await?;
|
|
#[cfg(any(target_os = "android", target_os = "ios", test))]
|
|
{
|
|
let universal_id_for_cache = universal_id;
|
|
let _ = self
|
|
.run_mobile_cache_io(
|
|
move |cache| {
|
|
cache.invalidate_blob(&universal_id_for_cache, &key);
|
|
Ok(())
|
|
},
|
|
"Failed to invalidate mobile blob cache entry",
|
|
)
|
|
.await;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn release_blobs(&self, universal_id: String) -> Result<()> {
|
|
self.inner.get(universal_id.clone()).await?.release_blobs().await?;
|
|
#[cfg(any(target_os = "android", target_os = "ios", test))]
|
|
{
|
|
let universal_id_for_cache = universal_id;
|
|
let _ = self
|
|
.run_mobile_cache_io(
|
|
move |cache| {
|
|
cache.clear_workspace_cache(&universal_id_for_cache);
|
|
Ok(())
|
|
},
|
|
"Failed to clear mobile blob cache workspace",
|
|
)
|
|
.await;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn list_blobs(&self, universal_id: String) -> Result<Vec<ListedBlob>> {
|
|
Ok(
|
|
self
|
|
.inner
|
|
.get(universal_id)
|
|
.await?
|
|
.list_blobs()
|
|
.await?
|
|
.into_iter()
|
|
.map(Into::into)
|
|
.collect(),
|
|
)
|
|
}
|
|
|
|
pub async fn set_blob_uploaded_at(
|
|
&self,
|
|
universal_id: String,
|
|
peer: String,
|
|
blob_id: String,
|
|
uploaded_at: Option<i64>,
|
|
) -> Result<()> {
|
|
Ok(
|
|
self
|
|
.inner
|
|
.get(universal_id)
|
|
.await?
|
|
.set_blob_uploaded_at(peer, blob_id, uploaded_at.map(millis_to_naive_utc).transpose()?)
|
|
.await?,
|
|
)
|
|
}
|
|
|
|
pub async fn get_blob_uploaded_at(&self, universal_id: String, peer: String, blob_id: String) -> Result<Option<i64>> {
|
|
Ok(
|
|
self
|
|
.inner
|
|
.get(universal_id)
|
|
.await?
|
|
.get_blob_uploaded_at(peer, blob_id)
|
|
.await?
|
|
.map(|t| t.and_utc().timestamp_millis()),
|
|
)
|
|
}
|
|
}
|