mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: write checkpoint in fixed duration
This commit is contained in:
parent
f6ade11eb2
commit
aeb69f307c
@ -11,11 +11,12 @@ use crate::{
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flowy_sync::client_document::default::{initial_quill_delta_string, initial_read_me};
|
use flowy_sync::client_document::default::{initial_quill_delta_string, initial_read_me};
|
||||||
|
|
||||||
|
use crate::services::folder_editor::FolderRevisionCompactor;
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use flowy_folder_data_model::entities::view::ViewDataType;
|
use flowy_folder_data_model::entities::view::ViewDataType;
|
||||||
use flowy_folder_data_model::user_default;
|
use flowy_folder_data_model::user_default;
|
||||||
use flowy_revision::disk::SQLiteTextBlockRevisionPersistence;
|
use flowy_revision::disk::SQLiteTextBlockRevisionPersistence;
|
||||||
use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket};
|
use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket, SQLiteRevisionHistoryPersistence};
|
||||||
use flowy_sync::{client_folder::FolderPad, entities::ws_data::ServerRevisionWSData};
|
use flowy_sync::{client_folder::FolderPad, entities::ws_data::ServerRevisionWSData};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
@ -162,9 +163,17 @@ impl FolderManager {
|
|||||||
let _ = self.persistence.initialize(user_id, &folder_id).await?;
|
let _ = self.persistence.initialize(user_id, &folder_id).await?;
|
||||||
|
|
||||||
let pool = self.persistence.db_pool()?;
|
let pool = self.persistence.db_pool()?;
|
||||||
let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(user_id, pool));
|
let disk_cache = SQLiteTextBlockRevisionPersistence::new(user_id, pool.clone());
|
||||||
let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache));
|
let rev_persistence = RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache);
|
||||||
let rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence);
|
let rev_compactor = FolderRevisionCompactor();
|
||||||
|
let history_persistence = SQLiteRevisionHistoryPersistence::new(pool);
|
||||||
|
let rev_manager = RevisionManager::new(
|
||||||
|
user_id,
|
||||||
|
folder_id.as_ref(),
|
||||||
|
rev_persistence,
|
||||||
|
rev_compactor,
|
||||||
|
history_persistence,
|
||||||
|
);
|
||||||
|
|
||||||
let folder_editor = FolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?;
|
let folder_editor = FolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?;
|
||||||
*self.folder_editor.write().await = Some(Arc::new(folder_editor));
|
*self.folder_editor.write().await = Some(Arc::new(folder_editor));
|
||||||
|
@ -91,11 +91,7 @@ impl FolderEditor {
|
|||||||
&self.user_id,
|
&self.user_id,
|
||||||
md5,
|
md5,
|
||||||
);
|
);
|
||||||
let _ = futures::executor::block_on(async {
|
let _ = futures::executor::block_on(async { self.rev_manager.add_local_revision(&revision).await })?;
|
||||||
self.rev_manager
|
|
||||||
.add_local_revision(&revision, Box::new(FolderRevisionCompactor()))
|
|
||||||
.await
|
|
||||||
})?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +131,7 @@ impl FolderEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FolderRevisionCompactor();
|
pub struct FolderRevisionCompactor();
|
||||||
impl RevisionCompactor for FolderRevisionCompactor {
|
impl RevisionCompactor for FolderRevisionCompactor {
|
||||||
fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
|
fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
|
||||||
let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
|
let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
|
||||||
|
@ -88,7 +88,7 @@ impl FolderMigration {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let pool = self.database.db_pool()?;
|
let pool = self.database.db_pool()?;
|
||||||
let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(user_id, pool));
|
let disk_cache = SQLiteTextBlockRevisionPersistence::new(user_id, pool);
|
||||||
let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache));
|
let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache));
|
||||||
let (revisions, _) = RevisionLoader {
|
let (revisions, _) = RevisionLoader {
|
||||||
object_id: folder_id.as_ref().to_owned(),
|
object_id: folder_id.as_ref().to_owned(),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::services::block::make_grid_block_meta_rev_manager;
|
use crate::services::block::make_grid_block_meta_rev_manager;
|
||||||
use crate::services::grid_meta_editor::GridMetaEditor;
|
use crate::services::grid_meta_editor::{GridMetaEditor, GridMetaRevisionCompactor};
|
||||||
use crate::services::persistence::block_index::BlockIndexCache;
|
use crate::services::persistence::block_index::BlockIndexCache;
|
||||||
use crate::services::persistence::kv::GridKVPersistence;
|
use crate::services::persistence::kv::GridKVPersistence;
|
||||||
use crate::services::persistence::GridDatabase;
|
use crate::services::persistence::GridDatabase;
|
||||||
@ -9,7 +9,7 @@ use flowy_database::ConnectionPool;
|
|||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use flowy_grid_data_model::entities::{BuildGridContext, GridMeta};
|
use flowy_grid_data_model::entities::{BuildGridContext, GridMeta};
|
||||||
use flowy_revision::disk::SQLiteGridRevisionPersistence;
|
use flowy_revision::disk::SQLiteGridRevisionPersistence;
|
||||||
use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket};
|
use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket, SQLiteRevisionHistoryPersistence};
|
||||||
use flowy_sync::client_grid::{make_block_meta_delta, make_grid_delta};
|
use flowy_sync::client_grid::{make_block_meta_delta, make_grid_delta};
|
||||||
use flowy_sync::entities::revision::{RepeatedRevision, Revision};
|
use flowy_sync::entities::revision::{RepeatedRevision, Revision};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -128,9 +128,11 @@ impl GridManager {
|
|||||||
pub fn make_grid_rev_manager(&self, grid_id: &str, pool: Arc<ConnectionPool>) -> FlowyResult<RevisionManager> {
|
pub fn make_grid_rev_manager(&self, grid_id: &str, pool: Arc<ConnectionPool>) -> FlowyResult<RevisionManager> {
|
||||||
let user_id = self.grid_user.user_id()?;
|
let user_id = self.grid_user.user_id()?;
|
||||||
|
|
||||||
let disk_cache = Arc::new(SQLiteGridRevisionPersistence::new(&user_id, pool));
|
let disk_cache = SQLiteGridRevisionPersistence::new(&user_id, pool.clone());
|
||||||
let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, grid_id, disk_cache));
|
let rev_persistence = RevisionPersistence::new(&user_id, grid_id, disk_cache);
|
||||||
let rev_manager = RevisionManager::new(&user_id, grid_id, rev_persistence);
|
let history_persistence = SQLiteRevisionHistoryPersistence::new(pool);
|
||||||
|
let rev_compactor = GridMetaRevisionCompactor();
|
||||||
|
let rev_manager = RevisionManager::new(&user_id, grid_id, rev_persistence, rev_compactor, history_persistence);
|
||||||
Ok(rev_manager)
|
Ok(rev_manager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,10 +169,7 @@ impl GridBlockMetaEditor {
|
|||||||
&user_id,
|
&user_id,
|
||||||
md5,
|
md5,
|
||||||
);
|
);
|
||||||
let _ = self
|
let _ = self.rev_manager.add_local_revision(&revision).await?;
|
||||||
.rev_manager
|
|
||||||
.add_local_revision(&revision, Box::new(GridBlockMetaRevisionCompactor()))
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,7 +196,7 @@ impl RevisionObjectBuilder for GridBlockMetaPadBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GridBlockMetaRevisionCompactor();
|
pub struct GridBlockMetaRevisionCompactor();
|
||||||
impl RevisionCompactor for GridBlockMetaRevisionCompactor {
|
impl RevisionCompactor for GridBlockMetaRevisionCompactor {
|
||||||
fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
|
fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
|
||||||
let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
|
let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::dart_notification::{send_dart_notification, GridNotification};
|
use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||||
use crate::manager::GridUser;
|
use crate::manager::GridUser;
|
||||||
use crate::services::block::GridBlockMetaEditor;
|
use crate::services::block::{GridBlockMetaEditor, GridBlockMetaRevisionCompactor};
|
||||||
use crate::services::persistence::block_index::BlockIndexCache;
|
use crate::services::persistence::block_index::BlockIndexCache;
|
||||||
use crate::services::row::{group_row_orders, GridBlockSnapshot};
|
use crate::services::row::{group_row_orders, GridBlockSnapshot};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
@ -10,7 +10,7 @@ use flowy_grid_data_model::entities::{
|
|||||||
RowMeta, RowMetaChangeset, RowOrder, UpdatedRowOrder,
|
RowMeta, RowMetaChangeset, RowOrder, UpdatedRowOrder,
|
||||||
};
|
};
|
||||||
use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
|
use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
|
||||||
use flowy_revision::{RevisionManager, RevisionPersistence};
|
use flowy_revision::{RevisionManager, RevisionPersistence, SQLiteRevisionHistoryPersistence};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -278,7 +278,16 @@ pub fn make_grid_block_meta_rev_manager(user: &Arc<dyn GridUser>, block_id: &str
|
|||||||
let user_id = user.user_id()?;
|
let user_id = user.user_id()?;
|
||||||
let pool = user.db_pool()?;
|
let pool = user.db_pool()?;
|
||||||
|
|
||||||
let disk_cache = Arc::new(SQLiteGridBlockMetaRevisionPersistence::new(&user_id, pool));
|
let disk_cache = SQLiteGridBlockMetaRevisionPersistence::new(&user_id, pool.clone());
|
||||||
let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, block_id, disk_cache));
|
let rev_persistence = RevisionPersistence::new(&user_id, block_id, disk_cache);
|
||||||
Ok(RevisionManager::new(&user_id, block_id, rev_persistence))
|
let rev_compactor = GridBlockMetaRevisionCompactor();
|
||||||
|
let history_persistence = SQLiteRevisionHistoryPersistence::new(pool);
|
||||||
|
|
||||||
|
Ok(RevisionManager::new(
|
||||||
|
&user_id,
|
||||||
|
block_id,
|
||||||
|
rev_persistence,
|
||||||
|
rev_compactor,
|
||||||
|
history_persistence,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
@ -547,10 +547,7 @@ impl GridMetaEditor {
|
|||||||
&user_id,
|
&user_id,
|
||||||
md5,
|
md5,
|
||||||
);
|
);
|
||||||
let _ = self
|
let _ = self.rev_manager.add_local_revision(&revision).await?;
|
||||||
.rev_manager
|
|
||||||
.add_local_revision(&revision, Box::new(GridRevisionCompactor()))
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,8 +626,8 @@ impl RevisionCloudService for GridRevisionCloudService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GridRevisionCompactor();
|
pub struct GridMetaRevisionCompactor();
|
||||||
impl RevisionCompactor for GridRevisionCompactor {
|
impl RevisionCompactor for GridMetaRevisionCompactor {
|
||||||
fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
|
fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
|
||||||
let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
|
let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
|
||||||
Ok(delta.to_delta_bytes())
|
Ok(delta.to_delta_bytes())
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::cache::disk::RevisionDiskCache;
|
use crate::cache::disk::RevisionDiskCache;
|
||||||
use crate::disk::{RevisionChangeset, RevisionRecord, RevisionState};
|
use crate::disk::{RevisionChangeset, RevisionRecord, RevisionState};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use diesel::{sql_types::Integer, update, SqliteConnection};
|
use diesel::{sql_types::Integer, update, SqliteConnection};
|
||||||
use flowy_database::{
|
use flowy_database::{
|
@ -1,10 +1,10 @@
|
|||||||
mod folder_rev_impl;
|
mod folder_rev_impl;
|
||||||
mod grid_meta_rev_impl;
|
mod grid_block_meta_rev_impl;
|
||||||
mod grid_rev_impl;
|
mod grid_rev_impl;
|
||||||
mod text_rev_impl;
|
mod text_rev_impl;
|
||||||
|
|
||||||
pub use folder_rev_impl::*;
|
pub use folder_rev_impl::*;
|
||||||
pub use grid_meta_rev_impl::*;
|
pub use grid_block_meta_rev_impl::*;
|
||||||
pub use grid_rev_impl::*;
|
pub use grid_rev_impl::*;
|
||||||
pub use text_rev_impl::*;
|
pub use text_rev_impl::*;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
mod persistence;
|
mod persistence;
|
||||||
mod rev_history;
|
mod rev_history;
|
||||||
|
|
||||||
|
pub use persistence::*;
|
||||||
pub use rev_history::*;
|
pub use rev_history::*;
|
||||||
|
@ -1,22 +1,59 @@
|
|||||||
use crate::history::RevisionHistoryDiskCache;
|
use crate::history::RevisionHistoryDiskCache;
|
||||||
use flowy_error::FlowyError;
|
use diesel::{sql_types::Integer, update, SqliteConnection};
|
||||||
|
use flowy_database::{
|
||||||
|
prelude::*,
|
||||||
|
schema::{rev_history, rev_history::dsl},
|
||||||
|
ConnectionPool,
|
||||||
|
};
|
||||||
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use flowy_sync::entities::revision::Revision;
|
use flowy_sync::entities::revision::Revision;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct SQLiteRevisionHistoryPersistence {}
|
pub struct SQLiteRevisionHistoryPersistence {
|
||||||
|
pool: Arc<ConnectionPool>,
|
||||||
|
}
|
||||||
|
|
||||||
impl SQLiteRevisionHistoryPersistence {
|
impl SQLiteRevisionHistoryPersistence {
|
||||||
pub fn new() -> Self {
|
pub fn new(pool: Arc<ConnectionPool>) -> Self {
|
||||||
Self {}
|
Self { pool }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RevisionHistoryDiskCache for SQLiteRevisionHistoryPersistence {
|
impl RevisionHistoryDiskCache for SQLiteRevisionHistoryPersistence {
|
||||||
type Error = FlowyError;
|
fn save_revision(&self, revision: Revision) -> FlowyResult<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
fn save_revision(&self, revision: Revision) -> Result<(), Self::Error> {
|
fn read_revision(&self, rev_id: i64) -> FlowyResult<Revision> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&self) -> FlowyResult<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RevisionHistorySql();
|
struct RevisionHistorySql();
|
||||||
impl RevisionHistorySql {}
|
impl RevisionHistorySql {
|
||||||
|
fn read_revision(object_id: &str, rev_id: i64, conn: &SqliteConnection) -> Result<Revision, FlowyError> {
|
||||||
|
let records: Vec<RevisionRecord> = dsl::rev_history
|
||||||
|
.filter(dsl::start_rev_id.lt(rev_id))
|
||||||
|
.filter(dsl::end_rev_id.ge(rev_id))
|
||||||
|
.filter(dsl::object_id.eq(object_id))
|
||||||
|
.load::<RevisionRecord>(conn)?;
|
||||||
|
|
||||||
|
debug_assert_eq!(records.len(), 1);
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
|
||||||
|
#[table_name = "rev_history"]
|
||||||
|
struct RevisionRecord {
|
||||||
|
id: i32,
|
||||||
|
object_id: String,
|
||||||
|
start_rev_id: i64,
|
||||||
|
end_rev_id: i64,
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
@ -1,78 +1,195 @@
|
|||||||
use crate::history::persistence::SQLiteRevisionHistoryPersistence;
|
use crate::history::persistence::SQLiteRevisionHistoryPersistence;
|
||||||
use flowy_error::FlowyError;
|
use crate::RevisionCompactor;
|
||||||
|
use async_stream::stream;
|
||||||
|
use flowy_database::ConnectionPool;
|
||||||
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use flowy_sync::entities::revision::Revision;
|
use flowy_sync::entities::revision::Revision;
|
||||||
|
use futures_util::future::BoxFuture;
|
||||||
|
use futures_util::stream::StreamExt;
|
||||||
|
use futures_util::FutureExt;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use std::time::Duration;
|
||||||
|
use tokio::sync::mpsc::error::SendError;
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
|
use tokio::sync::{mpsc, oneshot, RwLock};
|
||||||
|
use tokio::time::interval;
|
||||||
|
|
||||||
pub trait RevisionHistoryDiskCache: Send + Sync {
|
pub trait RevisionHistoryDiskCache: Send + Sync {
|
||||||
type Error: Debug;
|
fn save_revision(&self, revision: Revision) -> FlowyResult<()>;
|
||||||
|
|
||||||
fn save_revision(&self, revision: Revision) -> Result<(), Self::Error>;
|
fn read_revision(&self, rev_id: i64) -> FlowyResult<Revision>;
|
||||||
|
|
||||||
|
fn clear(&self) -> FlowyResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RevisionHistory {
|
pub struct RevisionHistory {
|
||||||
|
stop_timer: mpsc::Sender<()>,
|
||||||
config: RevisionHistoryConfig,
|
config: RevisionHistoryConfig,
|
||||||
checkpoint: Arc<RwLock<HistoryCheckpoint>>,
|
revisions: Arc<RwLock<Vec<Revision>>>,
|
||||||
disk_cache: Arc<dyn RevisionHistoryDiskCache<Error = FlowyError>>,
|
disk_cache: Arc<dyn RevisionHistoryDiskCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RevisionHistory {
|
impl RevisionHistory {
|
||||||
pub fn new(config: RevisionHistoryConfig) -> Self {
|
pub fn new(
|
||||||
let disk_cache = Arc::new(SQLiteRevisionHistoryPersistence::new());
|
user_id: &str,
|
||||||
|
object_id: &str,
|
||||||
|
config: RevisionHistoryConfig,
|
||||||
|
disk_cache: Arc<dyn RevisionHistoryDiskCache>,
|
||||||
|
rev_compactor: Arc<dyn RevisionCompactor>,
|
||||||
|
) -> Self {
|
||||||
|
let user_id = user_id.to_string();
|
||||||
|
let object_id = object_id.to_string();
|
||||||
let cloned_disk_cache = disk_cache.clone();
|
let cloned_disk_cache = disk_cache.clone();
|
||||||
let checkpoint = HistoryCheckpoint::from_config(&config, move |revision| {
|
let (stop_timer, stop_rx) = mpsc::channel(1);
|
||||||
let _ = cloned_disk_cache.save_revision(revision);
|
let (checkpoint_tx, checkpoint_rx) = mpsc::channel(1);
|
||||||
});
|
let revisions = Arc::new(RwLock::new(vec![]));
|
||||||
let checkpoint = Arc::new(RwLock::new(checkpoint));
|
let fix_duration_checkpoint_tx = FixedDurationCheckpointSender {
|
||||||
|
user_id,
|
||||||
|
object_id,
|
||||||
|
checkpoint_tx,
|
||||||
|
disk_cache: cloned_disk_cache,
|
||||||
|
revisions: revisions.clone(),
|
||||||
|
rev_compactor,
|
||||||
|
duration: config.check_duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::spawn(CheckpointRunner::new(stop_rx, checkpoint_rx).run());
|
||||||
|
tokio::spawn(fix_duration_checkpoint_tx.run());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
stop_timer,
|
||||||
config,
|
config,
|
||||||
checkpoint,
|
revisions,
|
||||||
disk_cache,
|
disk_cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn save_revision(&self, revision: &Revision) {
|
pub async fn add_revision(&self, revision: &Revision) {
|
||||||
self.checkpoint.write().await.add_revision(revision);
|
self.revisions.write().await.push(revision.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn reset_history(&self) {
|
||||||
|
self.revisions.write().await.clear();
|
||||||
|
match self.disk_cache.clear() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => tracing::error!("Clear history failed: {:?}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RevisionHistoryConfig {
|
pub struct RevisionHistoryConfig {
|
||||||
check_when_close: bool,
|
check_duration: Duration,
|
||||||
check_interval: i64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::default::Default for RevisionHistoryConfig {
|
impl std::default::Default for RevisionHistoryConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
check_when_close: true,
|
check_duration: Duration::from_secs(5),
|
||||||
check_interval: 19,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CheckpointRunner {
|
||||||
|
stop_rx: Option<mpsc::Receiver<()>>,
|
||||||
|
checkpoint_rx: Option<mpsc::Receiver<HistoryCheckpoint>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckpointRunner {
|
||||||
|
fn new(stop_rx: mpsc::Receiver<()>, checkpoint_rx: mpsc::Receiver<HistoryCheckpoint>) -> Self {
|
||||||
|
Self {
|
||||||
|
stop_rx: Some(stop_rx),
|
||||||
|
checkpoint_rx: Some(checkpoint_rx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(mut self) {
|
||||||
|
let mut stop_rx = self.stop_rx.take().expect("It should only run once");
|
||||||
|
let mut checkpoint_rx = self.checkpoint_rx.take().expect("It should only run once");
|
||||||
|
let stream = stream! {
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
result = checkpoint_rx.recv() => {
|
||||||
|
match result {
|
||||||
|
Some(checkpoint) => yield checkpoint,
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = stop_rx.recv() => {
|
||||||
|
tracing::trace!("Checkpoint runner exit");
|
||||||
|
break
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
stream
|
||||||
|
.for_each(|checkpoint| async move {
|
||||||
|
checkpoint.write().await;
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct HistoryCheckpoint {
|
struct HistoryCheckpoint {
|
||||||
interval: i64,
|
user_id: String,
|
||||||
|
object_id: String,
|
||||||
revisions: Vec<Revision>,
|
revisions: Vec<Revision>,
|
||||||
on_check: Box<dyn Fn(Revision) + Send + Sync + 'static>,
|
disk_cache: Arc<dyn RevisionHistoryDiskCache>,
|
||||||
|
rev_compactor: Arc<dyn RevisionCompactor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistoryCheckpoint {
|
impl HistoryCheckpoint {
|
||||||
fn from_config<F>(config: &RevisionHistoryConfig, on_check: F) -> Self
|
async fn write(self) {
|
||||||
where
|
if self.revisions.is_empty() {
|
||||||
F: Fn(Revision) + Send + Sync + 'static,
|
return;
|
||||||
{
|
}
|
||||||
Self {
|
|
||||||
interval: config.check_interval,
|
let result = || {
|
||||||
revisions: vec![],
|
let revision = self
|
||||||
on_check: Box::new(on_check),
|
.rev_compactor
|
||||||
|
.compact(&self.user_id, &self.object_id, self.revisions)?;
|
||||||
|
let _ = self.disk_cache.save_revision(revision)?;
|
||||||
|
Ok::<(), FlowyError>(())
|
||||||
|
};
|
||||||
|
|
||||||
|
match result() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => tracing::error!("Write history checkout failed: {:?}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(&mut self) -> Revision {
|
struct FixedDurationCheckpointSender {
|
||||||
todo!()
|
user_id: String,
|
||||||
|
object_id: String,
|
||||||
|
checkpoint_tx: mpsc::Sender<HistoryCheckpoint>,
|
||||||
|
disk_cache: Arc<dyn RevisionHistoryDiskCache>,
|
||||||
|
revisions: Arc<RwLock<Vec<Revision>>>,
|
||||||
|
rev_compactor: Arc<dyn RevisionCompactor>,
|
||||||
|
duration: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_revision(&mut self, revision: &Revision) {}
|
impl FixedDurationCheckpointSender {
|
||||||
|
fn run(self) -> BoxFuture<'static, ()> {
|
||||||
|
async move {
|
||||||
|
let mut interval = interval(self.duration);
|
||||||
|
let checkpoint_revisions: Vec<Revision> = revisions.write().await.drain(..).collect();
|
||||||
|
let checkpoint = HistoryCheckpoint {
|
||||||
|
user_id: self.user_id.clone(),
|
||||||
|
object_id: self.object_id.clone(),
|
||||||
|
revisions: checkpoint_revisions,
|
||||||
|
disk_cache: self.disk_cache.clone(),
|
||||||
|
rev_compactor: self.rev_compactor.clone(),
|
||||||
|
};
|
||||||
|
match checkpoint_tx.send(checkpoint).await {
|
||||||
|
Ok(_) => {
|
||||||
|
interval.tick().await;
|
||||||
|
self.run();
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ mod ws_manager;
|
|||||||
|
|
||||||
pub use cache::*;
|
pub use cache::*;
|
||||||
pub use conflict_resolve::*;
|
pub use conflict_resolve::*;
|
||||||
|
pub use history::*;
|
||||||
pub use rev_manager::*;
|
pub use rev_manager::*;
|
||||||
pub use rev_persistence::*;
|
pub use rev_persistence::*;
|
||||||
pub use ws_manager::*;
|
pub use ws_manager::*;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::disk::RevisionState;
|
use crate::disk::RevisionState;
|
||||||
use crate::history::{RevisionHistory, RevisionHistoryConfig};
|
use crate::history::{RevisionHistory, RevisionHistoryConfig, RevisionHistoryDiskCache};
|
||||||
use crate::{RevisionPersistence, WSDataProviderDataSource};
|
use crate::{RevisionPersistence, WSDataProviderDataSource};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
@ -47,15 +47,36 @@ pub struct RevisionManager {
|
|||||||
rev_id_counter: RevIdCounter,
|
rev_id_counter: RevIdCounter,
|
||||||
rev_persistence: Arc<RevisionPersistence>,
|
rev_persistence: Arc<RevisionPersistence>,
|
||||||
rev_history: Arc<RevisionHistory>,
|
rev_history: Arc<RevisionHistory>,
|
||||||
|
rev_compactor: Arc<dyn RevisionCompactor>,
|
||||||
#[cfg(feature = "flowy_unit_test")]
|
#[cfg(feature = "flowy_unit_test")]
|
||||||
rev_ack_notifier: tokio::sync::broadcast::Sender<i64>,
|
rev_ack_notifier: tokio::sync::broadcast::Sender<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RevisionManager {
|
impl RevisionManager {
|
||||||
pub fn new(user_id: &str, object_id: &str, rev_persistence: Arc<RevisionPersistence>) -> Self {
|
pub fn new<P, C>(
|
||||||
|
user_id: &str,
|
||||||
|
object_id: &str,
|
||||||
|
rev_persistence: RevisionPersistence,
|
||||||
|
rev_compactor: C,
|
||||||
|
history_persistence: P,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
P: 'static + RevisionHistoryDiskCache,
|
||||||
|
C: 'static + RevisionCompactor,
|
||||||
|
{
|
||||||
let rev_id_counter = RevIdCounter::new(0);
|
let rev_id_counter = RevIdCounter::new(0);
|
||||||
|
let rev_compactor = Arc::new(rev_compactor);
|
||||||
|
let history_persistence = Arc::new(history_persistence);
|
||||||
let rev_history_config = RevisionHistoryConfig::default();
|
let rev_history_config = RevisionHistoryConfig::default();
|
||||||
let rev_history = Arc::new(RevisionHistory::new(rev_history_config));
|
let rev_persistence = Arc::new(rev_persistence);
|
||||||
|
|
||||||
|
let rev_history = Arc::new(RevisionHistory::new(
|
||||||
|
user_id,
|
||||||
|
object_id,
|
||||||
|
rev_history_config,
|
||||||
|
history_persistence,
|
||||||
|
rev_compactor.clone(),
|
||||||
|
));
|
||||||
#[cfg(feature = "flowy_unit_test")]
|
#[cfg(feature = "flowy_unit_test")]
|
||||||
let (revision_ack_notifier, _) = tokio::sync::broadcast::channel(1);
|
let (revision_ack_notifier, _) = tokio::sync::broadcast::channel(1);
|
||||||
|
|
||||||
@ -65,7 +86,7 @@ impl RevisionManager {
|
|||||||
rev_id_counter,
|
rev_id_counter,
|
||||||
rev_persistence,
|
rev_persistence,
|
||||||
rev_history,
|
rev_history,
|
||||||
|
rev_compactor,
|
||||||
#[cfg(feature = "flowy_unit_test")]
|
#[cfg(feature = "flowy_unit_test")]
|
||||||
rev_ack_notifier: revision_ack_notifier,
|
rev_ack_notifier: revision_ack_notifier,
|
||||||
}
|
}
|
||||||
@ -93,6 +114,7 @@ impl RevisionManager {
|
|||||||
pub async fn reset_object(&self, revisions: RepeatedRevision) -> FlowyResult<()> {
|
pub async fn reset_object(&self, revisions: RepeatedRevision) -> FlowyResult<()> {
|
||||||
let rev_id = pair_rev_id_from_revisions(&revisions).1;
|
let rev_id = pair_rev_id_from_revisions(&revisions).1;
|
||||||
let _ = self.rev_persistence.reset(revisions.into_inner()).await?;
|
let _ = self.rev_persistence.reset(revisions.into_inner()).await?;
|
||||||
|
self.rev_history.reset_history().await;
|
||||||
self.rev_id_counter.set(rev_id);
|
self.rev_id_counter.set(rev_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -104,20 +126,21 @@ impl RevisionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.rev_persistence.add_ack_revision(revision).await?;
|
let _ = self.rev_persistence.add_ack_revision(revision).await?;
|
||||||
|
self.rev_history.add_revision(revision).await;
|
||||||
self.rev_id_counter.set(revision.rev_id);
|
self.rev_id_counter.set(revision.rev_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||||
pub async fn add_local_revision<'a>(
|
pub async fn add_local_revision(&self, revision: &Revision) -> Result<(), FlowyError> {
|
||||||
&'a self,
|
|
||||||
revision: &Revision,
|
|
||||||
compactor: Box<dyn RevisionCompactor + 'a>,
|
|
||||||
) -> Result<(), FlowyError> {
|
|
||||||
if revision.delta_data.is_empty() {
|
if revision.delta_data.is_empty() {
|
||||||
return Err(FlowyError::internal().context("Delta data should be empty"));
|
return Err(FlowyError::internal().context("Delta data should be empty"));
|
||||||
}
|
}
|
||||||
let rev_id = self.rev_persistence.add_sync_revision(revision, compactor).await?;
|
let rev_id = self
|
||||||
|
.rev_persistence
|
||||||
|
.add_sync_revision(revision, &self.rev_compactor)
|
||||||
|
.await?;
|
||||||
|
self.rev_history.add_revision(revision).await;
|
||||||
self.rev_id_counter.set(rev_id);
|
self.rev_id_counter.set(rev_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,13 @@ pub struct RevisionPersistence {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RevisionPersistence {
|
impl RevisionPersistence {
|
||||||
pub fn new(
|
pub fn new<C>(user_id: &str, object_id: &str, disk_cache: C) -> RevisionPersistence
|
||||||
user_id: &str,
|
where
|
||||||
object_id: &str,
|
C: 'static + RevisionDiskCache<Error = FlowyError>,
|
||||||
disk_cache: Arc<dyn RevisionDiskCache<Error = FlowyError>>,
|
{
|
||||||
) -> RevisionPersistence {
|
|
||||||
let object_id = object_id.to_owned();
|
let object_id = object_id.to_owned();
|
||||||
let user_id = user_id.to_owned();
|
let user_id = user_id.to_owned();
|
||||||
|
let disk_cache = Arc::new(disk_cache) as Arc<dyn RevisionDiskCache<Error = FlowyError>>;
|
||||||
let sync_seq = RwLock::new(RevisionSyncSequence::new());
|
let sync_seq = RwLock::new(RevisionSyncSequence::new());
|
||||||
let memory_cache = Arc::new(RevisionMemoryCache::new(&object_id, Arc::new(disk_cache.clone())));
|
let memory_cache = Arc::new(RevisionMemoryCache::new(&object_id, Arc::new(disk_cache.clone())));
|
||||||
Self {
|
Self {
|
||||||
@ -63,7 +63,7 @@ impl RevisionPersistence {
|
|||||||
pub(crate) async fn add_sync_revision<'a>(
|
pub(crate) async fn add_sync_revision<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
revision: &'a Revision,
|
revision: &'a Revision,
|
||||||
compactor: Box<dyn RevisionCompactor + 'a>,
|
compactor: &Arc<dyn RevisionCompactor + 'a>,
|
||||||
) -> FlowyResult<i64> {
|
) -> FlowyResult<i64> {
|
||||||
let result = self.sync_seq.read().await.compact();
|
let result = self.sync_seq.read().await.compact();
|
||||||
match result {
|
match result {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::ConflictRevisionSink;
|
use crate::ConflictRevisionSink;
|
||||||
use async_stream::stream;
|
use async_stream::stream;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use flowy_sync::entities::{
|
use flowy_sync::entities::{
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
use crate::queue::TextBlockRevisionCompactor;
|
||||||
use crate::{editor::TextBlockEditor, errors::FlowyError, BlockCloudService};
|
use crate::{editor::TextBlockEditor, errors::FlowyError, BlockCloudService};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use flowy_database::ConnectionPool;
|
use flowy_database::ConnectionPool;
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
use flowy_revision::disk::SQLiteTextBlockRevisionPersistence;
|
use flowy_revision::disk::SQLiteTextBlockRevisionPersistence;
|
||||||
use flowy_revision::{RevisionCloudService, RevisionManager, RevisionPersistence, RevisionWebSocket};
|
use flowy_revision::{
|
||||||
|
RevisionCloudService, RevisionManager, RevisionPersistence, RevisionWebSocket, SQLiteRevisionHistoryPersistence,
|
||||||
|
};
|
||||||
use flowy_sync::entities::{
|
use flowy_sync::entities::{
|
||||||
revision::{md5, RepeatedRevision, Revision},
|
revision::{md5, RepeatedRevision, Revision},
|
||||||
text_block_info::{TextBlockDelta, TextBlockId},
|
text_block_info::{TextBlockDelta, TextBlockId},
|
||||||
@ -139,9 +142,18 @@ impl TextBlockManager {
|
|||||||
|
|
||||||
fn make_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
|
fn make_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
|
||||||
let user_id = self.user.user_id()?;
|
let user_id = self.user.user_id()?;
|
||||||
let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(&user_id, pool));
|
let disk_cache = SQLiteTextBlockRevisionPersistence::new(&user_id, pool.clone());
|
||||||
let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, doc_id, disk_cache));
|
let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache);
|
||||||
Ok(RevisionManager::new(&user_id, doc_id, rev_persistence))
|
let history_persistence = SQLiteRevisionHistoryPersistence::new(pool);
|
||||||
|
let rev_compactor = TextBlockRevisionCompactor();
|
||||||
|
|
||||||
|
Ok(RevisionManager::new(
|
||||||
|
&user_id,
|
||||||
|
doc_id,
|
||||||
|
rev_persistence,
|
||||||
|
rev_compactor,
|
||||||
|
history_persistence,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,10 +186,7 @@ impl EditBlockQueue {
|
|||||||
&user_id,
|
&user_id,
|
||||||
md5,
|
md5,
|
||||||
);
|
);
|
||||||
let _ = self
|
let _ = self.rev_manager.add_local_revision(&revision).await?;
|
||||||
.rev_manager
|
|
||||||
.add_local_revision(&revision, Box::new(TextBlockRevisionCompactor()))
|
|
||||||
.await?;
|
|
||||||
Ok(rev_id.into())
|
Ok(rev_id.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,7 @@ pub trait JsonDeserializer {
|
|||||||
|
|
||||||
impl GridMetaPad {
|
impl GridMetaPad {
|
||||||
pub async fn duplicate_grid_meta(&self) -> (Vec<FieldMeta>, Vec<GridBlockMetaSnapshot>) {
|
pub async fn duplicate_grid_meta(&self) -> (Vec<FieldMeta>, Vec<GridBlockMetaSnapshot>) {
|
||||||
let fields = self
|
let fields = self.grid_meta.fields.to_vec();
|
||||||
.grid_meta
|
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.map(|field| field.clone())
|
|
||||||
.collect::<Vec<FieldMeta>>();
|
|
||||||
|
|
||||||
let blocks = self
|
let blocks = self
|
||||||
.grid_meta
|
.grid_meta
|
||||||
|
Loading…
Reference in New Issue
Block a user