[flutter]: fix resize window bugs

This commit is contained in:
appflowy
2021-11-11 19:56:30 +08:00
parent 45bb157aa7
commit 5f4ee57c95
15 changed files with 180 additions and 133 deletions

View File

@ -62,7 +62,7 @@ class DocBloc extends Bloc<DocEvent, DocState> {
await _subscription?.cancel(); await _subscription?.cancel();
} }
docManager.closeDoc(); // docManager.closeDoc();
return super.close(); return super.close();
} }
@ -122,6 +122,10 @@ class DocBloc extends Bloc<DocEvent, DocState> {
} }
Document _decodeJsonToDocument(String data) { Document _decodeJsonToDocument(String data) {
// String d = r'''
// [{"insert":"\n👋 Welcome to AppFlowy!\n"},{"insert":"\n","attributes":{"header":1}},{"insert":"Here are the basics\n"},{"insert":"lick anywhere and just start typing\n"},{"insert":"H","attributes":{"list":"unchecked"}},{"insert":"ighlight any text, and use the menu at the bottom to style your writing however you like\n"},{"insert":"C","attributes":{"list":"unchecked"}},{"insert":"lick + New Page button at the bottom of your sidebar to add a new page\n"},{"insert":"C","attributes":{"list":"unchecked"}},{"insert":"lick the + next to any page title in the sidebar to quickly add a new subpage\n"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Have a question? \n"},{"insert":"lick the '?' at the bottom right for help and support.\n\nLike AppFlowy? Follow us:\n"},{"insert":"G","attributes":{"header":2}},{"insert":"ithub: https://github.com/AppFlowy-IO/appflowy\n"},{"insert":"T","attributes":{"blockquote":true}},{"insert":"witter: https://twitter.com/appflowy\n"},{"insert":"N","attributes":{"blockquote":true}},{"insert":"ewsletter: https://www.appflowy.io/blog\n"},{"retain":1,"attributes":{"blockquote":true}},{"insert":"\n"}]
// ''';
final json = jsonDecode(data); final json = jsonDecode(data);
final document = Document.fromJson(json); final document = Document.fromJson(json);
return document; return document;

View File

@ -56,6 +56,10 @@ class HomeStackNotifier extends ChangeNotifier {
HomeStackNotifier({HomeStackContext? context}) : stackContext = context ?? BlankStackContext(); HomeStackNotifier({HomeStackContext? context}) : stackContext = context ?? BlankStackContext();
set context(HomeStackContext context) { set context(HomeStackContext context) {
if (stackContext.identifier == context.identifier) {
return;
}
stackContext.isUpdated.removeListener(notifyListeners); stackContext.isUpdated.removeListener(notifyListeners);
stackContext.dispose(); stackContext.dispose();

View File

@ -1,7 +1,6 @@
import 'package:app_flowy/workspace/application/home/home_bloc.dart'; import 'package:app_flowy/workspace/application/home/home_bloc.dart';
import 'package:app_flowy/workspace/application/home/home_listen_bloc.dart'; import 'package:app_flowy/workspace/application/home/home_listen_bloc.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart'; import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/presentation/stack_page/doc/doc_stack_page.dart';
import 'package:app_flowy/workspace/presentation/stack_page/home_stack.dart'; import 'package:app_flowy/workspace/presentation/stack_page/home_stack.dart';
import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart'; import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
import 'package:app_flowy/workspace/presentation/widgets/prelude.dart'; import 'package:app_flowy/workspace/presentation/widgets/prelude.dart';
@ -18,18 +17,25 @@ import 'package:app_flowy/workspace/domain/view_ext.dart';
import 'home_layout.dart'; import 'home_layout.dart';
class HomeScreen extends StatelessWidget { class HomeScreen extends StatefulWidget {
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey(); static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
final UserProfile user; final UserProfile user;
final CurrentWorkspaceSetting workspaceSetting; final CurrentWorkspaceSetting workspaceSetting;
const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key); const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
View? initialView;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<HomeListenBloc>( BlocProvider<HomeListenBloc>(
create: (context) => getIt<HomeListenBloc>(param1: user)..add(const HomeListenEvent.started()), create: (context) => getIt<HomeListenBloc>(param1: widget.user)..add(const HomeListenEvent.started()),
), ),
BlocProvider<HomeBloc>(create: (context) => getIt<HomeBloc>()), BlocProvider<HomeBloc>(create: (context) => getIt<HomeBloc>()),
], ],
@ -94,22 +100,15 @@ class HomeScreen extends StatelessWidget {
homeBloc.add(HomeEvent.forceCollapse(isCollapsed)); homeBloc.add(HomeEvent.forceCollapse(isCollapsed));
}); });
final pageContext = PublishNotifier<HomeStackContext>(); if (initialView == null && widget.workspaceSetting.hasLatestView()) {
pageContext.addPublishListener((pageContext) { initialView = widget.workspaceSetting.latestView;
getIt<HomeStackManager>().switchStack(pageContext); getIt<HomeStackManager>().switchStack(initialView!.stackContext());
});
HomeStackContext? initialStackContext;
if (workspaceSetting.hasLatestView()) {
initialStackContext = workspaceSetting.latestView.stackContext();
} }
HomeMenu homeMenu = HomeMenu( HomeMenu homeMenu = HomeMenu(
user: user, user: widget.user,
workspaceSetting: workspaceSetting, workspaceSetting: widget.workspaceSetting,
collapsedNotifier: collapasedNotifier, collapsedNotifier: collapasedNotifier,
pageContext: pageContext,
initialStackContext: initialStackContext,
); );
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu)); return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));

View File

@ -15,7 +15,7 @@ import 'widget/toolbar/tool_bar.dart';
class DocPage extends StatefulWidget { class DocPage extends StatefulWidget {
final View view; final View view;
const DocPage({Key? key, required this.view}) : super(key: key); DocPage({Key? key, required this.view}) : super(key: ValueKey(view.id));
@override @override
State<DocPage> createState() => _DocPageState(); State<DocPage> createState() => _DocPageState();

View File

@ -21,10 +21,10 @@ import 'package:clipboard/clipboard.dart';
import 'doc_page.dart'; import 'doc_page.dart';
class DocStackContext extends HomeStackContext<String, ShareActionWrapper> { class DocStackContext extends HomeStackContext<int, ShareActionWrapper> {
View _view; View _view;
late IViewListener _listener; late IViewListener _listener;
final ValueNotifier<String> _isUpdated = ValueNotifier<String>(""); final ValueNotifier<int> _isUpdated = ValueNotifier<int>(0);
DocStackContext({required View view, Key? key}) : _view = view { DocStackContext({required View view, Key? key}) : _view = view {
_listener = getIt<IViewListener>(param1: view); _listener = getIt<IViewListener>(param1: view);
@ -32,7 +32,7 @@ class DocStackContext extends HomeStackContext<String, ShareActionWrapper> {
result.fold( result.fold(
(newView) { (newView) {
_view = newView; _view = newView;
_isUpdated.value = _view.name; _isUpdated.value = _view.hashCode;
}, },
(error) {}, (error) {},
); );
@ -59,7 +59,7 @@ class DocStackContext extends HomeStackContext<String, ShareActionWrapper> {
List<NavigationItem> get navigationItems => _makeNavigationItems(); List<NavigationItem> get navigationItems => _makeNavigationItems();
@override @override
ValueNotifier<String> get isUpdated => _isUpdated; ValueNotifier<int> get isUpdated => _isUpdated;
// List<NavigationItem> get navigationItems => naviStacks.map((stack) { // List<NavigationItem> get navigationItems => naviStacks.map((stack) {
// return NavigationItemImpl(context: stack); // return NavigationItemImpl(context: stack);
@ -111,6 +111,7 @@ class _DocLeftBarItemState extends State<DocLeftBarItem> {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
return IntrinsicWidth( return IntrinsicWidth(
key: ValueKey(_controller.text),
child: TextField( child: TextField(
controller: _controller, controller: _controller,
focusNode: _focusNode, focusNode: _focusNode,

View File

@ -42,25 +42,17 @@ import 'widget/menu_trash.dart';
// └────────┘ // └────────┘
class HomeMenu extends StatelessWidget { class HomeMenu extends StatelessWidget {
final PublishNotifier<HomeStackContext> _pageContext;
final PublishNotifier<bool> _collapsedNotifier; final PublishNotifier<bool> _collapsedNotifier;
final UserProfile user; final UserProfile user;
final CurrentWorkspaceSetting workspaceSetting; final CurrentWorkspaceSetting workspaceSetting;
HomeMenu({ const HomeMenu({
Key? key, Key? key,
required this.user, required this.user,
required this.workspaceSetting, required this.workspaceSetting,
required PublishNotifier<bool> collapsedNotifier, required PublishNotifier<bool> collapsedNotifier,
required PublishNotifier<HomeStackContext> pageContext, }) : _collapsedNotifier = collapsedNotifier,
HomeStackContext? initialStackContext, super(key: key);
}) : _pageContext = pageContext,
_collapsedNotifier = collapsedNotifier,
super(key: key) {
if (initialStackContext != null) {
pageContext.value = initialStackContext;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -78,7 +70,9 @@ class HomeMenu extends StatelessWidget {
listeners: [ listeners: [
BlocListener<MenuBloc, MenuState>( BlocListener<MenuBloc, MenuState>(
listenWhen: (p, c) => p.context != c.context, listenWhen: (p, c) => p.context != c.context,
listener: (context, state) => _pageContext.value = state.context, listener: (context, state) {
getIt<HomeStackManager>().switchStack(state.context);
},
), ),
BlocListener<MenuBloc, MenuState>( BlocListener<MenuBloc, MenuState>(
listenWhen: (p, c) => p.isCollapse != c.isCollapse, listenWhen: (p, c) => p.isCollapse != c.isCollapse,
@ -168,40 +162,40 @@ class HomeMenu extends StatelessWidget {
} }
class MenuSharedState extends ChangeNotifier { class MenuSharedState extends ChangeNotifier {
View? _view; PublishNotifier<View> forcedOpenView = PublishNotifier();
View? _forcedOpenView; ValueNotifier<View?> selectedView = ValueNotifier<View?>(null);
MenuSharedState({View? view}) : _view = view; MenuSharedState({View? view}) {
if (view != null) {
selectedView.value = view;
}
void addForcedOpenViewListener(void Function(View) callback) { forcedOpenView.addPublishListener((view) {
super.addListener(() { selectedView.value = view;
if (_forcedOpenView != null) {
callback(_forcedOpenView!);
}
}); });
} }
void addSelectedViewListener(void Function(View?) callback) { // void addForcedOpenViewListener(void Function(View) callback) {
super.addListener(() { // super.addListener(() {
callback(_view); // if (_forcedOpenView != null) {
}); // callback(_forcedOpenView!);
} // }
// });
// }
set forcedOpenView(View? view) { // void addSelectedViewListener(void Function(View?) callback) {
if (_forcedOpenView != view) { // super.addListener(() {
_forcedOpenView = view; // callback(_view);
selectedView = view; // });
notifyListeners(); // }
}
_forcedOpenView = null;
}
set selectedView(View? view) { // set forcedOpenView(View? view) {
if (_view != view) { // if (_forcedOpenView != view) {
_view = view; // _forcedOpenView = view;
notifyListeners();
} // selectedView = view;
} // notifyListeners();
// }
// }
View? get selectedView => _view;
} }

View File

@ -38,11 +38,11 @@ class _MenuAppState extends State<MenuApp> {
selector: (state) { selector: (state) {
final menuState = Provider.of<MenuSharedState>(context, listen: false); final menuState = Provider.of<MenuSharedState>(context, listen: false);
if (state.latestCreatedView != null) { if (state.latestCreatedView != null) {
menuState.forcedOpenView = state.latestCreatedView; menuState.forcedOpenView.value = state.latestCreatedView!;
} }
notifier.views = state.views; notifier.views = state.views;
notifier.selectedView = menuState.selectedView; notifier.selectedView = menuState.selectedView.value;
return notifier; return notifier;
}, },
builder: (context, notifier) => ChangeNotifierProvider.value( builder: (context, notifier) => ChangeNotifierProvider.value(
@ -88,7 +88,7 @@ class _MenuAppState extends State<MenuApp> {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: notifier)], providers: [ChangeNotifierProvider.value(value: notifier)],
child: Consumer(builder: (context, AppDataNotifier notifier, child) { child: Consumer(builder: (context, AppDataNotifier notifier, child) {
return const ViewSection(); return ViewSection(appData: notifier);
}), }),
); );
} }
@ -112,13 +112,16 @@ class MenuAppSizes {
class AppDataNotifier extends ChangeNotifier { class AppDataNotifier extends ChangeNotifier {
List<View> _views = []; List<View> _views = [];
View? _selectedView;
ExpandableController expandController = ExpandableController(initialExpanded: false); ExpandableController expandController = ExpandableController(initialExpanded: false);
AppDataNotifier(); AppDataNotifier();
set selectedView(View? selectedView) { set selectedView(View? view) {
if (selectedView != null) { _selectedView = view;
final isExpanded = _views.contains(selectedView);
if (view != null) {
final isExpanded = _views.contains(view);
if (expandController.expanded == false && expandController.expanded != isExpanded) { if (expandController.expanded == false && expandController.expanded != isExpanded) {
// Workaround: Delay 150 milliseconds to make the smooth animation while expanding // Workaround: Delay 150 milliseconds to make the smooth animation while expanding
Future.delayed(const Duration(milliseconds: 150), () { Future.delayed(const Duration(milliseconds: 150), () {
@ -128,6 +131,8 @@ class AppDataNotifier extends ChangeNotifier {
} }
} }
View? get selectedView => _selectedView;
set views(List<View>? views) { set views(List<View>? views) {
if (views == null) { if (views == null) {
if (_views.isNotEmpty) { if (_views.isNotEmpty) {

View File

@ -13,20 +13,18 @@ import 'item.dart';
import 'package:async/async.dart'; import 'package:async/async.dart';
class ViewSection extends StatelessWidget { class ViewSection extends StatelessWidget {
const ViewSection({Key? key}) : super(key: key); final AppDataNotifier appData;
const ViewSection({Key? key, required this.appData}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// The ViewSectionNotifier will be updated after AppDataNotifier changed passed by parent widget // The ViewSectionNotifier will be updated after AppDataNotifier changed passed by parent widget
return ChangeNotifierProxyProvider<AppDataNotifier, ViewSectionNotifier>( return ChangeNotifierProxyProvider<AppDataNotifier, ViewSectionNotifier>(
create: (_) { create: (_) {
final views = Provider.of<AppDataNotifier>(context, listen: false).views;
final menuState = Provider.of<MenuSharedState>(context, listen: false);
return ViewSectionNotifier( return ViewSectionNotifier(
context: context, context: context,
views: views, views: appData.views,
initialSelectedView: menuState.selectedView, initialSelectedView: appData.selectedView,
); );
}, },
update: (_, notifier, controller) => controller!..update(notifier), update: (_, notifier, controller) => controller!..update(notifier),
@ -47,7 +45,7 @@ class ViewSection extends StatelessWidget {
isSelected: _isViewSelected(context, view.id), isSelected: _isViewSelected(context, view.id),
onSelected: (view) { onSelected: (view) {
context.read<ViewSectionNotifier>().selectedView = view; context.read<ViewSectionNotifier>().selectedView = view;
Provider.of<MenuSharedState>(context, listen: false).selectedView = view; Provider.of<MenuSharedState>(context, listen: false).selectedView.value = view;
}, },
).padding(vertical: 4), ).padding(vertical: 4),
) )
@ -80,14 +78,14 @@ class ViewSectionNotifier with ChangeNotifier {
_selectedView = initialSelectedView { _selectedView = initialSelectedView {
final menuSharedState = Provider.of<MenuSharedState>(context, listen: false); final menuSharedState = Provider.of<MenuSharedState>(context, listen: false);
// The forcedOpenView will be the view after creating the new view // The forcedOpenView will be the view after creating the new view
menuSharedState.addForcedOpenViewListener((forcedOpenView) { menuSharedState.forcedOpenView.addPublishListener((forcedOpenView) {
selectedView = forcedOpenView; selectedView = forcedOpenView;
}); });
menuSharedState.addSelectedViewListener((currentSelectedView) { menuSharedState.selectedView.addListener(() {
// Cancel the selected view of this section by setting the selectedView to null // Cancel the selected view of this section by setting the selectedView to null
// that will notify the listener to refresh the ViewSection UI // that will notify the listener to refresh the ViewSection UI
if (currentSelectedView != _selectedView) { if (menuSharedState.selectedView.value != _selectedView) {
selectedView = null; selectedView = null;
} }
}); });

View File

@ -18,7 +18,7 @@ class MenuTrash extends StatelessWidget {
height: 26, height: 26,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
Provider.of<MenuSharedState>(context, listen: false).selectedView = null; Provider.of<MenuSharedState>(context, listen: false).selectedView.value = null;
getIt<HomeStackManager>().switchStack(TrashStackContext()); getIt<HomeStackManager>().switchStack(TrashStackContext());
}, },
child: _render(context), child: _render(context),

View File

@ -4,17 +4,21 @@ class PublishNotifier<T> extends ChangeNotifier {
T? _value; T? _value;
set value(T newValue) { set value(T newValue) {
_value = newValue; if (_value != newValue) {
notifyListeners(); _value = newValue;
notifyListeners();
}
} }
T? get currentValue => _value; T? get currentValue => _value;
void addPublishListener(void Function(T) callback) { void addPublishListener(void Function(T) callback) {
super.addListener(() { super.addListener(
if (_value != null) { () {
callback(_value!); if (_value != null) {
} callback(_value!);
}); }
},
);
} }
} }

View File

@ -226,7 +226,8 @@ impl ClientEditDoc {
let _ = rx.await.map_err(internal_error)??; let _ = rx.await.map_err(internal_error)??;
// update rev id // update rev id
self.rev_manager.set_rev_id(server_rev_id.clone().into()); self.rev_manager
.update_rev_id_counter_value(server_rev_id.clone().into());
let (local_base_rev_id, local_rev_id) = self.rev_manager.next_rev_id(); let (local_base_rev_id, local_rev_id) = self.rev_manager.next_rev_id();
// save the revision // save the revision

View File

@ -37,7 +37,7 @@ impl RevisionManager {
pub async fn load_document(&mut self) -> DocResult<Delta> { pub async fn load_document(&mut self) -> DocResult<Delta> {
let doc = self.rev_store.fetch_document().await?; let doc = self.rev_store.fetch_document().await?;
self.set_rev_id(doc.rev_id); self.update_rev_id_counter_value(doc.rev_id);
Ok(doc.delta()?) Ok(doc.delta()?)
} }
@ -59,7 +59,7 @@ impl RevisionManager {
(cur, next) (cur, next)
} }
pub fn set_rev_id(&self, rev_id: i64) { self.rev_id_counter.set(rev_id); } pub fn update_rev_id_counter_value(&self, rev_id: i64) { self.rev_id_counter.set(rev_id); }
pub async fn mk_revisions(&self, range: RevisionRange) -> Result<Revision, DocError> { pub async fn mk_revisions(&self, range: RevisionRange) -> Result<Revision, DocError> {
debug_assert!(&range.doc_id == &self.doc_id); debug_assert!(&range.doc_id == &self.doc_id);

View File

@ -6,9 +6,9 @@ use crate::{
}; };
use async_stream::stream; use async_stream::stream;
use dashmap::DashMap; use dashmap::DashMap;
use flowy_database::ConnectionPool; use flowy_database::{ConnectionPool, SqliteConnection};
use flowy_infra::future::ResultFuture; use flowy_infra::future::ResultFuture;
use flowy_ot::core::{Delta, OperationTransformable}; use flowy_ot::core::{Delta, Operation, OperationTransformable};
use futures::stream::StreamExt; use futures::stream::StreamExt;
use std::{collections::VecDeque, sync::Arc, time::Duration}; use std::{collections::VecDeque, sync::Arc, time::Duration};
use tokio::{ use tokio::{
@ -22,7 +22,7 @@ pub struct RevisionStore {
revs_map: Arc<DashMap<i64, RevisionRecord>>, revs_map: Arc<DashMap<i64, RevisionRecord>>,
pending_tx: PendingSender, pending_tx: PendingSender,
pending_revs: Arc<RwLock<VecDeque<PendingRevId>>>, pending_revs: Arc<RwLock<VecDeque<PendingRevId>>>,
defer_save_oper: RwLock<Option<JoinHandle<()>>>, defer_save: RwLock<Option<JoinHandle<()>>>,
server: Arc<dyn RevisionServer>, server: Arc<dyn RevisionServer>,
} }
@ -45,7 +45,7 @@ impl RevisionStore {
revs_map, revs_map,
pending_revs, pending_revs,
pending_tx, pending_tx,
defer_save_oper: RwLock::new(None), defer_save: RwLock::new(None),
server, server,
}); });
@ -94,7 +94,7 @@ impl RevisionStore {
} }
async fn save_revisions(&self) { async fn save_revisions(&self) {
if let Some(handler) = self.defer_save_oper.write().await.take() { if let Some(handler) = self.defer_save.write().await.take() {
handler.abort(); handler.abort();
} }
@ -105,7 +105,7 @@ impl RevisionStore {
let revs_map = self.revs_map.clone(); let revs_map = self.revs_map.clone();
let persistence = self.persistence.clone(); let persistence = self.persistence.clone();
*self.defer_save_oper.write().await = Some(tokio::spawn(async move { *self.defer_save.write().await = Some(tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(300)).await; tokio::time::sleep(Duration::from_millis(300)).await;
let ids = revs_map.iter().map(|kv| kv.key().clone()).collect::<Vec<i64>>(); let ids = revs_map.iter().map(|kv| kv.key().clone()).collect::<Vec<i64>>();
let revisions_state = revs_map let revisions_state = revs_map
@ -182,7 +182,7 @@ async fn fetch_from_local(doc_id: &str, persistence: Arc<Persistence>) -> DocRes
let doc_id = doc_id.to_owned(); let doc_id = doc_id.to_owned();
spawn_blocking(move || { spawn_blocking(move || {
let conn = &*persistence.pool.get().map_err(internal_error)?; let conn = &*persistence.pool.get().map_err(internal_error)?;
let revisions = persistence.rev_sql.read_rev_tables(&doc_id, None, conn)?; let revisions = persistence.rev_sql.read_rev_tables(&doc_id, conn)?;
if revisions.is_empty() { if revisions.is_empty() {
return Err(DocError::record_not_found().context("Local doesn't have this document")); return Err(DocError::record_not_found().context("Local doesn't have this document"));
} }
@ -190,7 +190,16 @@ async fn fetch_from_local(doc_id: &str, persistence: Arc<Persistence>) -> DocRes
let base_rev_id: RevId = revisions.last().unwrap().base_rev_id.into(); let base_rev_id: RevId = revisions.last().unwrap().base_rev_id.into();
let rev_id: RevId = revisions.last().unwrap().rev_id.into(); let rev_id: RevId = revisions.last().unwrap().rev_id.into();
let mut delta = Delta::new(); let mut delta = Delta::new();
for revision in revisions { let mut pre_rev_id = 0;
for (index, revision) in revisions.into_iter().enumerate() {
if cfg!(debug_assertions) {
if index == 0 {
pre_rev_id = revision.rev_id;
} else {
validate_rev_id(pre_rev_id, revision.rev_id);
}
}
match Delta::from_bytes(revision.delta_data) { match Delta::from_bytes(revision.delta_data) {
Ok(local_delta) => { Ok(local_delta) => {
delta = delta.compose(&local_delta)?; delta = delta.compose(&local_delta)?;
@ -200,13 +209,17 @@ async fn fetch_from_local(doc_id: &str, persistence: Arc<Persistence>) -> DocRes
}, },
} }
} }
if cfg!(debug_assertions) {
validate_delta(&doc_id, persistence, conn, &delta);
}
match delta.ops.last() { match delta.ops.last() {
None => {}, None => {},
Some(op) => { Some(op) => {
let data = op.get_data(); let data = op.get_data();
if !data.ends_with("\n") { if !data.ends_with("\n") {
log::error!("The op must end with newline"); delta.ops.push(Operation::Insert("\n".into()))
log::debug!("Invalid delta: {}", delta.to_json());
} }
}, },
} }
@ -222,6 +235,37 @@ async fn fetch_from_local(doc_id: &str, persistence: Arc<Persistence>) -> DocRes
.map_err(internal_error)? .map_err(internal_error)?
} }
#[cfg(debug_assertions)]
fn validate_rev_id(current: i64, next: i64) {
if current >= next {
log::error!("The next revision id should be greater than the previous");
}
}
#[cfg(debug_assertions)]
fn validate_delta(doc_id: &str, persistence: Arc<Persistence>, conn: &SqliteConnection, delta: &Delta) {
if delta.ops.last().is_none() {
return;
}
let data = delta.ops.last().as_ref().unwrap().get_data();
if !data.ends_with("\n") {
log::error!("The op must end with newline");
let result = || {
let revisions = persistence.rev_sql.read_rev_tables(&doc_id, conn)?;
for revision in revisions {
let delta = Delta::from_bytes(revision.delta_data)?;
log::error!("Invalid revision: {}:{}", revision.rev_id, delta.to_json());
}
Ok::<(), DocError>(())
};
match result() {
Ok(_) => {},
Err(e) => log::error!("{}", e),
}
}
}
// fn update_revisions(&self) { // fn update_revisions(&self) {
// let rev_ids = self // let rev_ids = self
// .revs // .revs

View File

@ -4,12 +4,7 @@ use crate::{
sql_tables::{doc::RevTable, RevChangeset, RevState, RevTableType}, sql_tables::{doc::RevTable, RevChangeset, RevState, RevTableType},
}; };
use diesel::update; use diesel::update;
use flowy_database::{ use flowy_database::{insert_or_ignore_into, prelude::*, schema::rev_table::dsl, SqliteConnection};
insert_or_ignore_into,
prelude::*,
schema::rev_table::{columns::*, dsl, dsl::doc_id},
SqliteConnection,
};
pub struct RevTableSql {} pub struct RevTableSql {}
@ -25,12 +20,12 @@ impl RevTableSql {
.map(|(revision, new_state)| { .map(|(revision, new_state)| {
let rev_ty: RevTableType = revision.ty.into(); let rev_ty: RevTableType = revision.ty.into();
( (
doc_id.eq(revision.doc_id), dsl::doc_id.eq(revision.doc_id),
base_rev_id.eq(revision.base_rev_id), dsl::base_rev_id.eq(revision.base_rev_id),
rev_id.eq(revision.rev_id), dsl::rev_id.eq(revision.rev_id),
data.eq(revision.delta_data), dsl::data.eq(revision.delta_data),
state.eq(new_state), dsl::state.eq(new_state),
ty.eq(rev_ty), dsl::ty.eq(rev_ty),
) )
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -42,24 +37,18 @@ impl RevTableSql {
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn update_rev_table(&self, changeset: RevChangeset, conn: &SqliteConnection) -> Result<(), DocError> { pub(crate) fn update_rev_table(&self, changeset: RevChangeset, conn: &SqliteConnection) -> Result<(), DocError> {
let filter = dsl::rev_table let filter = dsl::rev_table
.filter(rev_id.eq(changeset.rev_id.as_ref())) .filter(dsl::rev_id.eq(changeset.rev_id.as_ref()))
.filter(doc_id.eq(changeset.doc_id)); .filter(dsl::doc_id.eq(changeset.doc_id));
let _ = update(filter).set(state.eq(changeset.state)).execute(conn)?; let _ = update(filter).set(dsl::state.eq(changeset.state)).execute(conn)?;
tracing::debug!("Set {} to {:?}", changeset.rev_id, changeset.state); tracing::debug!("Set {} to {:?}", changeset.rev_id, changeset.state);
Ok(()) Ok(())
} }
pub(crate) fn read_rev_tables( pub(crate) fn read_rev_tables(&self, doc_id: &str, conn: &SqliteConnection) -> Result<Vec<Revision>, DocError> {
&self, let filter = dsl::rev_table
did: &str, .filter(dsl::doc_id.eq(doc_id))
rid: Option<i64>, .order(dsl::rev_id.asc())
conn: &SqliteConnection, .into_boxed();
) -> Result<Vec<Revision>, DocError> {
let mut filter = dsl::rev_table.filter(doc_id.eq(did)).order(rev_id.asc()).into_boxed();
if let Some(rid) = rid {
filter = filter.filter(rev_id.eq(rid))
}
let rev_tables = filter.load::<RevTable>(conn)?; let rev_tables = filter.load::<RevTable>(conn)?;
let revisions = rev_tables let revisions = rev_tables
.into_iter() .into_iter()
@ -70,11 +59,13 @@ impl RevTableSql {
pub(crate) fn read_rev_table( pub(crate) fn read_rev_table(
&self, &self,
did: &str, doc_id: &str,
rid: &i64, revision_id: &i64,
conn: &SqliteConnection, conn: &SqliteConnection,
) -> Result<Option<Revision>, DocError> { ) -> Result<Option<Revision>, DocError> {
let filter = dsl::rev_table.filter(doc_id.eq(did)).filter(rev_id.eq(rid)); let filter = dsl::rev_table
.filter(dsl::doc_id.eq(doc_id))
.filter(dsl::rev_id.eq(revision_id));
let result = filter.first::<RevTable>(conn); let result = filter.first::<RevTable>(conn);
if Err(diesel::NotFound) == result { if Err(diesel::NotFound) == result {
@ -91,10 +82,10 @@ impl RevTableSql {
conn: &SqliteConnection, conn: &SqliteConnection,
) -> Result<Vec<Revision>, DocError> { ) -> Result<Vec<Revision>, DocError> {
let rev_tables = dsl::rev_table let rev_tables = dsl::rev_table
.filter(rev_id.ge(range.start)) .filter(dsl::rev_id.ge(range.start))
.filter(rev_id.le(range.end)) .filter(dsl::rev_id.le(range.end))
.filter(doc_id.eq(doc_id_s)) .filter(dsl::doc_id.eq(doc_id_s))
.order(rev_id.asc()) .order(dsl::rev_id.asc())
.load::<RevTable>(conn)?; .load::<RevTable>(conn)?;
let revisions = rev_tables let revisions = rev_tables
@ -111,7 +102,9 @@ impl RevTableSql {
rev_id_s: i64, rev_id_s: i64,
conn: &SqliteConnection, conn: &SqliteConnection,
) -> Result<(), DocError> { ) -> Result<(), DocError> {
let filter = dsl::rev_table.filter(rev_id.eq(rev_id_s)).filter(doc_id.eq(doc_id_s)); let filter = dsl::rev_table
.filter(dsl::rev_id.eq(rev_id_s))
.filter(dsl::doc_id.eq(doc_id_s));
let affected_row = diesel::delete(filter).execute(conn)?; let affected_row = diesel::delete(filter).execute(conn)?;
debug_assert_eq!(affected_row, 1); debug_assert_eq!(affected_row, 1);
Ok(()) Ok(())

View File

@ -128,7 +128,7 @@ impl ViewController {
pub(crate) async fn delete_view(&self, params: DocIdentifier) -> Result<(), WorkspaceError> { pub(crate) async fn delete_view(&self, params: DocIdentifier) -> Result<(), WorkspaceError> {
if let Some(view_id) = KV::get_str(LATEST_VIEW_ID) { if let Some(view_id) = KV::get_str(LATEST_VIEW_ID) {
if view_id == params.doc_id { if view_id == params.doc_id {
KV::remove(LATEST_VIEW_ID); let _ = KV::remove(LATEST_VIEW_ID);
} }
} }
let _ = self.document.close(params).await?; let _ = self.document.close(params).await?;