mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
parent
b3a0119c18
commit
4b24b41dd4
@ -1,4 +1,4 @@
|
||||
use flowy_core::MutexAppFlowyCore;
|
||||
use flowy_core::;
|
||||
use lib_dispatch::prelude::{
|
||||
AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode,
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
use flowy_core::{AppFlowyCore, MutexAppFlowyCore};
|
||||
use flowy_core::MutexAppFlowyCore;
|
||||
use lib_dispatch::prelude::{
|
||||
AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode,
|
||||
};
|
||||
|
@ -11,7 +11,6 @@ use client_api::error::AppResponseError;
|
||||
use flowy_error::FlowyError;
|
||||
use futures::stream::BoxStream;
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::FutureResult;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
@ -21,12 +20,12 @@ pub type StreamAnswer = BoxStream<'static, Result<QuestionStreamValue, FlowyErro
|
||||
pub type StreamComplete = BoxStream<'static, Result<Bytes, FlowyError>>;
|
||||
#[async_trait]
|
||||
pub trait ChatCloudService: Send + Sync + 'static {
|
||||
fn create_chat(
|
||||
async fn create_chat(
|
||||
&self,
|
||||
uid: &i64,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
) -> FutureResult<(), FlowyError>;
|
||||
) -> Result<(), FlowyError>;
|
||||
|
||||
async fn create_question(
|
||||
&self,
|
||||
|
@ -14,7 +14,6 @@ use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||
use lib_infra::isolate_stream::IsolateSink;
|
||||
use std::sync::{Arc, Weak};
|
||||
use tokio::sync::oneshot;
|
||||
use tracing::trace;
|
||||
use validator::Validate;
|
||||
|
||||
@ -114,15 +113,9 @@ pub(crate) async fn get_related_question_handler(
|
||||
) -> DataResult<RepeatedRelatedQuestionPB, FlowyError> {
|
||||
let ai_manager = upgrade_ai_manager(ai_manager)?;
|
||||
let data = data.into_inner();
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
let messages = ai_manager
|
||||
.get_related_questions(&data.chat_id, data.message_id)
|
||||
.await?;
|
||||
let _ = tx.send(messages);
|
||||
Ok::<_, FlowyError>(())
|
||||
});
|
||||
let messages = rx.await?;
|
||||
let messages = ai_manager
|
||||
.get_related_questions(&data.chat_id, data.message_id)
|
||||
.await?;
|
||||
data_result_ok(messages)
|
||||
}
|
||||
|
||||
@ -133,15 +126,9 @@ pub(crate) async fn get_answer_handler(
|
||||
) -> DataResult<ChatMessagePB, FlowyError> {
|
||||
let ai_manager = upgrade_ai_manager(ai_manager)?;
|
||||
let data = data.into_inner();
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
let message = ai_manager
|
||||
.generate_answer(&data.chat_id, data.message_id)
|
||||
.await?;
|
||||
let _ = tx.send(message);
|
||||
Ok::<_, FlowyError>(())
|
||||
});
|
||||
let message = rx.await?;
|
||||
let message = ai_manager
|
||||
.generate_answer(&data.chat_id, data.message_id)
|
||||
.await?;
|
||||
data_result_ok(message)
|
||||
}
|
||||
|
||||
@ -163,25 +150,17 @@ pub(crate) async fn refresh_local_ai_info_handler(
|
||||
ai_manager: AFPluginState<Weak<AIManager>>,
|
||||
) -> DataResult<LLMModelInfoPB, FlowyError> {
|
||||
let ai_manager = upgrade_ai_manager(ai_manager)?;
|
||||
let (tx, rx) = oneshot::channel::<Result<LLMModelInfo, FlowyError>>();
|
||||
tokio::spawn(async move {
|
||||
let model_info = ai_manager.local_ai_controller.refresh().await;
|
||||
if model_info.is_err() {
|
||||
if let Some(llm_model) = ai_manager.local_ai_controller.get_current_model() {
|
||||
let model_info = LLMModelInfo {
|
||||
selected_model: llm_model.clone(),
|
||||
models: vec![llm_model],
|
||||
};
|
||||
let _ = tx.send(Ok(model_info));
|
||||
return;
|
||||
}
|
||||
let model_info = ai_manager.local_ai_controller.refresh().await;
|
||||
if model_info.is_err() {
|
||||
if let Some(llm_model) = ai_manager.local_ai_controller.get_current_model() {
|
||||
let model_info = LLMModelInfo {
|
||||
selected_model: llm_model.clone(),
|
||||
models: vec![llm_model],
|
||||
};
|
||||
return data_result_ok(model_info.into());
|
||||
}
|
||||
|
||||
let _ = tx.send(model_info);
|
||||
});
|
||||
|
||||
let model_info = rx.await??;
|
||||
data_result_ok(model_info.into())
|
||||
}
|
||||
data_result_ok(model_info?.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
@ -268,16 +247,9 @@ pub(crate) async fn chat_file_handler(
|
||||
}
|
||||
|
||||
tracing::debug!("File size: {} bytes", file_size);
|
||||
|
||||
let (tx, rx) = oneshot::channel::<Result<(), FlowyError>>();
|
||||
tokio::spawn(async move {
|
||||
let ai_manager = upgrade_ai_manager(ai_manager)?;
|
||||
ai_manager.chat_with_file(&data.chat_id, file_path).await?;
|
||||
let _ = tx.send(Ok(()));
|
||||
Ok::<_, FlowyError>(())
|
||||
});
|
||||
|
||||
rx.await?
|
||||
let ai_manager = upgrade_ai_manager(ai_manager)?;
|
||||
ai_manager.chat_with_file(&data.chat_id, file_path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
@ -420,17 +392,10 @@ pub(crate) async fn get_offline_app_handler(
|
||||
ai_manager: AFPluginState<Weak<AIManager>>,
|
||||
) -> DataResult<OfflineAIPB, FlowyError> {
|
||||
let ai_manager = upgrade_ai_manager(ai_manager)?;
|
||||
let (tx, rx) = oneshot::channel::<Result<String, FlowyError>>();
|
||||
tokio::spawn(async move {
|
||||
let link = ai_manager
|
||||
.local_ai_controller
|
||||
.get_offline_ai_app_download_link()
|
||||
.await?;
|
||||
let _ = tx.send(Ok(link));
|
||||
Ok::<_, FlowyError>(())
|
||||
});
|
||||
|
||||
let link = rx.await??;
|
||||
let link = ai_manager
|
||||
.local_ai_controller
|
||||
.get_offline_ai_app_download_link()
|
||||
.await?;
|
||||
data_result_ok(OfflineAIPB { link })
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,6 @@ use flowy_ai_pub::cloud::{
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use futures::{stream, Sink, StreamExt, TryStreamExt};
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::local_ai::stream_util::QuestionStream;
|
||||
use crate::stream_message::StreamMessage;
|
||||
@ -108,13 +107,16 @@ impl AICloudServiceMiddleware {
|
||||
|
||||
#[async_trait]
|
||||
impl ChatCloudService for AICloudServiceMiddleware {
|
||||
fn create_chat(
|
||||
async fn create_chat(
|
||||
&self,
|
||||
uid: &i64,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
self.cloud_service.create_chat(uid, workspace_id, chat_id)
|
||||
) -> Result<(), FlowyError> {
|
||||
self
|
||||
.cloud_service
|
||||
.create_chat(uid, workspace_id, chat_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn create_question(
|
||||
|
@ -26,7 +26,7 @@ use flowy_sqlite::kv::KVStorePreferences;
|
||||
use flowy_user::services::authenticate_user::AuthenticateUser;
|
||||
use flowy_user::services::data_import::{load_collab_by_object_id, load_collab_by_object_ids};
|
||||
use lib_dispatch::prelude::ToBytes;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::{Arc, Weak};
|
||||
@ -35,6 +35,7 @@ use tokio::sync::RwLock;
|
||||
use crate::integrate::server::ServerProvider;
|
||||
|
||||
use collab_plugins::local_storage::kv::KVTransactionDB;
|
||||
use lib_infra::async_trait::async_trait;
|
||||
|
||||
pub struct FolderDepsResolver();
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -113,363 +114,305 @@ impl FolderUser for FolderUserImpl {
|
||||
}
|
||||
|
||||
struct DocumentFolderOperation(Arc<DocumentManager>);
|
||||
#[async_trait]
|
||||
impl FolderOperationHandler for DocumentFolderOperation {
|
||||
fn create_workspace_view(
|
||||
async fn create_workspace_view(
|
||||
&self,
|
||||
uid: i64,
|
||||
workspace_view_builder: Arc<RwLock<NestedViewBuilder>>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
) -> Result<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
FutureResult::new(async move {
|
||||
let mut write_guard = workspace_view_builder.write().await;
|
||||
|
||||
// Create a view named "Getting started" with an icon ⭐️ and the built-in README data.
|
||||
// Don't modify this code unless you know what you are doing.
|
||||
write_guard
|
||||
.with_view_builder(|view_builder| async {
|
||||
let view = view_builder
|
||||
.with_name("Getting started")
|
||||
.with_icon("⭐️")
|
||||
.build();
|
||||
// create a empty document
|
||||
let json_str = include_str!("../../assets/read_me.json");
|
||||
let document_pb = JsonToDocumentParser::json_str_to_document(json_str).unwrap();
|
||||
manager
|
||||
.create_document(uid, &view.parent_view.id, Some(document_pb.into()))
|
||||
.await
|
||||
.unwrap();
|
||||
view
|
||||
})
|
||||
.await;
|
||||
Ok(())
|
||||
})
|
||||
let mut write_guard = workspace_view_builder.write().await;
|
||||
// Create a view named "Getting started" with an icon ⭐️ and the built-in README data.
|
||||
// Don't modify this code unless you know what you are doing.
|
||||
write_guard
|
||||
.with_view_builder(|view_builder| async {
|
||||
let view = view_builder
|
||||
.with_name("Getting started")
|
||||
.with_icon("⭐️")
|
||||
.build();
|
||||
// create a empty document
|
||||
let json_str = include_str!("../../assets/read_me.json");
|
||||
let document_pb = JsonToDocumentParser::json_str_to_document(json_str).unwrap();
|
||||
manager
|
||||
.create_document(uid, &view.parent_view.id, Some(document_pb.into()))
|
||||
.await
|
||||
.unwrap();
|
||||
view
|
||||
})
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.open_document(&view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
async fn open_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
self.0.open_document(view_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Close the document view.
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.close_document(&view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
self.0.close_document(view_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
match manager.delete_document(&view_id).await {
|
||||
Ok(_) => tracing::trace!("Delete document: {}", view_id),
|
||||
Err(e) => tracing::error!("🔴delete document failed: {}", e),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
async fn delete_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
match self.0.delete_document(view_id).await {
|
||||
Ok(_) => tracing::trace!("Delete document: {}", view_id),
|
||||
Err(e) => tracing::error!("🔴delete document failed: {}", e),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn duplicate_view(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
let data: DocumentDataPB = manager.get_document_data(&view_id).await?.into();
|
||||
let data_bytes = data.into_bytes().map_err(|_| FlowyError::invalid_data())?;
|
||||
Ok(data_bytes)
|
||||
})
|
||||
async fn duplicate_view(&self, view_id: &str) -> Result<Bytes, FlowyError> {
|
||||
let data: DocumentDataPB = self.0.get_document_data(view_id).await?.into();
|
||||
let data_bytes = data.into_bytes().map_err(|_| FlowyError::invalid_data())?;
|
||||
Ok(data_bytes)
|
||||
}
|
||||
|
||||
fn create_view_with_view_data(
|
||||
async fn create_view_with_view_data(
|
||||
&self,
|
||||
user_id: i64,
|
||||
params: CreateViewParams,
|
||||
) -> FutureResult<Option<EncodedCollab>, FlowyError> {
|
||||
) -> Result<Option<EncodedCollab>, FlowyError> {
|
||||
debug_assert_eq!(params.layout, ViewLayoutPB::Document);
|
||||
let view_id = params.view_id.to_string();
|
||||
let manager = self.0.clone();
|
||||
FutureResult::new(async move {
|
||||
let data = DocumentDataPB::try_from(Bytes::from(params.initial_data))?;
|
||||
let encoded_collab = manager
|
||||
.create_document(user_id, &view_id, Some(data.into()))
|
||||
.await?;
|
||||
Ok(Some(encoded_collab))
|
||||
})
|
||||
let data = DocumentDataPB::try_from(Bytes::from(params.initial_data))?;
|
||||
let encoded_collab = self
|
||||
.0
|
||||
.create_document(user_id, ¶ms.view_id, Some(data.into()))
|
||||
.await?;
|
||||
Ok(Some(encoded_collab))
|
||||
}
|
||||
|
||||
fn get_encoded_collab_v1_from_disk(
|
||||
async fn get_encoded_collab_v1_from_disk(
|
||||
&self,
|
||||
user: Arc<dyn FolderUser>,
|
||||
view_id: &str,
|
||||
) -> FutureResult<EncodedCollabWrapper, FlowyError> {
|
||||
) -> Result<EncodedCollabWrapper, FlowyError> {
|
||||
// get the collab_object_id for the document.
|
||||
// the collab_object_id for the document is the view_id.
|
||||
let oid = view_id.to_string();
|
||||
|
||||
FutureResult::new(async move {
|
||||
let uid = user
|
||||
.user_id()
|
||||
.map_err(|e| e.with_context("unable to get the uid: {}"))?;
|
||||
let uid = user
|
||||
.user_id()
|
||||
.map_err(|e| e.with_context("unable to get the uid: {}"))?;
|
||||
|
||||
// get the collab db
|
||||
let collab_db = user
|
||||
.collab_db(uid)
|
||||
.map_err(|e| e.with_context("unable to get the collab"))?;
|
||||
let collab_db = collab_db.upgrade().ok_or_else(|| {
|
||||
FlowyError::internal().with_context(
|
||||
"The collab db has been dropped, indicating that the user has switched to a new account",
|
||||
)
|
||||
})?;
|
||||
let collab_read_txn = collab_db.read_txn();
|
||||
// get the collab db
|
||||
let collab_db = user
|
||||
.collab_db(uid)
|
||||
.map_err(|e| e.with_context("unable to get the collab"))?;
|
||||
let collab_db = collab_db.upgrade().ok_or_else(|| {
|
||||
FlowyError::internal().with_context(
|
||||
"The collab db has been dropped, indicating that the user has switched to a new account",
|
||||
)
|
||||
})?;
|
||||
let collab_read_txn = collab_db.read_txn();
|
||||
|
||||
// read the collab from the db
|
||||
let collab = load_collab_by_object_id(uid, &collab_read_txn, &oid).map_err(|e| {
|
||||
FlowyError::internal().with_context(format!("load document collab failed: {}", e))
|
||||
})?;
|
||||
// read the collab from the db
|
||||
let collab = load_collab_by_object_id(uid, &collab_read_txn, view_id).map_err(|e| {
|
||||
FlowyError::internal().with_context(format!("load document collab failed: {}", e))
|
||||
})?;
|
||||
|
||||
let encoded_collab = collab
|
||||
let encoded_collab = collab
|
||||
// encode the collab and check the integrity of the collab
|
||||
.encode_collab_v1(|collab| CollabType::Document.validate_require_data(collab))
|
||||
.map_err(|e| {
|
||||
FlowyError::internal().with_context(format!("encode document collab failed: {}", e))
|
||||
})?;
|
||||
|
||||
Ok(EncodedCollabWrapper::Document(DocumentEncodedCollab {
|
||||
document_encoded_collab: encoded_collab,
|
||||
}))
|
||||
})
|
||||
Ok(EncodedCollabWrapper::Document(DocumentEncodedCollab {
|
||||
document_encoded_collab: encoded_collab,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Create a view with built-in data.
|
||||
fn create_built_in_view(
|
||||
async fn create_built_in_view(
|
||||
&self,
|
||||
user_id: i64,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
layout: ViewLayout,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
) -> Result<(), FlowyError> {
|
||||
debug_assert_eq!(layout, ViewLayout::Document);
|
||||
let view_id = view_id.to_string();
|
||||
let manager = self.0.clone();
|
||||
FutureResult::new(async move {
|
||||
match manager.create_document(user_id, &view_id, None).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
if err.is_already_exists() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
match self.0.create_document(user_id, view_id, None).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
if err.is_already_exists() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_bytes(
|
||||
async fn import_from_bytes(
|
||||
&self,
|
||||
uid: i64,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
_import_type: ImportType,
|
||||
bytes: Vec<u8>,
|
||||
) -> FutureResult<EncodedCollab, FlowyError> {
|
||||
let view_id = view_id.to_string();
|
||||
let manager = self.0.clone();
|
||||
FutureResult::new(async move {
|
||||
let data = DocumentDataPB::try_from(Bytes::from(bytes))?;
|
||||
let encoded_collab = manager
|
||||
.create_document(uid, &view_id, Some(data.into()))
|
||||
.await?;
|
||||
Ok(encoded_collab)
|
||||
})
|
||||
) -> Result<EncodedCollab, FlowyError> {
|
||||
let data = DocumentDataPB::try_from(Bytes::from(bytes))?;
|
||||
let encoded_collab = self
|
||||
.0
|
||||
.create_document(uid, view_id, Some(data.into()))
|
||||
.await?;
|
||||
Ok(encoded_collab)
|
||||
}
|
||||
|
||||
// will implement soon
|
||||
fn import_from_file_path(
|
||||
async fn import_from_file_path(
|
||||
&self,
|
||||
_view_id: &str,
|
||||
_name: &str,
|
||||
_path: String,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move { Ok(()) })
|
||||
) -> Result<(), FlowyError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct DatabaseFolderOperation(Arc<DatabaseManager>);
|
||||
|
||||
#[async_trait]
|
||||
impl FolderOperationHandler for DatabaseFolderOperation {
|
||||
fn open_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
database_manager.open_database_view(view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
async fn open_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
self.0.open_database_view(view_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
database_manager.close_database_view(view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
self.0.close_database_view(view_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
match database_manager.delete_database_view(&view_id).await {
|
||||
Ok(_) => tracing::trace!("Delete database view: {}", view_id),
|
||||
Err(e) => tracing::error!("🔴delete database failed: {}", e),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
async fn delete_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
match self.0.delete_database_view(view_id).await {
|
||||
Ok(_) => tracing::trace!("Delete database view: {}", view_id),
|
||||
Err(e) => tracing::error!("🔴delete database failed: {}", e),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_encoded_collab_v1_from_disk(
|
||||
async fn get_encoded_collab_v1_from_disk(
|
||||
&self,
|
||||
user: Arc<dyn FolderUser>,
|
||||
view_id: &str,
|
||||
) -> FutureResult<EncodedCollabWrapper, FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
) -> Result<EncodedCollabWrapper, FlowyError> {
|
||||
// get the collab_object_id for the database.
|
||||
//
|
||||
// the collab object_id for the database is not the view_id,
|
||||
// we should use the view_id to get the database_id
|
||||
let oid = self.0.get_database_id_with_view_id(view_id).await?;
|
||||
let row_oids = self.0.get_database_row_ids_with_view_id(view_id).await?;
|
||||
let row_oids = row_oids
|
||||
.into_iter()
|
||||
.map(|oid| oid.into_inner())
|
||||
.collect::<Vec<_>>();
|
||||
let database_metas = self.0.get_all_databases_meta().await;
|
||||
|
||||
FutureResult::new(async move {
|
||||
// get the collab_object_id for the database.
|
||||
//
|
||||
// the collab object_id for the database is not the view_id,
|
||||
// we should use the view_id to get the database_id
|
||||
let oid = manager.get_database_id_with_view_id(&view_id).await?;
|
||||
let row_oids = manager.get_database_row_ids_with_view_id(&view_id).await?;
|
||||
let row_oids = row_oids
|
||||
.into_iter()
|
||||
.map(|oid| oid.into_inner())
|
||||
.collect::<Vec<_>>();
|
||||
let database_metas = manager.get_all_databases_meta().await;
|
||||
let uid = user
|
||||
.user_id()
|
||||
.map_err(|e| e.with_context("unable to get the uid: {}"))?;
|
||||
|
||||
let uid = user
|
||||
.user_id()
|
||||
.map_err(|e| e.with_context("unable to get the uid: {}"))?;
|
||||
// get the collab db
|
||||
let collab_db = user
|
||||
.collab_db(uid)
|
||||
.map_err(|e| e.with_context("unable to get the collab"))?;
|
||||
let collab_db = collab_db.upgrade().ok_or_else(|| {
|
||||
FlowyError::internal().with_context(
|
||||
"The collab db has been dropped, indicating that the user has switched to a new account",
|
||||
)
|
||||
})?;
|
||||
|
||||
// get the collab db
|
||||
let collab_db = user
|
||||
.collab_db(uid)
|
||||
.map_err(|e| e.with_context("unable to get the collab"))?;
|
||||
let collab_db = collab_db.upgrade().ok_or_else(|| {
|
||||
FlowyError::internal().with_context(
|
||||
"The collab db has been dropped, indicating that the user has switched to a new account",
|
||||
)
|
||||
})?;
|
||||
let collab_read_txn = collab_db.read_txn();
|
||||
|
||||
let collab_read_txn = collab_db.read_txn();
|
||||
// read the database collab from the db
|
||||
let database_collab = load_collab_by_object_id(uid, &collab_read_txn, &oid).map_err(|e| {
|
||||
FlowyError::internal().with_context(format!("load database collab failed: {}", e))
|
||||
})?;
|
||||
|
||||
// read the database collab from the db
|
||||
let database_collab = load_collab_by_object_id(uid, &collab_read_txn, &oid).map_err(|e| {
|
||||
FlowyError::internal().with_context(format!("load database collab failed: {}", e))
|
||||
})?;
|
||||
|
||||
let database_encoded_collab = database_collab
|
||||
let database_encoded_collab = database_collab
|
||||
// encode the collab and check the integrity of the collab
|
||||
.encode_collab_v1(|collab| CollabType::Database.validate_require_data(collab))
|
||||
.map_err(|e| {
|
||||
FlowyError::internal().with_context(format!("encode database collab failed: {}", e))
|
||||
})?;
|
||||
|
||||
// read the database rows collab from the db
|
||||
let database_row_collabs = load_collab_by_object_ids(uid, &collab_read_txn, &row_oids);
|
||||
let database_row_encoded_collabs = database_row_collabs
|
||||
.into_iter()
|
||||
.map(|(oid, collab)| {
|
||||
// encode the collab and check the integrity of the collab
|
||||
let encoded_collab = collab
|
||||
.encode_collab_v1(|collab| CollabType::DatabaseRow.validate_require_data(collab))
|
||||
.map_err(|e| {
|
||||
FlowyError::internal()
|
||||
.with_context(format!("encode database row collab failed: {}", e))
|
||||
})?;
|
||||
Ok((oid, encoded_collab))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, FlowyError>>()?;
|
||||
// read the database rows collab from the db
|
||||
let database_row_collabs = load_collab_by_object_ids(uid, &collab_read_txn, &row_oids);
|
||||
let database_row_encoded_collabs = database_row_collabs
|
||||
.into_iter()
|
||||
.map(|(oid, collab)| {
|
||||
// encode the collab and check the integrity of the collab
|
||||
let encoded_collab = collab
|
||||
.encode_collab_v1(|collab| CollabType::DatabaseRow.validate_require_data(collab))
|
||||
.map_err(|e| {
|
||||
FlowyError::internal().with_context(format!("encode database row collab failed: {}", e))
|
||||
})?;
|
||||
Ok((oid, encoded_collab))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, FlowyError>>()?;
|
||||
|
||||
// get the relation info from the database meta
|
||||
let database_relations = database_metas
|
||||
.into_iter()
|
||||
.filter_map(|meta| {
|
||||
let linked_views = meta.linked_views.into_iter().next()?;
|
||||
Some((meta.database_id, linked_views))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
// get the relation info from the database meta
|
||||
let database_relations = database_metas
|
||||
.into_iter()
|
||||
.filter_map(|meta| {
|
||||
let linked_views = meta.linked_views.into_iter().next()?;
|
||||
Some((meta.database_id, linked_views))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
Ok(EncodedCollabWrapper::Database(DatabaseEncodedCollab {
|
||||
database_encoded_collab,
|
||||
database_row_encoded_collabs,
|
||||
database_relations,
|
||||
}))
|
||||
})
|
||||
Ok(EncodedCollabWrapper::Database(DatabaseEncodedCollab {
|
||||
database_encoded_collab,
|
||||
database_row_encoded_collabs,
|
||||
database_relations,
|
||||
}))
|
||||
}
|
||||
|
||||
fn duplicate_view(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view_id.to_owned();
|
||||
FutureResult::new(async move {
|
||||
let delta_bytes = database_manager.duplicate_database(&view_id).await?;
|
||||
Ok(Bytes::from(delta_bytes))
|
||||
})
|
||||
async fn duplicate_view(&self, view_id: &str) -> Result<Bytes, FlowyError> {
|
||||
let delta_bytes = self.0.duplicate_database(view_id).await?;
|
||||
Ok(Bytes::from(delta_bytes))
|
||||
}
|
||||
|
||||
/// Create a database view with duplicated data.
|
||||
/// If the ext contains the {"database_id": "xx"}, then it will link
|
||||
/// to the existing database.
|
||||
fn create_view_with_view_data(
|
||||
async fn create_view_with_view_data(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
params: CreateViewParams,
|
||||
) -> FutureResult<Option<EncodedCollab>, FlowyError> {
|
||||
) -> Result<Option<EncodedCollab>, FlowyError> {
|
||||
match CreateDatabaseExtParams::from_map(params.meta.clone()) {
|
||||
None => {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = params.view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
let encoded_collab = database_manager
|
||||
.create_database_with_database_data(&view_id, params.initial_data)
|
||||
.await?;
|
||||
Ok(Some(encoded_collab))
|
||||
})
|
||||
let encoded_collab = self
|
||||
.0
|
||||
.create_database_with_database_data(¶ms.view_id, params.initial_data)
|
||||
.await?;
|
||||
Ok(Some(encoded_collab))
|
||||
},
|
||||
Some(database_params) => {
|
||||
let database_manager = self.0.clone();
|
||||
|
||||
let layout = match params.layout {
|
||||
ViewLayoutPB::Board => DatabaseLayoutPB::Board,
|
||||
ViewLayoutPB::Calendar => DatabaseLayoutPB::Calendar,
|
||||
ViewLayoutPB::Grid => DatabaseLayoutPB::Grid,
|
||||
ViewLayoutPB::Document | ViewLayoutPB::Chat => {
|
||||
return FutureResult::new(async move { Err(FlowyError::not_support()) });
|
||||
return Err(FlowyError::not_support());
|
||||
},
|
||||
};
|
||||
let name = params.name.to_string();
|
||||
let database_view_id = params.view_id.to_string();
|
||||
let database_parent_view_id = params.parent_view_id.to_string();
|
||||
|
||||
FutureResult::new(async move {
|
||||
database_manager
|
||||
.create_linked_view(
|
||||
name,
|
||||
layout.into(),
|
||||
database_params.database_id,
|
||||
database_view_id,
|
||||
database_parent_view_id,
|
||||
)
|
||||
.await?;
|
||||
Ok(None)
|
||||
})
|
||||
self
|
||||
.0
|
||||
.create_linked_view(
|
||||
name,
|
||||
layout.into(),
|
||||
database_params.database_id,
|
||||
database_view_id,
|
||||
database_parent_view_id,
|
||||
)
|
||||
.await?;
|
||||
Ok(None)
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -478,110 +421,90 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
||||
/// If the ext contains the {"database_id": "xx"}, then it will link to
|
||||
/// the existing database. The data of the database will be shared within
|
||||
/// these references views.
|
||||
fn create_built_in_view(
|
||||
async fn create_built_in_view(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
layout: ViewLayout,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
) -> Result<(), FlowyError> {
|
||||
let name = name.to_string();
|
||||
let database_manager = self.0.clone();
|
||||
let data = match layout {
|
||||
ViewLayout::Grid => make_default_grid(view_id, &name),
|
||||
ViewLayout::Board => make_default_board(view_id, &name),
|
||||
ViewLayout::Calendar => make_default_calendar(view_id, &name),
|
||||
ViewLayout::Document => {
|
||||
return FutureResult::new(async move {
|
||||
Err(FlowyError::internal().with_context(format!("Can't handle {:?} layout type", layout)))
|
||||
});
|
||||
},
|
||||
ViewLayout::Chat => {
|
||||
// TODO(nathan): AI
|
||||
todo!("AI")
|
||||
ViewLayout::Document | ViewLayout::Chat => {
|
||||
return Err(
|
||||
FlowyError::internal().with_context(format!("Can't handle {:?} layout type", layout)),
|
||||
);
|
||||
},
|
||||
};
|
||||
FutureResult::new(async move {
|
||||
let result = database_manager.create_database_with_params(data).await;
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
if err.is_already_exists() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
let result = self.0.create_database_with_params(data).await;
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
if err.is_already_exists() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_bytes(
|
||||
async fn import_from_bytes(
|
||||
&self,
|
||||
_uid: i64,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
import_type: ImportType,
|
||||
bytes: Vec<u8>,
|
||||
) -> FutureResult<EncodedCollab, FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
) -> Result<EncodedCollab, FlowyError> {
|
||||
let format = match import_type {
|
||||
ImportType::CSV => CSVFormat::Original,
|
||||
ImportType::HistoryDatabase => CSVFormat::META,
|
||||
ImportType::RawDatabase => CSVFormat::META,
|
||||
_ => CSVFormat::Original,
|
||||
};
|
||||
FutureResult::new(async move {
|
||||
let content = tokio::task::spawn_blocking(move || {
|
||||
String::from_utf8(bytes).map_err(|err| FlowyError::internal().with_context(err))
|
||||
})
|
||||
.await??;
|
||||
let result = database_manager
|
||||
.import_csv(view_id, content, format)
|
||||
.await?;
|
||||
Ok(result.encoded_collab)
|
||||
let content = tokio::task::spawn_blocking(move || {
|
||||
String::from_utf8(bytes).map_err(|err| FlowyError::internal().with_context(err))
|
||||
})
|
||||
.await??;
|
||||
let result = self
|
||||
.0
|
||||
.import_csv(view_id.to_string(), content, format)
|
||||
.await?;
|
||||
Ok(result.encoded_collab)
|
||||
}
|
||||
|
||||
fn import_from_file_path(
|
||||
async fn import_from_file_path(
|
||||
&self,
|
||||
_view_id: &str,
|
||||
_name: &str,
|
||||
path: String,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
FutureResult::new(async move {
|
||||
database_manager
|
||||
.import_csv_from_file(path, CSVFormat::META)
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
) -> Result<(), FlowyError> {
|
||||
self.0.import_csv_from_file(path, CSVFormat::META).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn did_update_view(&self, old: &View, new: &View) -> FutureResult<(), FlowyError> {
|
||||
async fn did_update_view(&self, old: &View, new: &View) -> Result<(), FlowyError> {
|
||||
let database_layout = match new.layout {
|
||||
ViewLayout::Document | ViewLayout::Chat => {
|
||||
return FutureResult::new(async {
|
||||
Err(FlowyError::internal().with_context("Can't handle document layout type"))
|
||||
});
|
||||
return Err(FlowyError::internal().with_context("Can't handle document layout type"));
|
||||
},
|
||||
ViewLayout::Grid => DatabaseLayoutPB::Grid,
|
||||
ViewLayout::Board => DatabaseLayoutPB::Board,
|
||||
ViewLayout::Calendar => DatabaseLayoutPB::Calendar,
|
||||
};
|
||||
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = new.id.clone();
|
||||
if old.layout != new.layout {
|
||||
FutureResult::new(async move {
|
||||
database_manager
|
||||
.update_database_layout(&view_id, database_layout)
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
self
|
||||
.0
|
||||
.update_database_layout(&new.id, database_layout)
|
||||
.await?;
|
||||
Ok(())
|
||||
} else {
|
||||
FutureResult::new(async move { Ok(()) })
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -599,78 +522,61 @@ impl CreateDatabaseExtParams {
|
||||
}
|
||||
|
||||
struct ChatFolderOperation(Arc<AIManager>);
|
||||
|
||||
#[async_trait]
|
||||
impl FolderOperationHandler for ChatFolderOperation {
|
||||
fn open_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.open_chat(&view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
async fn open_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
self.0.open_chat(view_id).await
|
||||
}
|
||||
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.close_chat(&view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
self.0.close_chat(view_id).await
|
||||
}
|
||||
|
||||
fn delete_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.delete_chat(&view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
async fn delete_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
self.0.delete_chat(view_id).await
|
||||
}
|
||||
|
||||
fn duplicate_view(&self, _view_id: &str) -> FutureResult<ViewData, FlowyError> {
|
||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||
async fn duplicate_view(&self, _view_id: &str) -> Result<ViewData, FlowyError> {
|
||||
Err(FlowyError::not_support())
|
||||
}
|
||||
|
||||
fn create_view_with_view_data(
|
||||
async fn create_view_with_view_data(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
_params: CreateViewParams,
|
||||
) -> FutureResult<Option<EncodedCollab>, FlowyError> {
|
||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||
) -> Result<Option<EncodedCollab>, FlowyError> {
|
||||
Err(FlowyError::not_support())
|
||||
}
|
||||
|
||||
fn create_built_in_view(
|
||||
async fn create_built_in_view(
|
||||
&self,
|
||||
user_id: i64,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
_layout: ViewLayout,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.create_chat(&user_id, &view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
) -> Result<(), FlowyError> {
|
||||
self.0.create_chat(&user_id, view_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_from_bytes(
|
||||
async fn import_from_bytes(
|
||||
&self,
|
||||
_uid: i64,
|
||||
_view_id: &str,
|
||||
_name: &str,
|
||||
_import_type: ImportType,
|
||||
_bytes: Vec<u8>,
|
||||
) -> FutureResult<EncodedCollab, FlowyError> {
|
||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||
) -> Result<EncodedCollab, FlowyError> {
|
||||
Err(FlowyError::not_support())
|
||||
}
|
||||
|
||||
fn import_from_file_path(
|
||||
async fn import_from_file_path(
|
||||
&self,
|
||||
_view_id: &str,
|
||||
_name: &str,
|
||||
_path: String,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||
) -> Result<(), FlowyError> {
|
||||
Err(FlowyError::not_support())
|
||||
}
|
||||
}
|
||||
|
@ -595,22 +595,17 @@ impl CollabCloudPluginProvider for ServerProvider {
|
||||
|
||||
#[async_trait]
|
||||
impl ChatCloudService for ServerProvider {
|
||||
fn create_chat(
|
||||
async fn create_chat(
|
||||
&self,
|
||||
uid: &i64,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
) -> Result<(), FlowyError> {
|
||||
let server = self.get_server();
|
||||
let chat_id = chat_id.to_string();
|
||||
let uid = *uid;
|
||||
FutureResult::new(async move {
|
||||
server?
|
||||
.chat_service()
|
||||
.create_chat(&uid, &workspace_id, &chat_id)
|
||||
.await
|
||||
})
|
||||
server?
|
||||
.chat_service()
|
||||
.create_chat(uid, workspace_id, chat_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn create_question(
|
||||
|
@ -15,7 +15,7 @@ use flowy_storage::manager::StorageManager;
|
||||
use flowy_user::event_map::UserStatusCallback;
|
||||
use flowy_user_pub::cloud::{UserCloudConfig, UserCloudServiceProvider};
|
||||
use flowy_user_pub::entities::{Authenticator, UserProfile, UserWorkspace};
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
use lib_infra::async_trait::async_trait;
|
||||
|
||||
use crate::integrate::server::{Server, ServerProvider};
|
||||
|
||||
@ -29,21 +29,16 @@ pub(crate) struct UserStatusCallbackImpl {
|
||||
pub(crate) ai_manager: Arc<AIManager>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserStatusCallback for UserStatusCallbackImpl {
|
||||
fn did_init(
|
||||
async fn did_init(
|
||||
&self,
|
||||
user_id: i64,
|
||||
user_authenticator: &Authenticator,
|
||||
cloud_config: &Option<UserCloudConfig>,
|
||||
user_workspace: &UserWorkspace,
|
||||
_device_id: &str,
|
||||
) -> Fut<FlowyResult<()>> {
|
||||
let user_id = user_id.to_owned();
|
||||
let user_workspace = user_workspace.clone();
|
||||
let folder_manager = self.folder_manager.clone();
|
||||
let database_manager = self.database_manager.clone();
|
||||
let document_manager = self.document_manager.clone();
|
||||
|
||||
) -> FlowyResult<()> {
|
||||
self
|
||||
.server_provider
|
||||
.set_user_authenticator(user_authenticator);
|
||||
@ -59,159 +54,142 @@ impl UserStatusCallback for UserStatusCallbackImpl {
|
||||
}
|
||||
}
|
||||
|
||||
to_fut(async move {
|
||||
folder_manager
|
||||
.initialize(
|
||||
user_id,
|
||||
&user_workspace.id,
|
||||
FolderInitDataSource::LocalDisk {
|
||||
create_if_not_exist: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
database_manager.initialize(user_id).await?;
|
||||
document_manager.initialize(user_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
self
|
||||
.folder_manager
|
||||
.initialize(
|
||||
user_id,
|
||||
&user_workspace.id,
|
||||
FolderInitDataSource::LocalDisk {
|
||||
create_if_not_exist: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
self.database_manager.initialize(user_id).await?;
|
||||
self.document_manager.initialize(user_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn did_sign_in(
|
||||
async fn did_sign_in(
|
||||
&self,
|
||||
user_id: i64,
|
||||
user_workspace: &UserWorkspace,
|
||||
device_id: &str,
|
||||
) -> Fut<FlowyResult<()>> {
|
||||
let device_id = device_id.to_owned();
|
||||
let user_id = user_id.to_owned();
|
||||
let user_workspace = user_workspace.clone();
|
||||
let folder_manager = self.folder_manager.clone();
|
||||
let database_manager = self.database_manager.clone();
|
||||
let document_manager = self.document_manager.clone();
|
||||
) -> FlowyResult<()> {
|
||||
event!(
|
||||
tracing::Level::TRACE,
|
||||
"Notify did sign in: latest_workspace: {:?}, device_id: {}",
|
||||
user_workspace,
|
||||
device_id
|
||||
);
|
||||
|
||||
to_fut(async move {
|
||||
event!(
|
||||
tracing::Level::TRACE,
|
||||
"Notify did sign in: latest_workspace: {:?}, device_id: {}",
|
||||
user_workspace,
|
||||
device_id
|
||||
);
|
||||
|
||||
folder_manager.initialize_with_workspace_id(user_id).await?;
|
||||
database_manager.initialize(user_id).await?;
|
||||
document_manager.initialize(user_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
self
|
||||
.folder_manager
|
||||
.initialize_with_workspace_id(user_id)
|
||||
.await?;
|
||||
self.database_manager.initialize(user_id).await?;
|
||||
self.document_manager.initialize(user_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn did_sign_up(
|
||||
async fn did_sign_up(
|
||||
&self,
|
||||
is_new_user: bool,
|
||||
user_profile: &UserProfile,
|
||||
user_workspace: &UserWorkspace,
|
||||
device_id: &str,
|
||||
) -> Fut<FlowyResult<()>> {
|
||||
let device_id = device_id.to_owned();
|
||||
let user_profile = user_profile.clone();
|
||||
let folder_manager = self.folder_manager.clone();
|
||||
let database_manager = self.database_manager.clone();
|
||||
let user_workspace = user_workspace.clone();
|
||||
let document_manager = self.document_manager.clone();
|
||||
) -> FlowyResult<()> {
|
||||
self
|
||||
.server_provider
|
||||
.set_user_authenticator(&user_profile.authenticator);
|
||||
let server_type = self.server_provider.get_server_type();
|
||||
|
||||
to_fut(async move {
|
||||
event!(
|
||||
tracing::Level::TRACE,
|
||||
"Notify did sign up: is new: {} user_workspace: {:?}, device_id: {}",
|
||||
is_new_user,
|
||||
user_workspace,
|
||||
device_id
|
||||
);
|
||||
event!(
|
||||
tracing::Level::TRACE,
|
||||
"Notify did sign up: is new: {} user_workspace: {:?}, device_id: {}",
|
||||
is_new_user,
|
||||
user_workspace,
|
||||
device_id
|
||||
);
|
||||
|
||||
// In the current implementation, when a user signs up for AppFlowy Cloud, a default workspace
|
||||
// is automatically created for them. However, for users who sign up through Supabase, the creation
|
||||
// of the default workspace relies on the client-side operation. This means that the process
|
||||
// for initializing a default workspace differs depending on the sign-up method used.
|
||||
let data_source = match folder_manager
|
||||
.cloud_service
|
||||
.get_folder_doc_state(
|
||||
&user_workspace.id,
|
||||
user_profile.uid,
|
||||
CollabType::Folder,
|
||||
&user_workspace.id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(doc_state) => match server_type {
|
||||
Server::Local => FolderInitDataSource::LocalDisk {
|
||||
create_if_not_exist: true,
|
||||
},
|
||||
Server::AppFlowyCloud => FolderInitDataSource::Cloud(doc_state),
|
||||
Server::Supabase => {
|
||||
if is_new_user {
|
||||
FolderInitDataSource::LocalDisk {
|
||||
create_if_not_exist: true,
|
||||
}
|
||||
} else {
|
||||
FolderInitDataSource::Cloud(doc_state)
|
||||
// In the current implementation, when a user signs up for AppFlowy Cloud, a default workspace
|
||||
// is automatically created for them. However, for users who sign up through Supabase, the creation
|
||||
// of the default workspace relies on the client-side operation. This means that the process
|
||||
// for initializing a default workspace differs depending on the sign-up method used.
|
||||
let data_source = match self
|
||||
.folder_manager
|
||||
.cloud_service
|
||||
.get_folder_doc_state(
|
||||
&user_workspace.id,
|
||||
user_profile.uid,
|
||||
CollabType::Folder,
|
||||
&user_workspace.id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(doc_state) => match server_type {
|
||||
Server::Local => FolderInitDataSource::LocalDisk {
|
||||
create_if_not_exist: true,
|
||||
},
|
||||
Server::AppFlowyCloud => FolderInitDataSource::Cloud(doc_state),
|
||||
Server::Supabase => {
|
||||
if is_new_user {
|
||||
FolderInitDataSource::LocalDisk {
|
||||
create_if_not_exist: true,
|
||||
}
|
||||
},
|
||||
} else {
|
||||
FolderInitDataSource::Cloud(doc_state)
|
||||
}
|
||||
},
|
||||
Err(err) => match server_type {
|
||||
Server::Local => FolderInitDataSource::LocalDisk {
|
||||
create_if_not_exist: true,
|
||||
},
|
||||
Server::AppFlowyCloud | Server::Supabase => {
|
||||
return Err(FlowyError::from(err));
|
||||
},
|
||||
},
|
||||
Err(err) => match server_type {
|
||||
Server::Local => FolderInitDataSource::LocalDisk {
|
||||
create_if_not_exist: true,
|
||||
},
|
||||
};
|
||||
Server::AppFlowyCloud | Server::Supabase => {
|
||||
return Err(FlowyError::from(err));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
folder_manager
|
||||
.initialize_with_new_user(
|
||||
user_profile.uid,
|
||||
&user_profile.token,
|
||||
is_new_user,
|
||||
data_source,
|
||||
&user_workspace.id,
|
||||
)
|
||||
.await
|
||||
.context("FolderManager error")?;
|
||||
self
|
||||
.folder_manager
|
||||
.initialize_with_new_user(
|
||||
user_profile.uid,
|
||||
&user_profile.token,
|
||||
is_new_user,
|
||||
data_source,
|
||||
&user_workspace.id,
|
||||
)
|
||||
.await
|
||||
.context("FolderManager error")?;
|
||||
|
||||
database_manager
|
||||
.initialize_with_new_user(user_profile.uid)
|
||||
.await
|
||||
.context("DatabaseManager error")?;
|
||||
self
|
||||
.database_manager
|
||||
.initialize_with_new_user(user_profile.uid)
|
||||
.await
|
||||
.context("DatabaseManager error")?;
|
||||
|
||||
document_manager
|
||||
.initialize_with_new_user(user_profile.uid)
|
||||
.await
|
||||
.context("DocumentManager error")?;
|
||||
Ok(())
|
||||
})
|
||||
self
|
||||
.document_manager
|
||||
.initialize_with_new_user(user_profile.uid)
|
||||
.await
|
||||
.context("DocumentManager error")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn did_expired(&self, _token: &str, user_id: i64) -> Fut<FlowyResult<()>> {
|
||||
let folder_manager = self.folder_manager.clone();
|
||||
to_fut(async move {
|
||||
folder_manager.clear(user_id).await;
|
||||
Ok(())
|
||||
})
|
||||
async fn did_expired(&self, _token: &str, user_id: i64) -> FlowyResult<()> {
|
||||
self.folder_manager.clear(user_id).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open_workspace(&self, user_id: i64, _user_workspace: &UserWorkspace) -> Fut<FlowyResult<()>> {
|
||||
let folder_manager = self.folder_manager.clone();
|
||||
let database_manager = self.database_manager.clone();
|
||||
let document_manager = self.document_manager.clone();
|
||||
|
||||
to_fut(async move {
|
||||
folder_manager.initialize_with_workspace_id(user_id).await?;
|
||||
database_manager.initialize(user_id).await?;
|
||||
document_manager.initialize(user_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
async fn open_workspace(&self, user_id: i64, _user_workspace: &UserWorkspace) -> FlowyResult<()> {
|
||||
self
|
||||
.folder_manager
|
||||
.initialize_with_workspace_id(user_id)
|
||||
.await?;
|
||||
self.database_manager.initialize(user_id).await?;
|
||||
self.document_manager.initialize(user_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn did_update_network(&self, reachable: bool) {
|
||||
|
@ -1,16 +1,15 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use collab::entity::EncodedCollab;
|
||||
pub use collab_folder::View;
|
||||
use collab_folder::ViewLayout;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use flowy_error::FlowyError;
|
||||
|
||||
use flowy_folder_pub::folder_builder::NestedViewBuilder;
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
use crate::entities::{CreateViewParams, ViewLayoutPB};
|
||||
@ -42,36 +41,37 @@ pub struct DatabaseEncodedCollab {
|
||||
/// view layout. Each [ViewLayout] will have a handler. So when creating a new
|
||||
/// view, the [ViewLayout] will be used to get the handler.
|
||||
///
|
||||
#[async_trait]
|
||||
pub trait FolderOperationHandler {
|
||||
/// Create the view for the workspace of new user.
|
||||
/// Only called once when the user is created.
|
||||
fn create_workspace_view(
|
||||
async fn create_workspace_view(
|
||||
&self,
|
||||
_uid: i64,
|
||||
_workspace_view_builder: Arc<RwLock<NestedViewBuilder>>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async { Ok(()) })
|
||||
) -> Result<(), FlowyError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open_view(&self, view_id: &str) -> FutureResult<(), FlowyError>;
|
||||
async fn open_view(&self, view_id: &str) -> Result<(), FlowyError>;
|
||||
/// Closes the view and releases the resources that this view has in
|
||||
/// the backend
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>;
|
||||
async fn close_view(&self, view_id: &str) -> Result<(), FlowyError>;
|
||||
|
||||
/// Called when the view is deleted.
|
||||
/// This will called after the view is deleted from the trash.
|
||||
fn delete_view(&self, view_id: &str) -> FutureResult<(), FlowyError>;
|
||||
async fn delete_view(&self, view_id: &str) -> Result<(), FlowyError>;
|
||||
|
||||
/// Returns the [ViewData] that can be used to create the same view.
|
||||
fn duplicate_view(&self, view_id: &str) -> FutureResult<ViewData, FlowyError>;
|
||||
async fn duplicate_view(&self, view_id: &str) -> Result<ViewData, FlowyError>;
|
||||
|
||||
/// get the encoded collab data from the disk.
|
||||
fn get_encoded_collab_v1_from_disk(
|
||||
async fn get_encoded_collab_v1_from_disk(
|
||||
&self,
|
||||
_user: Arc<dyn FolderUser>,
|
||||
_view_id: &str,
|
||||
) -> FutureResult<EncodedCollabWrapper, FlowyError> {
|
||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||
) -> Result<EncodedCollabWrapper, FlowyError> {
|
||||
Err(FlowyError::not_support())
|
||||
}
|
||||
|
||||
/// Create a view with the data.
|
||||
@ -92,46 +92,46 @@ pub trait FolderOperationHandler {
|
||||
///
|
||||
/// The return value is the [Option<EncodedCollab>] that can be used to create the view.
|
||||
/// It can be used in syncing the view data to cloud.
|
||||
fn create_view_with_view_data(
|
||||
async fn create_view_with_view_data(
|
||||
&self,
|
||||
user_id: i64,
|
||||
params: CreateViewParams,
|
||||
) -> FutureResult<Option<EncodedCollab>, FlowyError>;
|
||||
) -> Result<Option<EncodedCollab>, FlowyError>;
|
||||
|
||||
/// Create a view with the pre-defined data.
|
||||
/// For example, the initial data of the grid/calendar/kanban board when
|
||||
/// you create a new view.
|
||||
fn create_built_in_view(
|
||||
async fn create_built_in_view(
|
||||
&self,
|
||||
user_id: i64,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
layout: ViewLayout,
|
||||
) -> FutureResult<(), FlowyError>;
|
||||
) -> Result<(), FlowyError>;
|
||||
|
||||
/// Create a view by importing data
|
||||
///
|
||||
/// The return value
|
||||
fn import_from_bytes(
|
||||
async fn import_from_bytes(
|
||||
&self,
|
||||
uid: i64,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
import_type: ImportType,
|
||||
bytes: Vec<u8>,
|
||||
) -> FutureResult<EncodedCollab, FlowyError>;
|
||||
) -> Result<EncodedCollab, FlowyError>;
|
||||
|
||||
/// Create a view by importing data from a file
|
||||
fn import_from_file_path(
|
||||
async fn import_from_file_path(
|
||||
&self,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
path: String,
|
||||
) -> FutureResult<(), FlowyError>;
|
||||
) -> Result<(), FlowyError>;
|
||||
|
||||
/// Called when the view is updated. The handler is the `old` registered handler.
|
||||
fn did_update_view(&self, _old: &View, _new: &View) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move { Ok(()) })
|
||||
async fn did_update_view(&self, _old: &View, _new: &View) -> Result<(), FlowyError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,6 @@ use flowy_ai_pub::cloud::{
|
||||
use flowy_error::FlowyError;
|
||||
use futures_util::{StreamExt, TryStreamExt};
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_infra::util::{get_operating_system, OperatingSystem};
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
@ -28,29 +27,25 @@ impl<T> ChatCloudService for AFCloudChatCloudServiceImpl<T>
|
||||
where
|
||||
T: AFServer,
|
||||
{
|
||||
fn create_chat(
|
||||
async fn create_chat(
|
||||
&self,
|
||||
_uid: &i64,
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
) -> Result<(), FlowyError> {
|
||||
let chat_id = chat_id.to_string();
|
||||
let try_get_client = self.inner.try_get_client();
|
||||
let params = CreateChatParams {
|
||||
chat_id,
|
||||
name: "".to_string(),
|
||||
rag_ids: vec![],
|
||||
};
|
||||
try_get_client?
|
||||
.create_chat(workspace_id, params)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
|
||||
FutureResult::new(async move {
|
||||
let params = CreateChatParams {
|
||||
chat_id,
|
||||
name: "".to_string(),
|
||||
rag_ids: vec![],
|
||||
};
|
||||
try_get_client?
|
||||
.create_chat(&workspace_id, params)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_question(
|
||||
|
@ -5,7 +5,6 @@ use flowy_ai_pub::cloud::{
|
||||
};
|
||||
use flowy_error::FlowyError;
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::FutureResult;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
@ -14,15 +13,13 @@ pub(crate) struct DefaultChatCloudServiceImpl;
|
||||
|
||||
#[async_trait]
|
||||
impl ChatCloudService for DefaultChatCloudServiceImpl {
|
||||
fn create_chat(
|
||||
async fn create_chat(
|
||||
&self,
|
||||
_uid: &i64,
|
||||
_workspace_id: &str,
|
||||
_chat_id: &str,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
})
|
||||
) -> Result<(), FlowyError> {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
}
|
||||
|
||||
async fn create_question(
|
||||
|
@ -7,7 +7,7 @@ use flowy_error::FlowyResult;
|
||||
use flowy_user_pub::cloud::UserCloudConfig;
|
||||
use flowy_user_pub::entities::*;
|
||||
use lib_dispatch::prelude::*;
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
use lib_infra::async_trait::async_trait;
|
||||
|
||||
use crate::event_handler::*;
|
||||
use crate::user_manager::UserManager;
|
||||
@ -276,38 +276,53 @@ pub enum UserEvent {
|
||||
NotifyDidSwitchPlan = 63,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserStatusCallback: Send + Sync + 'static {
|
||||
/// When the [Authenticator] changed, this method will be called. Currently, the auth type
|
||||
/// will be changed when the user sign in or sign up.
|
||||
fn authenticator_did_changed(&self, _authenticator: Authenticator) {}
|
||||
/// This will be called after the application launches if the user is already signed in.
|
||||
/// If the user is not signed in, this method will not be called
|
||||
fn did_init(
|
||||
async fn did_init(
|
||||
&self,
|
||||
user_id: i64,
|
||||
user_authenticator: &Authenticator,
|
||||
cloud_config: &Option<UserCloudConfig>,
|
||||
user_workspace: &UserWorkspace,
|
||||
device_id: &str,
|
||||
) -> Fut<FlowyResult<()>>;
|
||||
_user_id: i64,
|
||||
_user_authenticator: &Authenticator,
|
||||
_cloud_config: &Option<UserCloudConfig>,
|
||||
_user_workspace: &UserWorkspace,
|
||||
_device_id: &str,
|
||||
) -> FlowyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
/// Will be called after the user signed in.
|
||||
fn did_sign_in(
|
||||
async fn did_sign_in(
|
||||
&self,
|
||||
user_id: i64,
|
||||
user_workspace: &UserWorkspace,
|
||||
device_id: &str,
|
||||
) -> Fut<FlowyResult<()>>;
|
||||
_user_id: i64,
|
||||
_user_workspace: &UserWorkspace,
|
||||
_device_id: &str,
|
||||
) -> FlowyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
/// Will be called after the user signed up.
|
||||
fn did_sign_up(
|
||||
async fn did_sign_up(
|
||||
&self,
|
||||
is_new_user: bool,
|
||||
user_profile: &UserProfile,
|
||||
user_workspace: &UserWorkspace,
|
||||
device_id: &str,
|
||||
) -> Fut<FlowyResult<()>>;
|
||||
_is_new_user: bool,
|
||||
_user_profile: &UserProfile,
|
||||
_user_workspace: &UserWorkspace,
|
||||
_device_id: &str,
|
||||
) -> FlowyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn did_expired(&self, token: &str, user_id: i64) -> Fut<FlowyResult<()>>;
|
||||
fn open_workspace(&self, user_id: i64, user_workspace: &UserWorkspace) -> Fut<FlowyResult<()>>;
|
||||
async fn did_expired(&self, _token: &str, _user_id: i64) -> FlowyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
async fn open_workspace(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
_user_workspace: &UserWorkspace,
|
||||
) -> FlowyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn did_update_network(&self, _reachable: bool) {}
|
||||
fn did_update_plans(&self, _plans: Vec<SubscriptionPlan>) {}
|
||||
fn did_update_storage_limitation(&self, _can_write: bool) {}
|
||||
@ -315,42 +330,4 @@ pub trait UserStatusCallback: Send + Sync + 'static {
|
||||
|
||||
/// Acts as a placeholder [UserStatusCallback] for the user session, but does not perform any function
|
||||
pub(crate) struct DefaultUserStatusCallback;
|
||||
impl UserStatusCallback for DefaultUserStatusCallback {
|
||||
fn did_init(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
_authenticator: &Authenticator,
|
||||
_cloud_config: &Option<UserCloudConfig>,
|
||||
_user_workspace: &UserWorkspace,
|
||||
_device_id: &str,
|
||||
) -> Fut<FlowyResult<()>> {
|
||||
to_fut(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn did_sign_in(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
_user_workspace: &UserWorkspace,
|
||||
_device_id: &str,
|
||||
) -> Fut<FlowyResult<()>> {
|
||||
to_fut(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn did_sign_up(
|
||||
&self,
|
||||
_is_new_user: bool,
|
||||
_user_profile: &UserProfile,
|
||||
_user_workspace: &UserWorkspace,
|
||||
_device_id: &str,
|
||||
) -> Fut<FlowyResult<()>> {
|
||||
to_fut(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn did_expired(&self, _token: &str, _user_id: i64) -> Fut<FlowyResult<()>> {
|
||||
to_fut(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn open_workspace(&self, _user_id: i64, _user_workspace: &UserWorkspace) -> Fut<FlowyResult<()>> {
|
||||
to_fut(async { Ok(()) })
|
||||
}
|
||||
}
|
||||
impl UserStatusCallback for DefaultUserStatusCallback {}
|
||||
|
@ -33,7 +33,7 @@ where
|
||||
pub fn load_collab_by_object_id<'a, R>(
|
||||
uid: i64,
|
||||
collab_read_txn: &R,
|
||||
object_id: &String,
|
||||
object_id: &str,
|
||||
) -> Result<Collab, PersistenceError>
|
||||
where
|
||||
R: CollabKVAction<'a>,
|
||||
|
@ -581,7 +581,6 @@ impl UserManager {
|
||||
Ok(UseAISettingPB::from(settings))
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self), err)]
|
||||
pub async fn get_workspace_member_info(&self, uid: i64) -> FlowyResult<WorkspaceMember> {
|
||||
let workspace_id = self.get_session()?.user_workspace.id.clone();
|
||||
let db = self.authenticate_user.get_sqlite_connection(uid)?;
|
||||
|
Loading…
Reference in New Issue
Block a user