trait for flowy-core persistence

This commit is contained in:
appflowy
2022-01-14 09:09:25 +08:00
parent dac86ef857
commit f9b552395b
25 changed files with 618 additions and 472 deletions

View File

@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-core-data-model/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-core-data-model/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-core-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-core-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-core/observable.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-core/dart_notification.pb.dart';
import 'package:flowy_sdk/rust_stream.dart'; import 'package:flowy_sdk/rust_stream.dart';
import 'helper.dart'; import 'helper.dart';

View File

@ -3,7 +3,7 @@ import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart';
import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-core/observable.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-core/dart_notification.pb.dart';
typedef UserNotificationCallback = void Function(UserNotification, Either<Uint8List, FlowyError>); typedef UserNotificationCallback = void Function(UserNotification, Either<Uint8List, FlowyError>);

View File

@ -9,8 +9,14 @@ use crate::{
dart_notification::{send_dart_notification, WorkspaceNotification}, dart_notification::{send_dart_notification, WorkspaceNotification},
entities::workspace::RepeatedWorkspace, entities::workspace::RepeatedWorkspace,
errors::{FlowyError, FlowyResult}, errors::{FlowyError, FlowyResult},
module::{WorkspaceCloudService, WorkspaceDatabase, WorkspaceUser}, module::{WorkspaceCloudService, WorkspaceUser},
services::{AppController, TrashController, ViewController, WorkspaceController}, services::{
persistence::FlowyCorePersistence,
AppController,
TrashController,
ViewController,
WorkspaceController,
},
}; };
lazy_static! { lazy_static! {
@ -20,7 +26,7 @@ lazy_static! {
pub struct CoreContext { pub struct CoreContext {
pub user: Arc<dyn WorkspaceUser>, pub user: Arc<dyn WorkspaceUser>,
pub(crate) cloud_service: Arc<dyn WorkspaceCloudService>, pub(crate) cloud_service: Arc<dyn WorkspaceCloudService>,
pub(crate) database: Arc<dyn WorkspaceDatabase>, pub(crate) persistence: Arc<FlowyCorePersistence>,
pub workspace_controller: Arc<WorkspaceController>, pub workspace_controller: Arc<WorkspaceController>,
pub(crate) app_controller: Arc<AppController>, pub(crate) app_controller: Arc<AppController>,
pub(crate) view_controller: Arc<ViewController>, pub(crate) view_controller: Arc<ViewController>,
@ -31,7 +37,7 @@ impl CoreContext {
pub(crate) fn new( pub(crate) fn new(
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
workspace_controller: Arc<WorkspaceController>, workspace_controller: Arc<WorkspaceController>,
app_controller: Arc<AppController>, app_controller: Arc<AppController>,
view_controller: Arc<ViewController>, view_controller: Arc<ViewController>,
@ -44,7 +50,7 @@ impl CoreContext {
Self { Self {
user, user,
cloud_service, cloud_service,
database, persistence,
workspace_controller, workspace_controller,
app_controller, app_controller,
view_controller, view_controller,

View File

@ -10,6 +10,7 @@ use crate::{
event::WorkspaceEvent, event::WorkspaceEvent,
services::{ services::{
app::event_handler::*, app::event_handler::*,
persistence::FlowyCorePersistence,
trash::event_handler::*, trash::event_handler::*,
view::event_handler::*, view::event_handler::*,
workspace::event_handler::*, workspace::event_handler::*,
@ -49,15 +50,17 @@ pub fn init_core(
flowy_document: Arc<DocumentContext>, flowy_document: Arc<DocumentContext>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
) -> Arc<CoreContext> { ) -> Arc<CoreContext> {
let persistence = Arc::new(FlowyCorePersistence::new(database.clone()));
let trash_controller = Arc::new(TrashController::new( let trash_controller = Arc::new(TrashController::new(
database.clone(), persistence.clone(),
cloud_service.clone(), cloud_service.clone(),
user.clone(), user.clone(),
)); ));
let view_controller = Arc::new(ViewController::new( let view_controller = Arc::new(ViewController::new(
user.clone(), user.clone(),
database.clone(), persistence.clone(),
cloud_service.clone(), cloud_service.clone(),
trash_controller.clone(), trash_controller.clone(),
flowy_document, flowy_document,
@ -65,14 +68,14 @@ pub fn init_core(
let app_controller = Arc::new(AppController::new( let app_controller = Arc::new(AppController::new(
user.clone(), user.clone(),
database.clone(), persistence.clone(),
trash_controller.clone(), trash_controller.clone(),
cloud_service.clone(), cloud_service.clone(),
)); ));
let workspace_controller = Arc::new(WorkspaceController::new( let workspace_controller = Arc::new(WorkspaceController::new(
user.clone(), user.clone(),
database.clone(), persistence.clone(),
trash_controller.clone(), trash_controller.clone(),
cloud_service.clone(), cloud_service.clone(),
)); ));
@ -80,7 +83,7 @@ pub fn init_core(
Arc::new(CoreContext::new( Arc::new(CoreContext::new(
user, user,
cloud_service, cloud_service,
database, persistence,
workspace_controller, workspace_controller,
app_controller, app_controller,
view_controller, view_controller,

View File

@ -5,35 +5,35 @@ use crate::{
trash::TrashType, trash::TrashType,
}, },
errors::*, errors::*,
module::{WorkspaceCloudService, WorkspaceDatabase, WorkspaceUser}, module::{WorkspaceCloudService, WorkspaceUser},
services::{ services::{
app::sql::{AppChangeset, AppTable, AppTableSql}, persistence::{AppChangeset, FlowyCorePersistence, FlowyCorePersistenceTransaction},
TrashController, TrashController,
TrashEvent, TrashEvent,
}, },
}; };
use flowy_database::SqliteConnection;
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use std::{collections::HashSet, sync::Arc}; use std::{collections::HashSet, sync::Arc};
pub(crate) struct AppController { pub(crate) struct AppController {
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
trash_can: Arc<TrashController>, trash_controller: Arc<TrashController>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
} }
impl AppController { impl AppController {
pub(crate) fn new( pub(crate) fn new(
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
trash_can: Arc<TrashController>, trash_can: Arc<TrashController>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
) -> Self { ) -> Self {
Self { Self {
user, user,
database, persistence,
trash_can, trash_controller: trash_can,
cloud_service, cloud_service,
} }
} }
@ -50,62 +50,52 @@ impl AppController {
} }
pub(crate) async fn create_app_on_local(&self, app: App) -> Result<App, FlowyError> { pub(crate) async fn create_app_on_local(&self, app: App) -> Result<App, FlowyError> {
let conn = &*self.database.db_connection()?; let _ = self.persistence.begin_transaction(|transaction| {
conn.immediate_transaction::<_, FlowyError, _>(|| { let _ = transaction.create_app(app.clone())?;
let _ = self.save_app(app.clone(), &*conn)?; let _ = notify_apps_changed(&app.workspace_id, self.trash_controller.clone(), &transaction)?;
let _ = notify_apps_changed(&app.workspace_id, self.trash_can.clone(), conn)?;
Ok(()) Ok(())
})?; })?;
Ok(app) Ok(app)
} }
pub(crate) fn save_app(&self, app: App, conn: &SqliteConnection) -> Result<(), FlowyError> {
let _ = AppTableSql::create_app(app, &*conn)?;
Ok(())
}
pub(crate) async fn read_app(&self, params: AppId) -> Result<App, FlowyError> { pub(crate) async fn read_app(&self, params: AppId) -> Result<App, FlowyError> {
let conn = self.database.db_connection()?; let app = self.persistence.begin_transaction(|transaction| {
let app_table = AppTableSql::read_app(&params.app_id, &*conn)?; let app = transaction.read_app(&params.app_id)?;
let trash_ids = self.trash_controller.read_trash_ids(&transaction)?;
let trash_ids = self.trash_can.read_trash_ids(&conn)?; if trash_ids.contains(&app.id) {
if trash_ids.contains(&app_table.id) {
return Err(FlowyError::record_not_found()); return Err(FlowyError::record_not_found());
} }
Ok(app)
})?;
let _ = self.read_app_on_server(params)?; let _ = self.read_app_on_server(params)?;
Ok(app_table.into()) Ok(app)
} }
pub(crate) async fn update_app(&self, params: UpdateAppParams) -> Result<(), FlowyError> { pub(crate) async fn update_app(&self, params: UpdateAppParams) -> Result<(), FlowyError> {
let changeset = AppChangeset::new(params.clone()); let changeset = AppChangeset::new(params.clone());
let app_id = changeset.id.clone(); let app_id = changeset.id.clone();
let conn = &*self.database.db_connection()?;
conn.immediate_transaction::<_, FlowyError, _>(|| { let app = self.persistence.begin_transaction(|transaction| {
let _ = AppTableSql::update_app(changeset, conn)?; let _ = transaction.update_app(changeset)?;
let app: App = AppTableSql::read_app(&app_id, conn)?.into(); let app = transaction.read_app(&app_id)?;
Ok(app)
})?;
send_dart_notification(&app_id, WorkspaceNotification::AppUpdated) send_dart_notification(&app_id, WorkspaceNotification::AppUpdated)
.payload(app) .payload(app)
.send(); .send();
Ok(())
})?;
let _ = self.update_app_on_server(params)?; let _ = self.update_app_on_server(params)?;
Ok(()) Ok(())
} }
pub(crate) fn read_app_tables(&self, ids: Vec<String>) -> Result<Vec<AppTable>, FlowyError> { pub(crate) fn read_local_apps(&self, ids: Vec<String>) -> Result<Vec<App>, FlowyError> {
let conn = &*self.database.db_connection()?; let apps = self.persistence.begin_transaction(|transaction| {
let mut app_tables = vec![]; let mut apps = vec![];
conn.immediate_transaction::<_, FlowyError, _>(|| { for id in ids {
for app_id in ids { apps.push(transaction.read_app(&id)?);
app_tables.push(AppTableSql::read_app(&app_id, conn)?);
} }
Ok(()) Ok(apps)
})?; })?;
Ok(apps)
Ok(app_tables)
} }
} }
@ -137,14 +127,11 @@ impl AppController {
fn read_app_on_server(&self, params: AppId) -> Result<(), FlowyError> { fn read_app_on_server(&self, params: AppId) -> Result<(), FlowyError> {
let token = self.user.token()?; let token = self.user.token()?;
let server = self.cloud_service.clone(); let server = self.cloud_service.clone();
let pool = self.database.db_pool()?; let persistence = self.persistence.clone();
tokio::spawn(async move { tokio::spawn(async move {
// Opti: retry?
match server.read_app(&token, params).await { match server.read_app(&token, params).await {
Ok(Some(app)) => match pool.get() { Ok(Some(app)) => {
Ok(conn) => { match persistence.begin_transaction(|transaction| transaction.create_app(app.clone())) {
let result = AppTableSql::create_app(app.clone(), &*conn);
match result {
Ok(_) => { Ok(_) => {
send_dart_notification(&app.id, WorkspaceNotification::AppUpdated) send_dart_notification(&app.id, WorkspaceNotification::AppUpdated)
.payload(app) .payload(app)
@ -153,8 +140,6 @@ impl AppController {
Err(e) => log::error!("Save app failed: {:?}", e), Err(e) => log::error!("Save app failed: {:?}", e),
} }
}, },
Err(e) => log::error!("Require db connection failed: {:?}", e),
},
Ok(None) => {}, Ok(None) => {},
Err(e) => log::error!("Read app failed: {:?}", e), Err(e) => log::error!("Read app failed: {:?}", e),
} }
@ -163,9 +148,9 @@ impl AppController {
} }
fn listen_trash_controller_event(&self) { fn listen_trash_controller_event(&self) {
let mut rx = self.trash_can.subscribe(); let mut rx = self.trash_controller.subscribe();
let database = self.database.clone(); let persistence = self.persistence.clone();
let trash_can = self.trash_can.clone(); let trash_controller = self.trash_controller.clone();
let _ = tokio::spawn(async move { let _ = tokio::spawn(async move {
loop { loop {
let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move { let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move {
@ -175,76 +160,69 @@ impl AppController {
} }
})); }));
if let Some(event) = stream.next().await { if let Some(event) = stream.next().await {
handle_trash_event(database.clone(), trash_can.clone(), event).await handle_trash_event(persistence.clone(), trash_controller.clone(), event).await
} }
} }
}); });
} }
} }
#[tracing::instrument(level = "trace", skip(database, trash_can))] #[tracing::instrument(level = "trace", skip(persistence, trash_controller))]
async fn handle_trash_event(database: Arc<dyn WorkspaceDatabase>, trash_can: Arc<TrashController>, event: TrashEvent) { async fn handle_trash_event(
let db_result = database.db_connection(); persistence: Arc<FlowyCorePersistence>,
trash_controller: Arc<TrashController>,
event: TrashEvent,
) {
match event { match event {
TrashEvent::NewTrash(identifiers, ret) | TrashEvent::Putback(identifiers, ret) => { TrashEvent::NewTrash(identifiers, ret) | TrashEvent::Putback(identifiers, ret) => {
let result = || { let result = persistence.begin_transaction(|transaction| {
let conn = &*db_result?;
let _ = conn.immediate_transaction::<_, FlowyError, _>(|| {
for identifier in identifiers.items { for identifier in identifiers.items {
let app_table = AppTableSql::read_app(&identifier.id, conn)?; let app = transaction.read_app(&identifier.id)?;
let _ = notify_apps_changed(&app_table.workspace_id, trash_can.clone(), conn)?; let _ = notify_apps_changed(&app.workspace_id, trash_controller.clone(), &transaction)?;
} }
Ok(()) Ok(())
})?; });
Ok::<(), FlowyError>(()) let _ = ret.send(result).await;
};
let _ = ret.send(result()).await;
}, },
TrashEvent::Delete(identifiers, ret) => { TrashEvent::Delete(identifiers, ret) => {
let result = || { let result = persistence.begin_transaction(|transaction| {
let conn = &*db_result?;
let _ = conn.immediate_transaction::<_, FlowyError, _>(|| {
let mut notify_ids = HashSet::new(); let mut notify_ids = HashSet::new();
for identifier in identifiers.items { for identifier in identifiers.items {
let app_table = AppTableSql::read_app(&identifier.id, conn)?; let app = transaction.read_app(&identifier.id)?;
let _ = AppTableSql::delete_app(&identifier.id, conn)?; let _ = transaction.delete_app(&identifier.id)?;
notify_ids.insert(app_table.workspace_id); notify_ids.insert(app.workspace_id);
} }
for notify_id in notify_ids { for notify_id in notify_ids {
let _ = notify_apps_changed(&notify_id, trash_can.clone(), conn)?; let _ = notify_apps_changed(&notify_id, trash_controller.clone(), &transaction)?;
} }
Ok(()) Ok(())
})?; });
Ok::<(), FlowyError>(()) let _ = ret.send(result).await;
};
let _ = ret.send(result()).await;
}, },
} }
} }
#[tracing::instrument(skip(workspace_id, trash_can, conn), err)] #[tracing::instrument(skip(workspace_id, trash_controller, transaction), err)]
fn notify_apps_changed( fn notify_apps_changed<'a>(
workspace_id: &str, workspace_id: &str,
trash_can: Arc<TrashController>, trash_controller: Arc<TrashController>,
conn: &SqliteConnection, transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
) -> FlowyResult<()> { ) -> FlowyResult<()> {
let repeated_app = read_local_workspace_apps(workspace_id, trash_can, conn)?; let repeated_app = read_local_workspace_apps(workspace_id, trash_controller, transaction)?;
send_dart_notification(workspace_id, WorkspaceNotification::WorkspaceAppsChanged) send_dart_notification(workspace_id, WorkspaceNotification::WorkspaceAppsChanged)
.payload(repeated_app) .payload(repeated_app)
.send(); .send();
Ok(()) Ok(())
} }
pub fn read_local_workspace_apps( pub fn read_local_workspace_apps<'a>(
workspace_id: &str, workspace_id: &str,
trash_controller: Arc<TrashController>, trash_controller: Arc<TrashController>,
conn: &SqliteConnection, transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
) -> Result<RepeatedApp, FlowyError> { ) -> Result<RepeatedApp, FlowyError> {
let mut app_tables = AppTableSql::read_workspace_apps(workspace_id, false, conn)?; let mut apps = transaction.read_workspace_apps(workspace_id)?;
let trash_ids = trash_controller.read_trash_ids(conn)?; let trash_ids = trash_controller.read_trash_ids(transaction)?;
app_tables.retain(|app_table| !trash_ids.contains(&app_table.id)); apps.retain(|app| !trash_ids.contains(&app.id));
let apps = app_tables.into_iter().map(|table| table.into()).collect::<Vec<App>>();
Ok(RepeatedApp { items: apps }) Ok(RepeatedApp { items: apps })
} }

View File

@ -21,14 +21,14 @@ pub(crate) async fn create_app_handler(
pub(crate) async fn delete_app_handler( pub(crate) async fn delete_app_handler(
data: Data<QueryAppRequest>, data: Data<QueryAppRequest>,
view_controller: Unit<Arc<AppController>>, app_controller: Unit<Arc<AppController>>,
trash_controller: Unit<Arc<TrashController>>, trash_controller: Unit<Arc<TrashController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let params: AppId = data.into_inner().try_into()?; let params: AppId = data.into_inner().try_into()?;
let trash = view_controller let trash = app_controller
.read_app_tables(vec![params.app_id])? .read_local_apps(vec![params.app_id])?
.into_iter() .into_iter()
.map(|view_table| view_table.into()) .map(|app| app.into())
.collect::<Vec<Trash>>(); .collect::<Vec<Trash>>();
let _ = trash_controller.add(trash).await?; let _ = trash_controller.add(trash).await?;

View File

@ -1,3 +1,2 @@
pub mod controller; pub mod controller;
pub mod event_handler; pub mod event_handler;
pub(crate) mod sql;

View File

@ -4,6 +4,7 @@ pub(crate) use view::controller::*;
pub(crate) use workspace::controller::*; pub(crate) use workspace::controller::*;
pub(crate) mod app; pub(crate) mod app;
pub(crate) mod persistence;
pub(crate) mod trash; pub(crate) mod trash;
pub(crate) mod view; pub(crate) mod view;
pub(crate) mod workspace; pub(crate) mod workspace;

View File

@ -0,0 +1,76 @@
mod version_1;
use std::sync::Arc;
pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*};
use crate::module::WorkspaceDatabase;
use flowy_core_data_model::entities::{
app::App,
prelude::RepeatedTrash,
trash::Trash,
view::View,
workspace::Workspace,
};
use flowy_error::{FlowyError, FlowyResult};
pub trait FlowyCorePersistenceTransaction {
fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()>;
fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>>;
fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()>;
fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()>;
fn create_app(&self, app: App) -> FlowyResult<()>;
fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()>;
fn read_app(&self, app_id: &str) -> FlowyResult<App>;
fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>>;
fn delete_app(&self, app_id: &str) -> FlowyResult<App>;
fn create_view(&self, view: View) -> FlowyResult<()>;
fn read_view(&self, view_id: &str) -> FlowyResult<View>;
fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<View>>;
fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()>;
fn delete_view(&self, view_id: &str) -> FlowyResult<()>;
fn create_trash(&self, trashes: Vec<Trash>) -> FlowyResult<()>;
fn read_all_trash(&self) -> FlowyResult<RepeatedTrash>;
fn delete_all_trash(&self) -> FlowyResult<()>;
fn read_trash(&self, trash_id: &str) -> FlowyResult<Trash>;
fn delete_trash(&self, trash_ids: Vec<String>) -> FlowyResult<()>;
}
pub struct FlowyCorePersistence {
database: Arc<dyn WorkspaceDatabase>,
}
impl FlowyCorePersistence {
pub fn new(database: Arc<dyn WorkspaceDatabase>) -> Self { Self { database } }
pub fn begin_transaction<F, O>(&self, f: F) -> FlowyResult<O>
where
F: for<'a> FnOnce(Box<dyn FlowyCorePersistenceTransaction + 'a>) -> FlowyResult<O>,
{
//[[immediate_transaction]]
// https://sqlite.org/lang_transaction.html
// IMMEDIATE cause the database connection to start a new write immediately,
// without waiting for a write statement. The BEGIN IMMEDIATE might fail
// with SQLITE_BUSY if another write transaction is already active on another
// database connection.
//
// EXCLUSIVE is similar to IMMEDIATE in that a write transaction is started
// immediately. EXCLUSIVE and IMMEDIATE are the same in WAL mode, but in
// other journaling modes, EXCLUSIVE prevents other database connections from
// reading the database while the transaction is underway.
let conn = self.database.db_connection()?;
conn.immediate_transaction::<_, FlowyError, _>(|| f(Box::new(V1Transaction(&conn))))
}
// pub fn scope_transaction<F, O>(&self, f: F) -> FlowyResult<O>
// where
// F: for<'a> FnOnce(Box<dyn FlowyCorePersistenceTransaction + 'a>) ->
// FlowyResult<O>, {
// match thread::scope(|_s| self.begin_transaction(f)) {
// Ok(result) => result,
// Err(e) => Err(FlowyError::internal().context(e)),
// }
// }
}

View File

@ -1,10 +1,7 @@
use crate::{ use crate::entities::{
entities::{
app::{App, ColorStyle, UpdateAppParams}, app::{App, ColorStyle, UpdateAppParams},
trash::{Trash, TrashType}, trash::{Trash, TrashType},
view::RepeatedView, view::RepeatedView,
},
services::workspace::sql::WorkspaceTable,
}; };
use diesel::sql_types::Binary; use diesel::sql_types::Binary;
use flowy_database::{ use flowy_database::{
@ -15,10 +12,9 @@ use flowy_database::{
use serde::{Deserialize, Serialize, __private::TryFrom}; use serde::{Deserialize, Serialize, __private::TryFrom};
use std::convert::TryInto; use std::convert::TryInto;
use crate::errors::FlowyError; use crate::{errors::FlowyError, services::persistence::version_1::workspace_sql::WorkspaceTable};
pub struct AppTableSql {}
pub struct AppTableSql();
impl AppTableSql { impl AppTableSql {
pub(crate) fn create_app(app: App, conn: &SqliteConnection) -> Result<(), FlowyError> { pub(crate) fn create_app(app: App, conn: &SqliteConnection) -> Result<(), FlowyError> {
let app_table = AppTable::new(app); let app_table = AppTable::new(app);
@ -161,7 +157,7 @@ impl_sql_binary_expression!(ColorStyleCol);
#[derive(AsChangeset, Identifiable, Default, Debug)] #[derive(AsChangeset, Identifiable, Default, Debug)]
#[table_name = "app_table"] #[table_name = "app_table"]
pub(crate) struct AppChangeset { pub struct AppChangeset {
pub id: String, pub id: String,
pub name: Option<String>, pub name: Option<String>,
pub desc: Option<String>, pub desc: Option<String>,

View File

@ -0,0 +1,5 @@
pub mod app_sql;
pub mod trash_sql;
pub mod v1_impl;
pub mod view_sql;
pub mod workspace_sql;

View File

@ -9,8 +9,7 @@ use flowy_database::{
SqliteConnection, SqliteConnection,
}; };
pub struct TrashTableSql {} pub struct TrashTableSql();
impl TrashTableSql { impl TrashTableSql {
pub(crate) fn create_trash(trashes: Vec<Trash>, conn: &SqliteConnection) -> Result<(), FlowyError> { pub(crate) fn create_trash(trashes: Vec<Trash>, conn: &SqliteConnection) -> Result<(), FlowyError> {
for trash in trashes { for trash in trashes {

View File

@ -0,0 +1,163 @@
use crate::services::persistence::{
version_1::{
app_sql::{AppChangeset, AppTableSql},
view_sql::{ViewChangeset, ViewTableSql},
workspace_sql::{WorkspaceChangeset, WorkspaceTableSql},
},
FlowyCorePersistenceTransaction,
TrashTableSql,
};
use flowy_core_data_model::entities::{
app::App,
prelude::{RepeatedTrash, Trash, View, Workspace},
};
use flowy_error::FlowyResult;
use lib_sqlite::DBConnection;
pub struct V1Transaction<'a>(pub &'a DBConnection);
impl<'a> FlowyCorePersistenceTransaction for V1Transaction<'a> {
fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()> {
let _ = WorkspaceTableSql::create_workspace(user_id, workspace, &*self.0)?;
Ok(())
}
fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>> {
let tables = WorkspaceTableSql::read_workspaces(workspace_id, user_id, &*self.0)?;
let workspaces = tables.into_iter().map(Workspace::from).collect::<Vec<_>>();
Ok(workspaces)
}
fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> {
WorkspaceTableSql::update_workspace(changeset, &*self.0)
}
fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> {
WorkspaceTableSql::delete_workspace(workspace_id, &*self.0)
}
fn create_app(&self, app: App) -> FlowyResult<()> {
let _ = AppTableSql::create_app(app, &*self.0)?;
Ok(())
}
fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> {
let _ = AppTableSql::update_app(changeset, &*self.0)?;
Ok(())
}
fn read_app(&self, app_id: &str) -> FlowyResult<App> {
let table = AppTableSql::read_app(app_id, &*self.0)?;
Ok(App::from(table))
}
fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
let tables = AppTableSql::read_workspace_apps(workspace_id, false, &*self.0)?;
let apps = tables.into_iter().map(App::from).collect::<Vec<_>>();
Ok(apps)
}
fn delete_app(&self, app_id: &str) -> FlowyResult<App> {
let table = AppTableSql::delete_app(app_id, &*self.0)?;
Ok(App::from(table))
}
fn create_view(&self, view: View) -> FlowyResult<()> {
let _ = ViewTableSql::create_view(view, &*self.0)?;
Ok(())
}
fn read_view(&self, view_id: &str) -> FlowyResult<View> {
let table = ViewTableSql::read_view(view_id, &*self.0)?;
Ok(View::from(table))
}
fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<View>> {
let tables = ViewTableSql::read_views(belong_to_id, &*self.0)?;
let views = tables.into_iter().map(View::from).collect::<Vec<_>>();
Ok(views)
}
fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> {
let _ = ViewTableSql::update_view(changeset, &*self.0)?;
Ok(())
}
fn delete_view(&self, view_id: &str) -> FlowyResult<()> {
let _ = ViewTableSql::delete_view(view_id, &*self.0)?;
Ok(())
}
fn create_trash(&self, trashes: Vec<Trash>) -> FlowyResult<()> {
let _ = TrashTableSql::create_trash(trashes, &*self.0)?;
Ok(())
}
fn read_all_trash(&self) -> FlowyResult<RepeatedTrash> { TrashTableSql::read_all(&*self.0) }
fn delete_all_trash(&self) -> FlowyResult<()> { TrashTableSql::delete_all(&*self.0) }
fn read_trash(&self, trash_id: &str) -> FlowyResult<Trash> {
let table = TrashTableSql::read(trash_id, &*self.0)?;
Ok(Trash::from(table))
}
fn delete_trash(&self, trash_ids: Vec<String>) -> FlowyResult<()> {
for trash_id in &trash_ids {
let _ = TrashTableSql::delete_trash(&trash_id, &*self.0)?;
}
Ok(())
}
}
// https://www.reddit.com/r/rust/comments/droxdg/why_arent_traits_impld_for_boxdyn_trait/
impl<T> FlowyCorePersistenceTransaction for Box<T>
where
T: FlowyCorePersistenceTransaction + ?Sized,
{
fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()> {
(**self).create_workspace(user_id, workspace)
}
fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>> {
(**self).read_workspaces(user_id, workspace_id)
}
fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> {
(**self).update_workspace(changeset)
}
fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { (**self).delete_workspace(workspace_id) }
fn create_app(&self, app: App) -> FlowyResult<()> { (**self).create_app(app) }
fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { (**self).update_app(changeset) }
fn read_app(&self, app_id: &str) -> FlowyResult<App> { (**self).read_app(app_id) }
fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
(**self).read_workspace_apps(workspace_id)
}
fn delete_app(&self, app_id: &str) -> FlowyResult<App> { (**self).delete_app(app_id) }
fn create_view(&self, view: View) -> FlowyResult<()> { (**self).create_view(view) }
fn read_view(&self, view_id: &str) -> FlowyResult<View> { (**self).read_view(view_id) }
fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<View>> { (**self).read_views(belong_to_id) }
fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { (**self).update_view(changeset) }
fn delete_view(&self, view_id: &str) -> FlowyResult<()> { (**self).delete_view(view_id) }
fn create_trash(&self, trashes: Vec<Trash>) -> FlowyResult<()> { (**self).create_trash(trashes) }
fn read_all_trash(&self) -> FlowyResult<RepeatedTrash> { (**self).read_all_trash() }
fn delete_all_trash(&self) -> FlowyResult<()> { (**self).delete_all_trash() }
fn read_trash(&self, trash_id: &str) -> FlowyResult<Trash> { (**self).read_trash(trash_id) }
fn delete_trash(&self, trash_ids: Vec<String>) -> FlowyResult<()> { (**self).delete_trash(trash_ids) }
}

View File

@ -4,7 +4,7 @@ use crate::{
view::{RepeatedView, UpdateViewParams, View, ViewType}, view::{RepeatedView, UpdateViewParams, View, ViewType},
}, },
errors::FlowyError, errors::FlowyError,
services::app::sql::AppTable, services::persistence::version_1::app_sql::AppTable,
}; };
use diesel::sql_types::Integer; use diesel::sql_types::Integer;
use flowy_database::{ use flowy_database::{
@ -14,8 +14,7 @@ use flowy_database::{
}; };
use lib_infra::timestamp; use lib_infra::timestamp;
pub struct ViewTableSql {} pub struct ViewTableSql();
impl ViewTableSql { impl ViewTableSql {
pub(crate) fn create_view(view: View, conn: &SqliteConnection) -> Result<(), FlowyError> { pub(crate) fn create_view(view: View, conn: &SqliteConnection) -> Result<(), FlowyError> {
let view_table = ViewTable::new(view); let view_table = ViewTable::new(view);
@ -182,7 +181,7 @@ impl std::convert::From<ViewTable> for Trash {
#[derive(AsChangeset, Identifiable, Clone, Default, Debug)] #[derive(AsChangeset, Identifiable, Clone, Default, Debug)]
#[table_name = "view_table"] #[table_name = "view_table"]
pub(crate) struct ViewChangeset { pub struct ViewChangeset {
pub id: String, pub id: String,
pub name: Option<String>, pub name: Option<String>,
pub desc: Option<String>, pub desc: Option<String>,

View File

@ -10,8 +10,7 @@ use flowy_database::{
prelude::*, prelude::*,
schema::{workspace_table, workspace_table::dsl}, schema::{workspace_table, workspace_table::dsl},
}; };
pub(crate) struct WorkspaceTableSql {} pub(crate) struct WorkspaceTableSql();
impl WorkspaceTableSql { impl WorkspaceTableSql {
pub(crate) fn create_workspace( pub(crate) fn create_workspace(
user_id: &str, user_id: &str,
@ -22,7 +21,7 @@ impl WorkspaceTableSql {
match diesel_record_count!(workspace_table, &table.id, conn) { match diesel_record_count!(workspace_table, &table.id, conn) {
0 => diesel_insert_table!(workspace_table, &table, conn), 0 => diesel_insert_table!(workspace_table, &table, conn),
_ => { _ => {
let changeset = WorkspaceTableChangeset::from_table(table); let changeset = WorkspaceChangeset::from_table(table);
diesel_update_table!(workspace_table, changeset, conn); diesel_update_table!(workspace_table, changeset, conn);
}, },
} }
@ -49,10 +48,7 @@ impl WorkspaceTableSql {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn update_workspace( pub(crate) fn update_workspace(changeset: WorkspaceChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> {
changeset: WorkspaceTableChangeset,
conn: &SqliteConnection,
) -> Result<(), FlowyError> {
diesel_update_table!(workspace_table, changeset, conn); diesel_update_table!(workspace_table, changeset, conn);
Ok(()) Ok(())
} }
@ -106,15 +102,15 @@ impl std::convert::From<WorkspaceTable> for Workspace {
#[derive(AsChangeset, Identifiable, Clone, Default, Debug)] #[derive(AsChangeset, Identifiable, Clone, Default, Debug)]
#[table_name = "workspace_table"] #[table_name = "workspace_table"]
pub struct WorkspaceTableChangeset { pub struct WorkspaceChangeset {
pub id: String, pub id: String,
pub name: Option<String>, pub name: Option<String>,
pub desc: Option<String>, pub desc: Option<String>,
} }
impl WorkspaceTableChangeset { impl WorkspaceChangeset {
pub fn new(params: UpdateWorkspaceParams) -> Self { pub fn new(params: UpdateWorkspaceParams) -> Self {
WorkspaceTableChangeset { WorkspaceChangeset {
id: params.id, id: params.id,
name: params.name, name: params.name,
desc: params.desc, desc: params.desc,
@ -122,7 +118,7 @@ impl WorkspaceTableChangeset {
} }
pub(crate) fn from_table(table: WorkspaceTable) -> Self { pub(crate) fn from_table(table: WorkspaceTable) -> Self {
WorkspaceTableChangeset { WorkspaceChangeset {
id: table.id, id: table.id,
name: Some(table.name), name: Some(table.name),
desc: Some(table.desc), desc: Some(table.desc),

View File

@ -2,16 +2,15 @@ use crate::{
dart_notification::{send_anonymous_dart_notification, WorkspaceNotification}, dart_notification::{send_anonymous_dart_notification, WorkspaceNotification},
entities::trash::{RepeatedTrash, RepeatedTrashId, Trash, TrashId, TrashType}, entities::trash::{RepeatedTrash, RepeatedTrashId, Trash, TrashId, TrashType},
errors::{FlowyError, FlowyResult}, errors::{FlowyError, FlowyResult},
module::{WorkspaceCloudService, WorkspaceDatabase, WorkspaceUser}, module::{WorkspaceCloudService, WorkspaceUser},
services::trash::sql::TrashTableSql, services::persistence::{FlowyCorePersistence, FlowyCorePersistenceTransaction},
}; };
use crossbeam_utils::thread;
use flowy_database::SqliteConnection;
use std::{fmt::Formatter, sync::Arc}; use std::{fmt::Formatter, sync::Arc};
use tokio::sync::{broadcast, mpsc}; use tokio::sync::{broadcast, mpsc};
pub struct TrashController { pub struct TrashController {
pub database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
notify: broadcast::Sender<TrashEvent>, notify: broadcast::Sender<TrashEvent>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
@ -19,14 +18,13 @@ pub struct TrashController {
impl TrashController { impl TrashController {
pub fn new( pub fn new(
database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
) -> Self { ) -> Self {
let (tx, _) = broadcast::channel(10); let (tx, _) = broadcast::channel(10);
Self { Self {
database, persistence,
notify: tx, notify: tx,
cloud_service, cloud_service,
user, user,
@ -38,22 +36,16 @@ impl TrashController {
#[tracing::instrument(level = "debug", skip(self), fields(putback) err)] #[tracing::instrument(level = "debug", skip(self), fields(putback) err)]
pub async fn putback(&self, trash_id: &str) -> FlowyResult<()> { pub async fn putback(&self, trash_id: &str) -> FlowyResult<()> {
let (tx, mut rx) = mpsc::channel::<FlowyResult<()>>(1); let (tx, mut rx) = mpsc::channel::<FlowyResult<()>>(1);
let trash_table = TrashTableSql::read(trash_id, &*self.database.db_connection()?)?; let trash = self.persistence.begin_transaction(|transaction| {
let _ = thread::scope(|_s| { let trash = transaction.read_trash(trash_id);
let conn = self.database.db_connection()?; let _ = transaction.delete_trash(vec![trash_id.to_owned()])?;
conn.immediate_transaction::<_, FlowyError, _>(|| { notify_trash_changed(transaction.read_all_trash()?);
let _ = TrashTableSql::delete_trash(trash_id, &*conn)?; trash
notify_trash_changed(TrashTableSql::read_all(&conn)?);
Ok(())
})?; })?;
Ok::<(), FlowyError>(())
})
.unwrap()?;
let identifier = TrashId { let identifier = TrashId {
id: trash_table.id, id: trash.id,
ty: trash_table.ty.into(), ty: trash.ty,
}; };
let _ = self.delete_trash_on_server(RepeatedTrashId { let _ = self.delete_trash_on_server(RepeatedTrashId {
@ -69,15 +61,11 @@ impl TrashController {
#[tracing::instrument(level = "debug", skip(self) err)] #[tracing::instrument(level = "debug", skip(self) err)]
pub async fn restore_all(&self) -> FlowyResult<()> { pub async fn restore_all(&self) -> FlowyResult<()> {
let repeated_trash = thread::scope(|_s| { let repeated_trash = self.persistence.begin_transaction(|transaction| {
let conn = self.database.db_connection()?; let trash = transaction.read_all_trash();
conn.immediate_transaction::<_, FlowyError, _>(|| { let _ = transaction.delete_all_trash();
let repeated_trash = TrashTableSql::read_all(&*conn)?; trash
let _ = TrashTableSql::delete_all(&*conn)?; })?;
Ok(repeated_trash)
})
})
.unwrap()?;
let identifiers: RepeatedTrashId = repeated_trash.items.clone().into(); let identifiers: RepeatedTrashId = repeated_trash.items.clone().into();
let (tx, mut rx) = mpsc::channel::<FlowyResult<()>>(1); let (tx, mut rx) = mpsc::channel::<FlowyResult<()>>(1);
@ -91,7 +79,9 @@ impl TrashController {
#[tracing::instrument(level = "debug", skip(self), err)] #[tracing::instrument(level = "debug", skip(self), err)]
pub async fn delete_all(&self) -> FlowyResult<()> { pub async fn delete_all(&self) -> FlowyResult<()> {
let repeated_trash = TrashTableSql::read_all(&*(self.database.db_connection()?))?; let repeated_trash = self
.persistence
.begin_transaction(|transaction| transaction.read_all_trash())?;
let trash_identifiers: RepeatedTrashId = repeated_trash.items.clone().into(); let trash_identifiers: RepeatedTrashId = repeated_trash.items.clone().into();
let _ = self.delete_with_identifiers(trash_identifiers.clone()).await?; let _ = self.delete_with_identifiers(trash_identifiers.clone()).await?;
@ -103,7 +93,10 @@ impl TrashController {
#[tracing::instrument(level = "debug", skip(self), err)] #[tracing::instrument(level = "debug", skip(self), err)]
pub async fn delete(&self, trash_identifiers: RepeatedTrashId) -> FlowyResult<()> { pub async fn delete(&self, trash_identifiers: RepeatedTrashId) -> FlowyResult<()> {
let _ = self.delete_with_identifiers(trash_identifiers.clone()).await?; let _ = self.delete_with_identifiers(trash_identifiers.clone()).await?;
notify_trash_changed(TrashTableSql::read_all(&*(self.database.db_connection()?))?); let repeated_trash = self
.persistence
.begin_transaction(|transaction| transaction.read_all_trash())?;
notify_trash_changed(repeated_trash);
let _ = self.delete_trash_on_server(trash_identifiers)?; let _ = self.delete_trash_on_server(trash_identifiers)?;
Ok(()) Ok(())
@ -122,14 +115,15 @@ impl TrashController {
Err(e) => log::error!("{}", e), Err(e) => log::error!("{}", e),
}, },
} }
let _ = self.persistence.begin_transaction(|transaction| {
let conn = self.database.db_connection()?; let ids = trash_identifiers
conn.immediate_transaction::<_, FlowyError, _>(|| { .items
for trash_identifier in &trash_identifiers.items { .into_iter()
let _ = TrashTableSql::delete_trash(&trash_identifier.id, &conn)?; .map(|item| item.id)
} .collect::<Vec<_>>();
Ok(()) transaction.delete_trash(ids)
})?; })?;
Ok(()) Ok(())
} }
@ -156,19 +150,13 @@ impl TrashController {
) )
.as_str(), .as_str(),
); );
let _ = thread::scope(|_s| {
let conn = self.database.db_connection()?;
conn.immediate_transaction::<_, FlowyError, _>(|| {
let _ = TrashTableSql::create_trash(repeated_trash.clone(), &*conn)?;
let _ = self.create_trash_on_server(repeated_trash);
notify_trash_changed(TrashTableSql::read_all(&conn)?); let _ = self.persistence.begin_transaction(|transaction| {
let _ = transaction.create_trash(repeated_trash.clone())?;
let _ = self.create_trash_on_server(repeated_trash);
notify_trash_changed(transaction.read_all_trash()?);
Ok(()) Ok(())
})?; })?;
Ok::<(), FlowyError>(())
})
.unwrap()?;
let _ = self.notify.send(TrashEvent::NewTrash(identifiers.into(), tx)); let _ = self.notify.send(TrashEvent::NewTrash(identifiers.into(), tx));
let _ = rx.recv().await.unwrap()?; let _ = rx.recv().await.unwrap()?;
@ -177,14 +165,20 @@ impl TrashController {
pub fn subscribe(&self) -> broadcast::Receiver<TrashEvent> { self.notify.subscribe() } pub fn subscribe(&self) -> broadcast::Receiver<TrashEvent> { self.notify.subscribe() }
pub fn read_trash(&self, conn: &SqliteConnection) -> Result<RepeatedTrash, FlowyError> { pub fn read_trash(&self) -> Result<RepeatedTrash, FlowyError> {
let repeated_trash = TrashTableSql::read_all(&*conn)?; let repeated_trash = self
.persistence
.begin_transaction(|transaction| transaction.read_all_trash())?;
let _ = self.read_trash_on_server()?; let _ = self.read_trash_on_server()?;
Ok(repeated_trash) Ok(repeated_trash)
} }
pub fn read_trash_ids(&self, conn: &SqliteConnection) -> Result<Vec<String>, FlowyError> { pub fn read_trash_ids<'a>(
let ids = TrashTableSql::read_all(&*conn)? &self,
transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
) -> Result<Vec<String>, FlowyError> {
let ids = transaction
.read_all_trash()?
.into_inner() .into_inner()
.into_iter() .into_iter()
.map(|item| item.id) .map(|item| item.id)
@ -227,17 +221,15 @@ impl TrashController {
fn read_trash_on_server(&self) -> FlowyResult<()> { fn read_trash_on_server(&self) -> FlowyResult<()> {
let token = self.user.token()?; let token = self.user.token()?;
let server = self.cloud_service.clone(); let server = self.cloud_service.clone();
let pool = self.database.db_pool()?; let persistence = self.persistence.clone();
tokio::spawn(async move { tokio::spawn(async move {
match server.read_trash(&token).await { match server.read_trash(&token).await {
Ok(repeated_trash) => { Ok(repeated_trash) => {
tracing::debug!("Remote trash count: {}", repeated_trash.items.len()); tracing::debug!("Remote trash count: {}", repeated_trash.items.len());
match pool.get() { let result = persistence.begin_transaction(|transaction| {
Ok(conn) => { let _ = transaction.create_trash(repeated_trash.items.clone())?;
let result = conn.immediate_transaction::<_, FlowyError, _>(|| { transaction.read_all_trash()
let _ = TrashTableSql::create_trash(repeated_trash.items.clone(), &*conn)?;
TrashTableSql::read_all(&conn)
}); });
match result { match result {
@ -247,9 +239,6 @@ impl TrashController {
Err(e) => log::error!("Save trash failed: {:?}", e), Err(e) => log::error!("Save trash failed: {:?}", e),
} }
}, },
Err(e) => log::error!("Require db connection failed: {:?}", e),
}
},
Err(e) => log::error!("Read trash failed: {:?}", e), Err(e) => log::error!("Read trash failed: {:?}", e),
} }
}); });

View File

@ -10,8 +10,7 @@ use std::sync::Arc;
pub(crate) async fn read_trash_handler( pub(crate) async fn read_trash_handler(
controller: Unit<Arc<TrashController>>, controller: Unit<Arc<TrashController>>,
) -> DataResult<RepeatedTrash, FlowyError> { ) -> DataResult<RepeatedTrash, FlowyError> {
let conn = controller.database.db_connection()?; let repeated_trash = controller.read_trash()?;
let repeated_trash = controller.read_trash(&conn)?;
data_result(repeated_trash) data_result(repeated_trash)
} }

View File

@ -1,3 +1,2 @@
pub mod controller; pub mod controller;
pub mod event_handler; pub mod event_handler;
mod sql;

View File

@ -3,7 +3,7 @@ use flowy_collaboration::entities::{
doc::{DocumentDelta, DocumentId}, doc::{DocumentDelta, DocumentId},
revision::{RepeatedRevision, Revision}, revision::{RepeatedRevision, Revision},
}; };
use flowy_database::SqliteConnection;
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use std::{collections::HashSet, sync::Arc}; use std::{collections::HashSet, sync::Arc};
@ -14,9 +14,9 @@ use crate::{
view::{CreateViewParams, RepeatedView, UpdateViewParams, View, ViewId}, view::{CreateViewParams, RepeatedView, UpdateViewParams, View, ViewId},
}, },
errors::{FlowyError, FlowyResult}, errors::{FlowyError, FlowyResult},
module::{WorkspaceCloudService, WorkspaceDatabase, WorkspaceUser}, module::{WorkspaceCloudService, WorkspaceUser},
services::{ services::{
view::sql::{ViewChangeset, ViewTable, ViewTableSql}, persistence::{FlowyCorePersistence, FlowyCorePersistenceTransaction, ViewChangeset},
TrashController, TrashController,
TrashEvent, TrashEvent,
}, },
@ -31,7 +31,7 @@ const LATEST_VIEW_ID: &str = "latest_view_id";
pub(crate) struct ViewController { pub(crate) struct ViewController {
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
trash_controller: Arc<TrashController>, trash_controller: Arc<TrashController>,
document_ctx: Arc<DocumentContext>, document_ctx: Arc<DocumentContext>,
} }
@ -39,7 +39,7 @@ pub(crate) struct ViewController {
impl ViewController { impl ViewController {
pub(crate) fn new( pub(crate) fn new(
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
trash_can: Arc<TrashController>, trash_can: Arc<TrashController>,
document_ctx: Arc<DocumentContext>, document_ctx: Arc<DocumentContext>,
@ -47,7 +47,7 @@ impl ViewController {
Self { Self {
user, user,
cloud_service, cloud_service,
database, persistence,
trash_controller: trash_can, trash_controller: trash_can,
document_ctx, document_ctx,
} }
@ -77,51 +77,37 @@ impl ViewController {
} }
pub(crate) async fn create_view_on_local(&self, view: View) -> Result<(), FlowyError> { pub(crate) async fn create_view_on_local(&self, view: View) -> Result<(), FlowyError> {
let conn = &*self.database.db_connection()?; let trash_controller = self.trash_controller.clone();
let trash_can = self.trash_controller.clone(); self.persistence.begin_transaction(|transaction| {
conn.immediate_transaction::<_, FlowyError, _>(|| {
let belong_to_id = view.belong_to_id.clone(); let belong_to_id = view.belong_to_id.clone();
let _ = self.save_view(view, conn)?; let _ = transaction.create_view(view)?;
let _ = notify_views_changed(&belong_to_id, trash_can, &conn)?; let _ = notify_views_changed(&belong_to_id, trash_controller, &transaction)?;
Ok(())
})?;
Ok(())
}
pub(crate) fn save_view(&self, view: View, conn: &SqliteConnection) -> Result<(), FlowyError> {
let _ = ViewTableSql::create_view(view, conn)?;
Ok(()) Ok(())
})
} }
#[tracing::instrument(skip(self, params), fields(view_id = %params.view_id), err)] #[tracing::instrument(skip(self, params), fields(view_id = %params.view_id), err)]
pub(crate) async fn read_view(&self, params: ViewId) -> Result<View, FlowyError> { pub(crate) async fn read_view(&self, params: ViewId) -> Result<View, FlowyError> {
let conn = self.database.db_connection()?; let view = self.persistence.begin_transaction(|transaction| {
let view_table = ViewTableSql::read_view(&params.view_id, &*conn)?; let view = transaction.read_view(&params.view_id)?;
let trash_ids = self.trash_controller.read_trash_ids(&transaction)?;
let trash_ids = self.trash_controller.read_trash_ids(&conn)?; if trash_ids.contains(&view.id) {
if trash_ids.contains(&view_table.id) {
return Err(FlowyError::record_not_found()); return Err(FlowyError::record_not_found());
} }
Ok(view)
let view: View = view_table.into(); })?;
let _ = self.read_view_on_server(params); let _ = self.read_view_on_server(params);
Ok(view) Ok(view)
} }
pub(crate) fn read_view_tables(&self, ids: Vec<String>) -> Result<Vec<ViewTable>, FlowyError> { pub(crate) fn read_local_views(&self, ids: Vec<String>) -> Result<Vec<View>, FlowyError> {
let conn = &*self.database.db_connection()?; self.persistence.begin_transaction(|transaction| {
let mut view_tables = vec![]; let mut views = vec![];
conn.immediate_transaction::<_, FlowyError, _>(|| {
for view_id in ids { for view_id in ids {
view_tables.push(ViewTableSql::read_view(&view_id, conn)?); views.push(transaction.read_view(&view_id)?);
} }
Ok(()) Ok(views)
})?; })
Ok(view_tables)
} }
#[tracing::instrument(level = "debug", skip(self, params), fields(doc_id = %params.doc_id), err)] #[tracing::instrument(level = "debug", skip(self, params), fields(doc_id = %params.doc_id), err)]
@ -156,7 +142,10 @@ impl ViewController {
#[tracing::instrument(level = "debug", skip(self, params), fields(doc_id = %params.doc_id), err)] #[tracing::instrument(level = "debug", skip(self, params), fields(doc_id = %params.doc_id), err)]
pub(crate) async fn duplicate_view(&self, params: DocumentId) -> Result<(), FlowyError> { pub(crate) async fn duplicate_view(&self, params: DocumentId) -> Result<(), FlowyError> {
let view: View = ViewTableSql::read_view(&params.doc_id, &*self.database.db_connection()?)?.into(); let view = self
.persistence
.begin_transaction(|transaction| transaction.read_view(&params.doc_id))?;
let editor = self.document_ctx.controller.open_document(&params.doc_id).await?; let editor = self.document_ctx.controller.open_document(&params.doc_id).await?;
let document_json = editor.document_json().await?; let document_json = editor.document_json().await?;
let duplicate_params = CreateViewParams { let duplicate_params = CreateViewParams {
@ -186,31 +175,27 @@ impl ViewController {
// belong_to_id will be the app_id or view_id. // belong_to_id will be the app_id or view_id.
#[tracing::instrument(level = "debug", skip(self), err)] #[tracing::instrument(level = "debug", skip(self), err)]
pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<RepeatedView, FlowyError> { pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<RepeatedView, FlowyError> {
// TODO: read from server self.persistence.begin_transaction(|transaction| {
let conn = self.database.db_connection()?; read_belonging_views_on_local(belong_to_id, self.trash_controller.clone(), &transaction)
let repeated_view = read_belonging_views_on_local(belong_to_id, self.trash_controller.clone(), &conn)?; })
Ok(repeated_view)
} }
#[tracing::instrument(level = "debug", skip(self, params), err)] #[tracing::instrument(level = "debug", skip(self, params), err)]
pub(crate) async fn update_view(&self, params: UpdateViewParams) -> Result<View, FlowyError> { pub(crate) async fn update_view(&self, params: UpdateViewParams) -> Result<View, FlowyError> {
let conn = &*self.database.db_connection()?;
let changeset = ViewChangeset::new(params.clone()); let changeset = ViewChangeset::new(params.clone());
let view_id = changeset.id.clone(); let view_id = changeset.id.clone();
let view = self.persistence.begin_transaction(|transaction| {
let updated_view = conn.immediate_transaction::<_, FlowyError, _>(|| { let _ = transaction.update_view(changeset)?;
let _ = ViewTableSql::update_view(changeset, conn)?; let view = transaction.read_view(&view_id)?;
let view: View = ViewTableSql::read_view(&view_id, conn)?.into(); send_dart_notification(&view_id, WorkspaceNotification::ViewUpdated)
.payload(view.clone())
.send();
let _ = notify_views_changed(&view.belong_to_id, self.trash_controller.clone(), &transaction)?;
Ok(view) Ok(view)
})?; })?;
send_dart_notification(&view_id, WorkspaceNotification::ViewUpdated)
.payload(updated_view.clone())
.send();
//
let _ = notify_views_changed(&updated_view.belong_to_id, self.trash_controller.clone(), conn)?;
let _ = self.update_view_on_server(params); let _ = self.update_view_on_server(params);
Ok(updated_view) Ok(view)
} }
pub(crate) async fn receive_document_delta(&self, params: DocumentDelta) -> Result<DocumentDelta, FlowyError> { pub(crate) async fn receive_document_delta(&self, params: DocumentDelta) -> Result<DocumentDelta, FlowyError> {
@ -222,9 +207,10 @@ impl ViewController {
match KV::get_str(LATEST_VIEW_ID) { match KV::get_str(LATEST_VIEW_ID) {
None => Ok(None), None => Ok(None),
Some(view_id) => { Some(view_id) => {
let conn = self.database.db_connection()?; let view = self
let view_table = ViewTableSql::read_view(&view_id, &*conn)?; .persistence
Ok(Some(view_table.into())) .begin_transaction(|transaction| transaction.read_view(&view_id))?;
Ok(Some(view))
}, },
} }
} }
@ -260,14 +246,12 @@ impl ViewController {
fn read_view_on_server(&self, params: ViewId) -> Result<(), FlowyError> { fn read_view_on_server(&self, params: ViewId) -> Result<(), FlowyError> {
let token = self.user.token()?; let token = self.user.token()?;
let server = self.cloud_service.clone(); let server = self.cloud_service.clone();
let pool = self.database.db_pool()?; let persistence = self.persistence.clone();
// TODO: Retry with RetryAction? // TODO: Retry with RetryAction?
tokio::spawn(async move { tokio::spawn(async move {
match server.read_view(&token, params).await { match server.read_view(&token, params).await {
Ok(Some(view)) => match pool.get() { Ok(Some(view)) => {
Ok(conn) => { match persistence.begin_transaction(|transaction| transaction.create_view(view.clone())) {
let result = ViewTableSql::create_view(view.clone(), &conn);
match result {
Ok(_) => { Ok(_) => {
send_dart_notification(&view.id, WorkspaceNotification::ViewUpdated) send_dart_notification(&view.id, WorkspaceNotification::ViewUpdated)
.payload(view.clone()) .payload(view.clone())
@ -276,8 +260,6 @@ impl ViewController {
Err(e) => log::error!("Save view failed: {:?}", e), Err(e) => log::error!("Save view failed: {:?}", e),
} }
}, },
Err(e) => log::error!("Require db connection failed: {:?}", e),
},
Ok(None) => {}, Ok(None) => {},
Err(e) => log::error!("Read view failed: {:?}", e), Err(e) => log::error!("Read view failed: {:?}", e),
} }
@ -287,9 +269,9 @@ impl ViewController {
fn listen_trash_can_event(&self) { fn listen_trash_can_event(&self) {
let mut rx = self.trash_controller.subscribe(); let mut rx = self.trash_controller.subscribe();
let database = self.database.clone(); let persistence = self.persistence.clone();
let document = self.document_ctx.clone(); let document = self.document_ctx.clone();
let trash_can = self.trash_controller.clone(); let trash_controller = self.trash_controller.clone();
let _ = tokio::spawn(async move { let _ = tokio::spawn(async move {
loop { loop {
let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move { let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move {
@ -300,96 +282,87 @@ impl ViewController {
})); }));
if let Some(event) = stream.next().await { if let Some(event) = stream.next().await {
handle_trash_event(database.clone(), document.clone(), trash_can.clone(), event).await handle_trash_event(persistence.clone(), document.clone(), trash_controller.clone(), event).await
} }
} }
}); });
} }
} }
#[tracing::instrument(level = "trace", skip(database, context, trash_can))] #[tracing::instrument(level = "trace", skip(persistence, context, trash_can))]
async fn handle_trash_event( async fn handle_trash_event(
database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
context: Arc<DocumentContext>, context: Arc<DocumentContext>,
trash_can: Arc<TrashController>, trash_can: Arc<TrashController>,
event: TrashEvent, event: TrashEvent,
) { ) {
let db_result = database.db_connection();
match event { match event {
TrashEvent::NewTrash(identifiers, ret) => { TrashEvent::NewTrash(identifiers, ret) => {
let result = || { let result = persistence.begin_transaction(|transaction| {
let conn = &*db_result?; let views = read_local_views_with_transaction(identifiers, &transaction)?;
let view_tables = read_view_tables(identifiers, conn)?; for view in views {
for view_table in view_tables { let _ = notify_views_changed(&view.belong_to_id, trash_can.clone(), &transaction)?;
let _ = notify_views_changed(&view_table.belong_to_id, trash_can.clone(), conn)?; notify_dart(view, WorkspaceNotification::ViewDeleted);
notify_dart(view_table, WorkspaceNotification::ViewDeleted);
} }
Ok::<(), FlowyError>(()) Ok(())
}; });
let _ = ret.send(result()).await; let _ = ret.send(result).await;
}, },
TrashEvent::Putback(identifiers, ret) => { TrashEvent::Putback(identifiers, ret) => {
let result = || { let result = persistence.begin_transaction(|transaction| {
let conn = &*db_result?; let views = read_local_views_with_transaction(identifiers, &transaction)?;
let view_tables = read_view_tables(identifiers, conn)?; for view in views {
for view_table in view_tables { let _ = notify_views_changed(&view.belong_to_id, trash_can.clone(), &transaction)?;
let _ = notify_views_changed(&view_table.belong_to_id, trash_can.clone(), conn)?; notify_dart(view, WorkspaceNotification::ViewRestored);
notify_dart(view_table, WorkspaceNotification::ViewRestored);
} }
Ok::<(), FlowyError>(()) Ok(())
}; });
let _ = ret.send(result()).await; let _ = ret.send(result).await;
}, },
TrashEvent::Delete(identifiers, ret) => { TrashEvent::Delete(identifiers, ret) => {
let result = || { let result = persistence.begin_transaction(|transaction| {
let conn = &*db_result?;
let _ = conn.immediate_transaction::<_, FlowyError, _>(|| {
let mut notify_ids = HashSet::new(); let mut notify_ids = HashSet::new();
for identifier in identifiers.items { for identifier in identifiers.items {
let view_table = ViewTableSql::read_view(&identifier.id, conn)?; let view = transaction.read_view(&identifier.id)?;
let _ = ViewTableSql::delete_view(&identifier.id, conn)?; let _ = transaction.delete_view(&identifier.id)?;
let _ = context.controller.delete(&identifier.id)?; let _ = context.controller.delete(&identifier.id)?;
notify_ids.insert(view_table.belong_to_id); notify_ids.insert(view.belong_to_id);
} }
for notify_id in notify_ids { for notify_id in notify_ids {
let _ = notify_views_changed(&notify_id, trash_can.clone(), conn)?; let _ = notify_views_changed(&notify_id, trash_can.clone(), &transaction)?;
} }
Ok(()) Ok(())
})?; });
Ok::<(), FlowyError>(()) let _ = ret.send(result).await;
};
let _ = ret.send(result()).await;
}, },
} }
} }
fn read_view_tables(identifiers: RepeatedTrashId, conn: &SqliteConnection) -> Result<Vec<ViewTable>, FlowyError> { fn read_local_views_with_transaction<'a>(
let mut view_tables = vec![]; identifiers: RepeatedTrashId,
let _ = conn.immediate_transaction::<_, FlowyError, _>(|| { transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
) -> Result<Vec<View>, FlowyError> {
let mut views = vec![];
for identifier in identifiers.items { for identifier in identifiers.items {
let view_table = ViewTableSql::read_view(&identifier.id, conn)?; let view = transaction.read_view(&identifier.id)?;
view_tables.push(view_table); views.push(view);
} }
Ok(()) Ok(views)
})?;
Ok(view_tables)
} }
fn notify_dart(view_table: ViewTable, notification: WorkspaceNotification) { fn notify_dart(view: View, notification: WorkspaceNotification) {
let view: View = view_table.into();
send_dart_notification(&view.id, notification).payload(view).send(); send_dart_notification(&view.id, notification).payload(view).send();
} }
#[tracing::instrument(skip(belong_to_id, trash_controller, conn), fields(view_count), err)] #[tracing::instrument(skip(belong_to_id, trash_controller, transaction), fields(view_count), err)]
fn notify_views_changed( fn notify_views_changed<'a>(
belong_to_id: &str, belong_to_id: &str,
trash_controller: Arc<TrashController>, trash_controller: Arc<TrashController>,
conn: &SqliteConnection, transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
) -> FlowyResult<()> { ) -> FlowyResult<()> {
let repeated_view = read_belonging_views_on_local(belong_to_id, trash_controller.clone(), conn)?; let repeated_view = read_belonging_views_on_local(belong_to_id, trash_controller.clone(), transaction)?;
tracing::Span::current().record("view_count", &format!("{}", repeated_view.len()).as_str()); tracing::Span::current().record("view_count", &format!("{}", repeated_view.len()).as_str());
send_dart_notification(&belong_to_id, WorkspaceNotification::AppViewsChanged) send_dart_notification(&belong_to_id, WorkspaceNotification::AppViewsChanged)
.payload(repeated_view) .payload(repeated_view)
@ -397,19 +370,14 @@ fn notify_views_changed(
Ok(()) Ok(())
} }
fn read_belonging_views_on_local( fn read_belonging_views_on_local<'a>(
belong_to_id: &str, belong_to_id: &str,
trash_controller: Arc<TrashController>, trash_controller: Arc<TrashController>,
conn: &SqliteConnection, transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
) -> FlowyResult<RepeatedView> { ) -> FlowyResult<RepeatedView> {
let mut view_tables = ViewTableSql::read_views(belong_to_id, conn)?; let mut views = transaction.read_views(belong_to_id)?;
let trash_ids = trash_controller.read_trash_ids(conn)?; let trash_ids = trash_controller.read_trash_ids(transaction)?;
view_tables.retain(|view_table| !trash_ids.contains(&view_table.id)); views.retain(|view_table| !trash_ids.contains(&view_table.id));
let views = view_tables
.into_iter()
.map(|view_table| view_table.into())
.collect::<Vec<View>>();
Ok(RepeatedView { items: views }) Ok(RepeatedView { items: views })
} }

View File

@ -70,9 +70,9 @@ pub(crate) async fn delete_view_handler(
} }
let trash = view_controller let trash = view_controller
.read_view_tables(params.items)? .read_local_views(params.items)?
.into_iter() .into_iter()
.map(|view_table| view_table.into()) .map(|view| view.into())
.collect::<Vec<Trash>>(); .collect::<Vec<Trash>>();
let _ = trash_controller.add(trash).await?; let _ = trash_controller.add(trash).await?;

View File

@ -1,3 +1,2 @@
pub mod controller; pub mod controller;
pub mod event_handler; pub mod event_handler;
mod sql;

View File

@ -1,20 +1,20 @@
use crate::{ use crate::{
dart_notification::*, dart_notification::*,
errors::*, errors::*,
module::{WorkspaceCloudService, WorkspaceDatabase, WorkspaceUser}, module::{WorkspaceCloudService, WorkspaceUser},
services::{ services::{
persistence::{FlowyCorePersistence, FlowyCorePersistenceTransaction, WorkspaceChangeset},
read_local_workspace_apps, read_local_workspace_apps,
workspace::sql::{WorkspaceTableChangeset, WorkspaceTableSql},
TrashController, TrashController,
}, },
}; };
use flowy_core_data_model::entities::{app::RepeatedApp, workspace::*}; use flowy_core_data_model::entities::{app::RepeatedApp, workspace::*};
use flowy_database::{kv::KV, SqliteConnection}; use flowy_database::kv::KV;
use std::sync::Arc; use std::sync::Arc;
pub struct WorkspaceController { pub struct WorkspaceController {
pub user: Arc<dyn WorkspaceUser>, pub user: Arc<dyn WorkspaceUser>,
pub(crate) database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
pub(crate) trash_controller: Arc<TrashController>, pub(crate) trash_controller: Arc<TrashController>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
} }
@ -22,13 +22,13 @@ pub struct WorkspaceController {
impl WorkspaceController { impl WorkspaceController {
pub(crate) fn new( pub(crate) fn new(
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
database: Arc<dyn WorkspaceDatabase>, persistence: Arc<FlowyCorePersistence>,
trash_can: Arc<TrashController>, trash_can: Arc<TrashController>,
cloud_service: Arc<dyn WorkspaceCloudService>, cloud_service: Arc<dyn WorkspaceCloudService>,
) -> Self { ) -> Self {
Self { Self {
user, user,
database, persistence,
trash_controller: trash_can, trash_controller: trash_can,
cloud_service, cloud_service,
} }
@ -47,49 +47,31 @@ impl WorkspaceController {
pub(crate) async fn create_workspace_on_local(&self, workspace: Workspace) -> Result<Workspace, FlowyError> { pub(crate) async fn create_workspace_on_local(&self, workspace: Workspace) -> Result<Workspace, FlowyError> {
let user_id = self.user.user_id()?; let user_id = self.user.user_id()?;
let token = self.user.token()?; let token = self.user.token()?;
let conn = &*self.database.db_connection()?; let workspaces = self.persistence.begin_transaction(|transaction| {
//[[immediate_transaction]] let _ = transaction.create_workspace(&user_id, workspace.clone())?;
// https://sqlite.org/lang_transaction.html transaction.read_workspaces(&user_id, None)
// IMMEDIATE cause the database connection to start a new write immediately, })?;
// without waiting for a write statement. The BEGIN IMMEDIATE might fail let repeated_workspace = RepeatedWorkspace { items: workspaces };
// with SQLITE_BUSY if another write transaction is already active on another
// database connection.
//
// EXCLUSIVE is similar to IMMEDIATE in that a write transaction is started
// immediately. EXCLUSIVE and IMMEDIATE are the same in WAL mode, but in
// other journaling modes, EXCLUSIVE prevents other database connections from
// reading the database while the transaction is underway.
conn.immediate_transaction::<_, FlowyError, _>(|| {
WorkspaceTableSql::create_workspace(&user_id, workspace.clone(), conn)?;
let repeated_workspace = self.read_local_workspaces(None, &user_id, conn)?;
send_dart_notification(&token, WorkspaceNotification::UserCreateWorkspace) send_dart_notification(&token, WorkspaceNotification::UserCreateWorkspace)
.payload(repeated_workspace) .payload(repeated_workspace)
.send(); .send();
Ok(())
})?;
set_current_workspace(&workspace.id); set_current_workspace(&workspace.id);
Ok(workspace) Ok(workspace)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) async fn update_workspace(&self, params: UpdateWorkspaceParams) -> Result<(), FlowyError> { pub(crate) async fn update_workspace(&self, params: UpdateWorkspaceParams) -> Result<(), FlowyError> {
let changeset = WorkspaceTableChangeset::new(params.clone()); let changeset = WorkspaceChangeset::new(params.clone());
let workspace_id = changeset.id.clone(); let workspace_id = changeset.id.clone();
let conn = &*self.database.db_connection()?; let workspace = self.persistence.begin_transaction(|transaction| {
conn.immediate_transaction::<_, FlowyError, _>(|| { let _ = transaction.update_workspace(changeset)?;
let _ = WorkspaceTableSql::update_workspace(changeset, conn)?;
let user_id = self.user.user_id()?; let user_id = self.user.user_id()?;
let workspace = self.read_local_workspace(workspace_id.clone(), &user_id, conn)?; self.read_local_workspace(workspace_id.clone(), &user_id, &transaction)
})?;
send_dart_notification(&workspace_id, WorkspaceNotification::WorkspaceUpdated) send_dart_notification(&workspace_id, WorkspaceNotification::WorkspaceUpdated)
.payload(workspace) .payload(workspace)
.send(); .send();
Ok(())
})?;
let _ = self.update_workspace_on_server(params)?; let _ = self.update_workspace_on_server(params)?;
Ok(()) Ok(())
@ -99,26 +81,23 @@ impl WorkspaceController {
pub(crate) async fn delete_workspace(&self, workspace_id: &str) -> Result<(), FlowyError> { pub(crate) async fn delete_workspace(&self, workspace_id: &str) -> Result<(), FlowyError> {
let user_id = self.user.user_id()?; let user_id = self.user.user_id()?;
let token = self.user.token()?; let token = self.user.token()?;
let conn = &*self.database.db_connection()?; let repeated_workspace = self.persistence.begin_transaction(|transaction| {
conn.immediate_transaction::<_, FlowyError, _>(|| { let _ = transaction.delete_workspace(workspace_id)?;
let _ = WorkspaceTableSql::delete_workspace(workspace_id, conn)?; self.read_local_workspaces(None, &user_id, &transaction)
let repeated_workspace = self.read_local_workspaces(None, &user_id, conn)?; })?;
send_dart_notification(&token, WorkspaceNotification::UserDeleteWorkspace) send_dart_notification(&token, WorkspaceNotification::UserDeleteWorkspace)
.payload(repeated_workspace) .payload(repeated_workspace)
.send(); .send();
Ok(())
})?;
let _ = self.delete_workspace_on_server(workspace_id)?; let _ = self.delete_workspace_on_server(workspace_id)?;
Ok(()) Ok(())
} }
pub(crate) async fn open_workspace(&self, params: WorkspaceId) -> Result<Workspace, FlowyError> { pub(crate) async fn open_workspace(&self, params: WorkspaceId) -> Result<Workspace, FlowyError> {
let user_id = self.user.user_id()?; let user_id = self.user.user_id()?;
let conn = self.database.db_connection()?;
if let Some(workspace_id) = params.workspace_id { if let Some(workspace_id) = params.workspace_id {
let workspace = self.read_local_workspace(workspace_id, &user_id, &*conn)?; let workspace = self
.persistence
.begin_transaction(|transaction| self.read_local_workspace(workspace_id, &user_id, &transaction))?;
set_current_workspace(&workspace.id); set_current_workspace(&workspace.id);
Ok(workspace) Ok(workspace)
} else { } else {
@ -128,52 +107,39 @@ impl WorkspaceController {
pub(crate) async fn read_current_workspace_apps(&self) -> Result<RepeatedApp, FlowyError> { pub(crate) async fn read_current_workspace_apps(&self) -> Result<RepeatedApp, FlowyError> {
let workspace_id = get_current_workspace()?; let workspace_id = get_current_workspace()?;
let conn = self.database.db_connection()?; let repeated_app = self.persistence.begin_transaction(|transaction| {
let repeated_app = self.read_local_apps(&workspace_id, &*conn)?; read_local_workspace_apps(&workspace_id, self.trash_controller.clone(), &transaction)
})?;
// TODO: read from server // TODO: read from server
Ok(repeated_app) Ok(repeated_app)
} }
#[tracing::instrument(level = "debug", skip(self, conn), err)] #[tracing::instrument(level = "debug", skip(self, transaction), err)]
pub(crate) fn read_local_workspaces( pub(crate) fn read_local_workspaces<'a>(
&self, &self,
workspace_id: Option<String>, workspace_id: Option<String>,
user_id: &str, user_id: &str,
conn: &SqliteConnection, transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
) -> Result<RepeatedWorkspace, FlowyError> { ) -> Result<RepeatedWorkspace, FlowyError> {
let workspace_id = workspace_id.to_owned(); let workspace_id = workspace_id.to_owned();
let workspace_tables = WorkspaceTableSql::read_workspaces(workspace_id, user_id, conn)?; let workspaces = transaction.read_workspaces(user_id, workspace_id)?;
let mut workspaces = vec![];
for table in workspace_tables {
let workspace: Workspace = table.into();
workspaces.push(workspace);
}
Ok(RepeatedWorkspace { items: workspaces }) Ok(RepeatedWorkspace { items: workspaces })
} }
pub(crate) fn read_local_workspace( pub(crate) fn read_local_workspace<'a>(
&self, &self,
workspace_id: String, workspace_id: String,
user_id: &str, user_id: &str,
conn: &SqliteConnection, transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
) -> Result<Workspace, FlowyError> { ) -> Result<Workspace, FlowyError> {
// Opti: fetch single workspace from local db let mut workspaces = transaction.read_workspaces(user_id, Some(workspace_id.clone()))?;
let mut repeated_workspace = self.read_local_workspaces(Some(workspace_id.clone()), user_id, conn)?; if workspaces.is_empty() {
if repeated_workspace.is_empty() {
return Err(FlowyError::record_not_found().context(format!("{} workspace not found", workspace_id))); return Err(FlowyError::record_not_found().context(format!("{} workspace not found", workspace_id)));
} }
debug_assert_eq!(workspaces.len(), 1);
debug_assert_eq!(repeated_workspace.len(), 1); let workspace = workspaces.drain(..1).collect::<Vec<Workspace>>().pop().unwrap();
let workspace = repeated_workspace.drain(..1).collect::<Vec<Workspace>>().pop().unwrap();
Ok(workspace) Ok(workspace)
} }
#[tracing::instrument(level = "debug", skip(self, conn), err)]
fn read_local_apps(&self, workspace_id: &str, conn: &SqliteConnection) -> Result<RepeatedApp, FlowyError> {
let repeated_app = read_local_workspace_apps(workspace_id, self.trash_controller.clone(), conn)?;
Ok(repeated_app)
}
} }
impl WorkspaceController { impl WorkspaceController {

View File

@ -2,18 +2,14 @@ use crate::{
context::CoreContext, context::CoreContext,
dart_notification::{send_dart_notification, WorkspaceNotification}, dart_notification::{send_dart_notification, WorkspaceNotification},
errors::FlowyError, errors::FlowyError,
services::{ services::{get_current_workspace, read_local_workspace_apps, WorkspaceController},
get_current_workspace,
read_local_workspace_apps,
workspace::sql::WorkspaceTableSql,
WorkspaceController,
},
}; };
use flowy_core_data_model::entities::{ use flowy_core_data_model::entities::{
app::RepeatedApp, app::RepeatedApp,
view::View, view::View,
workspace::{CurrentWorkspaceSetting, QueryWorkspaceRequest, RepeatedWorkspace, WorkspaceId, *}, workspace::{CurrentWorkspaceSetting, QueryWorkspaceRequest, RepeatedWorkspace, WorkspaceId, *},
}; };
use flowy_error::FlowyResult;
use lib_dispatch::prelude::{data_result, Data, DataResult, Unit}; use lib_dispatch::prelude::{data_result, Data, DataResult, Unit};
use std::{convert::TryInto, sync::Arc}; use std::{convert::TryInto, sync::Arc};
@ -53,21 +49,19 @@ pub(crate) async fn read_workspaces_handler(
) -> DataResult<RepeatedWorkspace, FlowyError> { ) -> DataResult<RepeatedWorkspace, FlowyError> {
let params: WorkspaceId = data.into_inner().try_into()?; let params: WorkspaceId = data.into_inner().try_into()?;
let user_id = core.user.user_id()?; let user_id = core.user.user_id()?;
let conn = &*core.database.db_connection()?;
let workspace_controller = core.workspace_controller.clone(); let workspace_controller = core.workspace_controller.clone();
let trash_controller = core.trash_controller.clone(); let trash_controller = core.trash_controller.clone();
let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| { let workspaces = core.persistence.begin_transaction(|transaction| {
let mut workspaces = workspace_controller.read_local_workspaces(params.workspace_id.clone(), &user_id, conn)?; let mut workspaces =
workspace_controller.read_local_workspaces(params.workspace_id.clone(), &user_id, &transaction)?;
for workspace in workspaces.iter_mut() { for workspace in workspaces.iter_mut() {
let apps = read_local_workspace_apps(&workspace.id, trash_controller.clone(), conn)?.into_inner(); let apps = read_local_workspace_apps(&workspace.id, trash_controller.clone(), &transaction)?.into_inner();
workspace.apps.items = apps; workspace.apps.items = apps;
} }
Ok(workspaces) Ok(workspaces)
})?; })?;
let _ = read_workspaces_on_server(core, user_id, params); let _ = read_workspaces_on_server(core, user_id, params);
data_result(workspaces) data_result(workspaces)
} }
@ -80,10 +74,11 @@ pub async fn read_cur_workspace_handler(
let params = WorkspaceId { let params = WorkspaceId {
workspace_id: Some(workspace_id.clone()), workspace_id: Some(workspace_id.clone()),
}; };
let conn = &*core.database.db_connection()?;
let workspace = core let workspace = core.persistence.begin_transaction(|transaction| {
.workspace_controller core.workspace_controller
.read_local_workspace(workspace_id, &user_id, conn)?; .read_local_workspace(workspace_id, &user_id, &transaction)
})?;
let latest_view: Option<View> = core.view_controller.latest_visit_view().unwrap_or(None); let latest_view: Option<View> = core.view_controller.latest_visit_view().unwrap_or(None);
let setting = CurrentWorkspaceSetting { workspace, latest_view }; let setting = CurrentWorkspaceSetting { workspace, latest_view };
@ -98,30 +93,29 @@ fn read_workspaces_on_server(
params: WorkspaceId, params: WorkspaceId,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let (token, server) = (core.user.token()?, core.cloud_service.clone()); let (token, server) = (core.user.token()?, core.cloud_service.clone());
let app_ctrl = core.app_controller.clone(); let _app_ctrl = core.app_controller.clone();
let view_ctrl = core.view_controller.clone(); let _view_ctrl = core.view_controller.clone();
let conn = core.database.db_connection()?; let persistence = core.persistence.clone();
tokio::spawn(async move { tokio::spawn(async move {
// Opti: handle the error and retry?
let workspaces = server.read_workspace(&token, params).await?; let workspaces = server.read_workspace(&token, params).await?;
let _ = (&*conn).immediate_transaction::<_, FlowyError, _>(|| { let _ = persistence.begin_transaction(|transaction| {
tracing::debug!("Save {} workspace", workspaces.len()); tracing::debug!("Save {} workspace", workspaces.len());
for workspace in &workspaces.items { for workspace in &workspaces.items {
let m_workspace = workspace.clone(); let m_workspace = workspace.clone();
let apps = m_workspace.apps.clone().into_inner(); let apps = m_workspace.apps.clone().into_inner();
let _ = WorkspaceTableSql::create_workspace(&user_id, m_workspace, &*conn)?; let _ = transaction.create_workspace(&user_id, m_workspace)?;
tracing::debug!("Save {} apps", apps.len()); tracing::debug!("Save {} apps", apps.len());
for app in apps { for app in apps {
let views = app.belongings.clone().into_inner(); let views = app.belongings.clone().into_inner();
match app_ctrl.save_app(app, &*conn) { match transaction.create_app(app) {
Ok(_) => {}, Ok(_) => {},
Err(e) => log::error!("create app failed: {:?}", e), Err(e) => log::error!("create app failed: {:?}", e),
} }
tracing::debug!("Save {} views", views.len()); tracing::debug!("Save {} views", views.len());
for view in views { for view in views {
match view_ctrl.save_view(view, &*conn) { match transaction.create_view(view) {
Ok(_) => {}, Ok(_) => {},
Err(e) => log::error!("create view failed: {:?}", e), Err(e) => log::error!("create view failed: {:?}", e),
} }

View File

@ -1,3 +1,2 @@
pub mod controller; pub mod controller;
pub mod event_handler; pub mod event_handler;
pub(crate) mod sql;

View File

@ -1,4 +1,4 @@
use crate::impl_def_and_def_mut; use crate::{entities::app::App, impl_def_and_def_mut};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use std::fmt::Formatter; use std::fmt::Formatter;
@ -123,3 +123,15 @@ pub struct RepeatedTrash {
} }
impl_def_and_def_mut!(RepeatedTrash, Trash); impl_def_and_def_mut!(RepeatedTrash, Trash);
impl std::convert::From<App> for Trash {
fn from(app: App) -> Self {
Trash {
id: app.id,
name: app.name,
modified_time: app.modified_time,
create_time: app.create_time,
ty: TrashType::App,
}
}
}