Skip to content

feature: support import user data with multiple workspaces #7822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions frontend/rust-lib/flowy-user-pub/src/sql/user_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,24 @@ fn select_user_table_row(uid: i64, conn: &mut SqliteConnection) -> Result<UserTa
Ok(row)
}

pub fn select_user_id(conn: &mut SqliteConnection) -> Result<i64, FlowyError> {
let row = user_table::dsl::user_table
.select(user_table::id)
.first::<String>(conn)?;
let uid = row
.parse::<i64>()
.map_err(|err| FlowyError::internal().with_context(err))?;
Ok(uid)
}

pub fn select_user_name(uid: i64, conn: &mut SqliteConnection) -> Result<String, FlowyError> {
let name = user_table::dsl::user_table
.select(user_table::name)
.filter(user_table::id.eq(&uid.to_string()))
.first(conn)?;
Ok(name)
}

pub fn select_user_profile(
uid: i64,
workspace_id: &str,
Expand Down
44 changes: 44 additions & 0 deletions frontend/rust-lib/flowy-user/src/entities/import_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,47 @@ pub struct ImportAppFlowyDataPB {
#[pb(index = 3, one_of)]
pub parent_view_id: Option<String>,
}

#[derive(ProtoBuf, Validate, Default)]
pub struct UserDataPathPB {
#[pb(index = 1)]
#[validate(custom(function = "required_not_empty_str"))]
pub path: String,
}

#[derive(ProtoBuf, Validate, Default)]
pub struct ImportUserDataPB {
#[pb(index = 1)]
#[validate(custom(function = "required_not_empty_str"))]
pub path: String,

#[pb(index = 2, one_of)]
pub parent_view_id: Option<String>,

#[pb(index = 3)]
pub workspaces: Vec<WorkspaceDataPreviewPB>,
}

#[derive(ProtoBuf, Validate, Default)]
pub struct WorkspaceDataPreviewPB {
#[pb(index = 1)]
pub name: String,

#[pb(index = 2)]
pub created_at: i64,

#[pb(index = 3)]
pub workspace_id: String,

#[pb(index = 4)]
pub workspace_database_id: String,
}

#[derive(ProtoBuf, Validate, Default)]
pub struct UserDataPreviewPB {
#[pb(index = 1)]
pub user_name: String,

#[pb(index = 2)]
pub workspaces: Vec<WorkspaceDataPreviewPB>,
}
20 changes: 19 additions & 1 deletion frontend/rust-lib/flowy-user/src/event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::notification::{send_notification, UserNotification};
use crate::services::cloud_config::{
get_cloud_config, get_or_create_cloud_config, save_cloud_config,
};
use crate::services::data_import::prepare_import;
use crate::services::data_import::{prepare_import, user_data_preview};
use crate::user_manager::UserManager;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_sqlite::kv::KVStorePreferences;
Expand Down Expand Up @@ -292,6 +292,24 @@ pub async fn import_appflowy_data_folder_handler(
Ok(())
}

#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn preview_user_data_folder_handler(
data: AFPluginData<UserDataPathPB>,
) -> DataResult<UserDataPreviewPB, FlowyError> {
let data = data.try_into_inner()?;
let preview = user_data_preview(&data.path)?;
data_result_ok(preview)
}

#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn import_user_data_folder_handler(
data: AFPluginData<ImportUserDataPB>,
manager: AFPluginState<Weak<UserManager>>,
) -> Result<(), FlowyError> {
let data = data.try_into_inner()?;
Ok(())
}

#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn get_user_setting(
manager: AFPluginState<Weak<UserManager>>,
Expand Down
8 changes: 8 additions & 0 deletions frontend/rust-lib/flowy-user/src/event_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ pub fn init(user_manager: Weak<UserManager>) -> AFPlugin {
.event(UserEvent::GetWorkspaceSetting, get_workspace_setting_handler)
.event(UserEvent::NotifyDidSwitchPlan, notify_did_switch_plan_handler)
.event(UserEvent::PasscodeSignIn, sign_in_with_passcode_handler)
.event(UserEvent::PreviewAppFlowyUserData, preview_user_data_folder_handler)
.event(UserEvent::ImportAppFlowyUserData, import_user_data_folder_handler)
}

#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
Expand Down Expand Up @@ -273,6 +275,12 @@ pub enum UserEvent {

#[event(input = "PasscodeSignInPB", output = "GotrueTokenResponsePB")]
PasscodeSignIn = 65,

#[event(input = "UserDataPathPB")]
PreviewAppFlowyUserData = 66,

#[event(input = "ImportUserDataPB")]
ImportAppFlowyUserData = 67,
}

#[async_trait]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ impl UserDataMigration for AnonUserWorkspaceTableMigration {
#[instrument(name = "AnonUserWorkspaceTableMigration", skip_all, err)]
fn run(
&self,
user: &Session,
uid: i64,
workspace_id: &str,
_collab_db: &Weak<CollabKVDB>,
user_auth_type: &AuthType,
db: &mut SqliteConnection,
Expand All @@ -46,7 +47,7 @@ impl UserDataMigration for AnonUserWorkspaceTableMigration {
if let Some(mut user_workspace) = get_session_workspace(store_preferences) {
if select_user_workspace(&user_workspace.id, db).ok().is_none() {
user_workspace.workspace_type = AuthType::Local;
upsert_user_workspace(user.user_id, *user_auth_type, user_workspace, db)?;
upsert_user_workspace(uid, *user_auth_type, user_workspace, db)?;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ impl UserDataMigration for CollabDocKeyWithWorkspaceIdMigration {
#[instrument(name = "CollabDocKeyWithWorkspaceIdMigration", skip_all, err)]
fn run(
&self,
user: &Session,
uid: i64,
workspace_id: &str,
collab_db: &Weak<CollabKVDB>,
_user_auth_type: &AuthType,
_db: &mut SqliteConnection,
Expand All @@ -48,9 +49,9 @@ impl UserDataMigration for CollabDocKeyWithWorkspaceIdMigration {
let collab_db = collab_db
.upgrade()
.ok_or_else(|| FlowyError::internal().with_context("Failed to upgrade DB object"))?;
trace!("migrate key with workspace id:{}", user.workspace_id);
trace!("migrate key with workspace id:{}", workspace_id);
collab_db.with_write_txn(|txn| {
migrate_old_keys(txn, &user.workspace_id)?;
migrate_old_keys(txn, &workspace_id)?;
Ok(())
})?;
Ok(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ impl UserDataMigration for HistoricalEmptyDocumentMigration {
#[instrument(name = "HistoricalEmptyDocumentMigration", skip_all, err)]
fn run(
&self,
user: &Session,
uid: i64,
workspace_id: &str,
collab_db: &Weak<CollabKVDB>,
user_auth_type: &AuthType,
_db: &mut SqliteConnection,
Expand All @@ -57,27 +58,20 @@ impl UserDataMigration for HistoricalEmptyDocumentMigration {
.upgrade()
.ok_or_else(|| FlowyError::internal().with_context("Failed to upgrade DB object"))?;
collab_db.with_write_txn(|write_txn| {
let origin = CollabOrigin::Client(CollabClient::new(user.user_id, "phantom"));
let folder_collab = match load_collab(
user.user_id,
write_txn,
&user.workspace_id,
&user.workspace_id,
) {
let origin = CollabOrigin::Client(CollabClient::new(uid, "phantom"));
let folder_collab = match load_collab(uid, write_txn, workspace_id, workspace_id) {
Ok(fc) => fc,
Err(_) => return Ok(()),
};

let folder = Folder::open(user.user_id, folder_collab, None)
let folder = Folder::open(uid, folder_collab, None)
.map_err(|err| PersistenceError::Internal(err.into()))?;
if let Some(workspace_id) = folder.get_workspace_id() {
let migration_views = folder.get_views_belong_to(&workspace_id);
// For historical reasons, the first level documents are empty. So migrate them by inserting
// the default document data.
for view in migration_views {
if migrate_empty_document(write_txn, &origin, &view, user.user_id, &user.workspace_id)
.is_err()
{
if migrate_empty_document(write_txn, &origin, &view, uid, &workspace_id).is_err() {
event!(
tracing::Level::ERROR,
"Failed to migrate document {}",
Expand Down
15 changes: 10 additions & 5 deletions frontend/rust-lib/flowy-user/src/migrations/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,24 @@ use tracing::info;
pub const FIRST_TIME_INSTALL_VERSION: &str = "first_install_version";

pub struct UserLocalDataMigration {
session: Session,
uid: i64,
workspace_id: String,
collab_db: Weak<CollabKVDB>,
sqlite_pool: Arc<ConnectionPool>,
kv: Arc<KVStorePreferences>,
}

impl UserLocalDataMigration {
pub fn new(
session: Session,
uid: i64,
workspace_id: String,
collab_db: Weak<CollabKVDB>,
sqlite_pool: Arc<ConnectionPool>,
kv: Arc<KVStorePreferences>,
) -> Self {
Self {
session,
uid,
workspace_id,
collab_db,
sqlite_pool,
kv,
Expand Down Expand Up @@ -76,7 +79,8 @@ impl UserLocalDataMigration {
let migration_name = migration.name().to_string();
if !duplicated_names.contains(&migration_name) {
migration.run(
&self.session,
self.uid,
&self.workspace_id,
&self.collab_db,
user_auth_type,
&mut conn,
Expand All @@ -102,7 +106,8 @@ pub trait UserDataMigration {
fn run_when(&self, first_installed_version: &Option<Version>, current_version: &Version) -> bool;
fn run(
&self,
user: &Session,
uid: i64,
workspace_id: &str,
collab_db: &Weak<CollabKVDB>,
user_auth_type: &AuthType,
db: &mut SqliteConnection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ impl UserDataMigration for FavoriteV1AndWorkspaceArrayMigration {
#[instrument(name = "FavoriteV1AndWorkspaceArrayMigration", skip_all, err)]
fn run(
&self,
user: &Session,
uid: i64,
workspace_id: &str,
collab_db: &Weak<CollabKVDB>,
_user_auth_type: &AuthType,
_db: &mut SqliteConnection,
Expand All @@ -49,14 +50,9 @@ impl UserDataMigration for FavoriteV1AndWorkspaceArrayMigration {
.upgrade()
.ok_or_else(|| FlowyError::internal().with_context("Failed to upgrade DB object"))?;
collab_db.with_write_txn(|write_txn| {
if let Ok(collab) = load_collab(
user.user_id,
write_txn,
&user.workspace_id,
&user.workspace_id,
) {
let mut folder = Folder::open(user.user_id, collab, None)
.map_err(|err| PersistenceError::Internal(err.into()))?;
if let Ok(collab) = load_collab(uid, write_txn, workspace_id, workspace_id) {
let mut folder =
Folder::open(uid, collab, None).map_err(|err| PersistenceError::Internal(err.into()))?;
folder
.body
.migrate_workspace_to_view(&mut folder.collab.transact_mut());
Expand All @@ -75,9 +71,9 @@ impl UserDataMigration for FavoriteV1AndWorkspaceArrayMigration {
.encode_collab()
.map_err(|err| PersistenceError::Internal(err.into()))?;
write_txn.flush_doc(
user.user_id,
&user.workspace_id,
&user.workspace_id,
uid,
workspace_id,
workspace_id,
encode.state_vector.to_vec(),
encode.doc_state.to_vec(),
)?;
Expand Down
20 changes: 8 additions & 12 deletions frontend/rust-lib/flowy-user/src/migrations/workspace_trash_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ impl UserDataMigration for WorkspaceTrashMapToSectionMigration {
#[instrument(name = "WorkspaceTrashMapToSectionMigration", skip_all, err)]
fn run(
&self,
user: &Session,
uid: i64,
workspace_id: &str,
collab_db: &Weak<CollabKVDB>,
_user_auth_type: &AuthType,
_db: &mut SqliteConnection,
Expand All @@ -47,14 +48,9 @@ impl UserDataMigration for WorkspaceTrashMapToSectionMigration {
.upgrade()
.ok_or_else(|| FlowyError::internal().with_context("Failed to upgrade DB object"))?;
collab_db.with_write_txn(|write_txn| {
if let Ok(collab) = load_collab(
user.user_id,
write_txn,
&user.workspace_id,
&user.workspace_id,
) {
let mut folder = Folder::open(user.user_id, collab, None)
.map_err(|err| PersistenceError::Internal(err.into()))?;
if let Ok(collab) = load_collab(uid, write_txn, workspace_id, workspace_id) {
let mut folder =
Folder::open(uid, collab, None).map_err(|err| PersistenceError::Internal(err.into()))?;
let trash_ids = folder
.get_trash_v1()
.into_iter()
Expand All @@ -69,9 +65,9 @@ impl UserDataMigration for WorkspaceTrashMapToSectionMigration {
.encode_collab()
.map_err(|err| PersistenceError::Internal(err.into()))?;
write_txn.flush_doc(
user.user_id,
&user.workspace_id,
&user.workspace_id,
uid,
workspace_id,
workspace_id,
encode.state_vector.to_vec(),
encode.doc_state.to_vec(),
)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,18 @@ use flowy_user_pub::session::Session;
use rayon::prelude::*;
use std::collections::{HashMap, HashSet};

use crate::entities::{ImportUserDataPB, UserDataPreviewPB, WorkspaceDataPreviewPB};
use collab_document::blocks::TextDelta;
use collab_document::document::Document;
use flowy_user_pub::sql::{select_user_auth_type, select_user_profile, select_user_workspace};
use flowy_sqlite::Database;
use flowy_user_pub::sql::{
select_all_user_workspace, select_user_auth_type, select_user_id, select_user_name,
select_user_profile, select_user_workspace,
};
use semver::Version;
use serde_json::json;
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Weak};
use tracing::{error, event, info, instrument, warn};
use uuid::Uuid;
Expand Down Expand Up @@ -123,7 +128,8 @@ pub(crate) fn prepare_import(
.or_else(|_| select_user_auth_type(imported_session.user_id, &mut conn))?;

run_data_migration(
&imported_session,
imported_session.user_id,
imported_session.workspace_id.clone(),
&imported_user_auth_type,
Arc::downgrade(&imported_collab_db),
imported_sqlite_db.get_pool(),
Expand All @@ -141,18 +147,6 @@ pub(crate) fn prepare_import(
})
}

#[allow(dead_code)]
fn migrate_user_awareness(
old_to_new_id_map: &mut OldToNewIdMap,
old_user_session: &Session,
new_user_session: &Session,
) -> Result<(), PersistenceError> {
let old_uid = old_user_session.user_id;
let new_uid = new_user_session.user_id;
old_to_new_id_map.insert(old_uid.to_string(), new_uid.to_string());
Ok(())
}

/// This path refers to the directory where AppFlowy stores its data. The directory structure is as follows:
/// root folder:
/// - cache.db
Expand Down
Loading
Loading