Skip to content

Commit 9215f51

Browse files
authored
Feat/restore from snapshot (#1699)
* feat: snapshot for folder * feat: snapshot for document Co-authored-by: nathan <[email protected]>
1 parent 6a36bcd commit 9215f51

File tree

30 files changed

+286
-110
lines changed

30 files changed

+286
-110
lines changed

frontend/app_flowy/lib/user/presentation/router.dart

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:app_flowy/user/presentation/sign_up_screen.dart';
55
import 'package:app_flowy/user/presentation/skip_log_in_screen.dart';
66
import 'package:app_flowy/user/presentation/welcome_screen.dart';
77
import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
8+
import 'package:appflowy_backend/dispatch/dispatch.dart';
89
import 'package:flowy_infra/time/duration.dart';
910
import 'package:flowy_infra_ui/widget/route/animation.dart';
1011
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
@@ -44,21 +45,31 @@ class AuthRouter {
4445

4546
class SplashRoute {
4647
Future<void> pushWelcomeScreen(
47-
BuildContext context, UserProfilePB userProfile) async {
48+
BuildContext context,
49+
UserProfilePB userProfile,
50+
) async {
4851
final screen = WelcomeScreen(userProfile: userProfile);
49-
final workspaceId = await Navigator.of(context).push(
52+
await Navigator.of(context).push(
5053
PageRoutes.fade(
5154
() => screen,
5255
RouteDurations.slow.inMilliseconds * .001,
5356
),
5457
);
5558

56-
// ignore: use_build_context_synchronously
57-
pushHomeScreen(context, userProfile, workspaceId);
59+
FolderEventReadCurrentWorkspace().send().then((result) {
60+
result.fold(
61+
(workspaceSettingPB) =>
62+
pushHomeScreen(context, userProfile, workspaceSettingPB),
63+
(r) => null,
64+
);
65+
});
5866
}
5967

60-
void pushHomeScreen(BuildContext context, UserProfilePB userProfile,
61-
WorkspaceSettingPB workspaceSetting) {
68+
void pushHomeScreen(
69+
BuildContext context,
70+
UserProfilePB userProfile,
71+
WorkspaceSettingPB workspaceSetting,
72+
) {
6273
Navigator.push(
6374
context,
6475
PageRoutes.fade(

frontend/rust-lib/flowy-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ fn crate_log_filter(level: String) -> String {
9191
filters.push(format!("lib_infra={}", level));
9292
filters.push(format!("flowy_sync={}", level));
9393
filters.push(format!("flowy_revision={}", level));
94+
filters.push(format!("flowy_revision_persistence={}", level));
9495
filters.push(format!("flowy_task={}", level));
9596
// filters.push(format!("lib_dispatch={}", level));
9697

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- This file should undo anything in `up.sql`
2+
DROP TABLE document_rev_snapshot;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- Your SQL goes here
2+
CREATE TABLE document_rev_snapshot (
3+
snapshot_id TEXT NOT NULL PRIMARY KEY DEFAULT '',
4+
object_id TEXT NOT NULL DEFAULT '',
5+
rev_id BIGINT NOT NULL DEFAULT 0,
6+
base_rev_id BIGINT NOT NULL DEFAULT 0,
7+
timestamp BIGINT NOT NULL DEFAULT 0,
8+
data BLOB NOT NULL DEFAULT (x'')
9+
);

frontend/rust-lib/flowy-database/src/schema.rs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
table! {
1+
// @generated automatically by Diesel CLI.
2+
3+
diesel::table! {
24
app_table (id) {
35
id -> Text,
46
workspace_id -> Text,
@@ -13,7 +15,18 @@ table! {
1315
}
1416
}
1517

16-
table! {
18+
diesel::table! {
19+
document_rev_snapshot (snapshot_id) {
20+
snapshot_id -> Text,
21+
object_id -> Text,
22+
rev_id -> BigInt,
23+
base_rev_id -> BigInt,
24+
timestamp -> BigInt,
25+
data -> Binary,
26+
}
27+
}
28+
29+
diesel::table! {
1730
document_rev_table (id) {
1831
id -> Integer,
1932
document_id -> Text,
@@ -24,7 +37,7 @@ table! {
2437
}
2538
}
2639

27-
table! {
40+
diesel::table! {
2841
folder_rev_snapshot (snapshot_id) {
2942
snapshot_id -> Text,
3043
object_id -> Text,
@@ -35,14 +48,14 @@ table! {
3548
}
3649
}
3750

38-
table! {
51+
diesel::table! {
3952
grid_block_index_table (row_id) {
4053
row_id -> Text,
4154
block_id -> Text,
4255
}
4356
}
4457

45-
table! {
58+
diesel::table! {
4659
grid_meta_rev_table (id) {
4760
id -> Integer,
4861
object_id -> Text,
@@ -53,7 +66,7 @@ table! {
5366
}
5467
}
5568

56-
table! {
69+
diesel::table! {
5770
grid_rev_snapshot (snapshot_id) {
5871
snapshot_id -> Text,
5972
object_id -> Text,
@@ -64,7 +77,7 @@ table! {
6477
}
6578
}
6679

67-
table! {
80+
diesel::table! {
6881
grid_rev_table (id) {
6982
id -> Integer,
7083
object_id -> Text,
@@ -75,7 +88,7 @@ table! {
7588
}
7689
}
7790

78-
table! {
91+
diesel::table! {
7992
grid_view_rev_table (id) {
8093
id -> Integer,
8194
object_id -> Text,
@@ -86,14 +99,14 @@ table! {
8699
}
87100
}
88101

89-
table! {
102+
diesel::table! {
90103
kv_table (key) {
91104
key -> Text,
92105
value -> Binary,
93106
}
94107
}
95108

96-
table! {
109+
diesel::table! {
97110
rev_snapshot (id) {
98111
id -> Integer,
99112
object_id -> Text,
@@ -102,7 +115,7 @@ table! {
102115
}
103116
}
104117

105-
table! {
118+
diesel::table! {
106119
rev_table (id) {
107120
id -> Integer,
108121
doc_id -> Text,
@@ -114,7 +127,7 @@ table! {
114127
}
115128
}
116129

117-
table! {
130+
diesel::table! {
118131
trash_table (id) {
119132
id -> Text,
120133
name -> Text,
@@ -125,7 +138,7 @@ table! {
125138
}
126139
}
127140

128-
table! {
141+
diesel::table! {
129142
user_table (id) {
130143
id -> Text,
131144
name -> Text,
@@ -136,7 +149,7 @@ table! {
136149
}
137150
}
138151

139-
table! {
152+
diesel::table! {
140153
view_table (id) {
141154
id -> Text,
142155
belong_to_id -> Text,
@@ -152,7 +165,7 @@ table! {
152165
}
153166
}
154167

155-
table! {
168+
diesel::table! {
156169
workspace_table (id) {
157170
id -> Text,
158171
name -> Text,
@@ -164,8 +177,9 @@ table! {
164177
}
165178
}
166179

167-
allow_tables_to_appear_in_same_query!(
180+
diesel::allow_tables_to_appear_in_same_query!(
168181
app_table,
182+
document_rev_snapshot,
169183
document_rev_table,
170184
folder_rev_snapshot,
171185
grid_block_index_table,

frontend/rust-lib/flowy-document/src/editor/document.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ impl RevisionObjectDeserializer for DocumentRevisionSerde {
8686
let document = Document::new(tree);
8787
Result::<Document, FlowyError>::Ok(document)
8888
}
89+
90+
fn recover_operations_from_revisions(_revisions: Vec<Revision>) -> Option<Self::Output> {
91+
None
92+
}
8993
}
9094

9195
impl RevisionObjectSerializer for DocumentRevisionSerde {

frontend/rust-lib/flowy-document/src/manager.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use crate::editor::{initial_document_content, AppFlowyDocumentEditor, DocumentRevisionMergeable};
22
use crate::entities::{DocumentVersionPB, EditParams};
33
use crate::old_editor::editor::{DeltaDocumentEditor, DeltaDocumentRevisionMergeable};
4-
use crate::services::rev_sqlite::{SQLiteDeltaDocumentRevisionPersistence, SQLiteDocumentRevisionPersistence};
4+
use crate::services::rev_sqlite::{
5+
SQLiteDeltaDocumentRevisionPersistence, SQLiteDocumentRevisionPersistence,
6+
SQLiteDocumentRevisionSnapshotPersistence,
7+
};
58
use crate::services::DocumentPersistence;
69
use crate::{errors::FlowyError, DocumentCloudService};
710
use bytes::Bytes;
@@ -261,15 +264,16 @@ impl DocumentManager {
261264
pool: Arc<ConnectionPool>,
262265
) -> Result<RevisionManager<Arc<ConnectionPool>>, FlowyError> {
263266
let user_id = self.user.user_id()?;
264-
let disk_cache = SQLiteDocumentRevisionPersistence::new(&user_id, pool);
265-
let configuration = RevisionPersistenceConfiguration::new(100, true);
267+
let disk_cache = SQLiteDocumentRevisionPersistence::new(&user_id, pool.clone());
268+
let configuration = RevisionPersistenceConfiguration::new(200, true);
266269
let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache, configuration);
270+
let snapshot_persistence = SQLiteDocumentRevisionSnapshotPersistence::new(doc_id, pool);
267271
Ok(RevisionManager::new(
268272
&user_id,
269273
doc_id,
270274
rev_persistence,
271275
DocumentRevisionMergeable(),
272-
PhantomSnapshotPersistence(),
276+
snapshot_persistence,
273277
))
274278
}
275279

frontend/rust-lib/flowy-document/src/old_editor/editor.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![allow(unused_attributes)]
22
#![allow(unused_attributes)]
3+
34
use crate::old_editor::queue::{EditDocumentQueue, EditorCommand, EditorCommandSender};
45
use crate::{errors::FlowyError, DocumentEditor, DocumentUser};
56
use bytes::Bytes;
@@ -260,6 +261,10 @@ impl RevisionObjectDeserializer for DeltaDocumentRevisionSerde {
260261
base_rev_id,
261262
})
262263
}
264+
265+
fn recover_operations_from_revisions(_revisions: Vec<Revision>) -> Option<Self::Output> {
266+
None
267+
}
263268
}
264269

265270
impl RevisionObjectSerializer for DeltaDocumentRevisionSerde {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use bytes::Bytes;
2+
use flowy_database::{
3+
prelude::*,
4+
schema::{document_rev_snapshot, document_rev_snapshot::dsl},
5+
ConnectionPool,
6+
};
7+
use flowy_error::{internal_error, FlowyResult};
8+
use flowy_revision::{RevisionSnapshot, RevisionSnapshotDiskCache};
9+
use lib_infra::util::timestamp;
10+
use std::sync::Arc;
11+
12+
pub struct SQLiteDocumentRevisionSnapshotPersistence {
13+
object_id: String,
14+
pool: Arc<ConnectionPool>,
15+
}
16+
17+
impl SQLiteDocumentRevisionSnapshotPersistence {
18+
pub fn new(object_id: &str, pool: Arc<ConnectionPool>) -> Self {
19+
Self {
20+
object_id: object_id.to_string(),
21+
pool,
22+
}
23+
}
24+
25+
fn gen_snapshot_id(&self, rev_id: i64) -> String {
26+
format!("{}:{}", self.object_id, rev_id)
27+
}
28+
}
29+
30+
impl RevisionSnapshotDiskCache for SQLiteDocumentRevisionSnapshotPersistence {
31+
fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool {
32+
(current_rev_id - start_rev_id) >= 150
33+
}
34+
35+
fn write_snapshot(&self, rev_id: i64, data: Vec<u8>) -> FlowyResult<()> {
36+
let conn = self.pool.get().map_err(internal_error)?;
37+
let snapshot_id = self.gen_snapshot_id(rev_id);
38+
let timestamp = timestamp();
39+
let record = (
40+
dsl::snapshot_id.eq(&snapshot_id),
41+
dsl::object_id.eq(&self.object_id),
42+
dsl::rev_id.eq(rev_id),
43+
dsl::base_rev_id.eq(rev_id),
44+
dsl::timestamp.eq(timestamp),
45+
dsl::data.eq(data),
46+
);
47+
let _ = insert_or_ignore_into(dsl::document_rev_snapshot)
48+
.values(record)
49+
.execute(&*conn)?;
50+
Ok(())
51+
}
52+
53+
fn read_snapshot(&self, rev_id: i64) -> FlowyResult<Option<RevisionSnapshot>> {
54+
let conn = self.pool.get().map_err(internal_error)?;
55+
let snapshot_id = self.gen_snapshot_id(rev_id);
56+
let record = dsl::document_rev_snapshot
57+
.filter(dsl::snapshot_id.eq(&snapshot_id))
58+
.first::<DocumentSnapshotRecord>(&*conn)?;
59+
60+
Ok(Some(record.into()))
61+
}
62+
63+
fn read_last_snapshot(&self) -> FlowyResult<Option<RevisionSnapshot>> {
64+
let conn = self.pool.get().map_err(internal_error)?;
65+
let latest_record = dsl::document_rev_snapshot
66+
.filter(dsl::object_id.eq(&self.object_id))
67+
.order(dsl::timestamp.desc())
68+
// .select(max(dsl::rev_id))
69+
// .select((dsl::id, dsl::object_id, dsl::rev_id, dsl::data))
70+
.first::<DocumentSnapshotRecord>(&*conn)?;
71+
Ok(Some(latest_record.into()))
72+
}
73+
}
74+
75+
#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
76+
#[table_name = "document_rev_snapshot"]
77+
#[primary_key("snapshot_id")]
78+
struct DocumentSnapshotRecord {
79+
snapshot_id: String,
80+
object_id: String,
81+
rev_id: i64,
82+
base_rev_id: i64,
83+
timestamp: i64,
84+
data: Vec<u8>,
85+
}
86+
87+
impl std::convert::From<DocumentSnapshotRecord> for RevisionSnapshot {
88+
fn from(record: DocumentSnapshotRecord) -> Self {
89+
RevisionSnapshot {
90+
rev_id: record.rev_id,
91+
base_rev_id: record.base_rev_id,
92+
timestamp: record.timestamp,
93+
data: Bytes::from(record.data),
94+
}
95+
}
96+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
mod document_rev_sqlite_v0;
22
mod document_rev_sqlite_v1;
3+
mod document_snapshot;
34

45
pub use document_rev_sqlite_v0::*;
56
pub use document_rev_sqlite_v1::*;
7+
pub use document_snapshot::*;

0 commit comments

Comments
 (0)