1use std::{
2 collections::{HashMap, HashSet},
3 num::NonZeroUsize,
4 sync::Arc,
5 thread,
6 time::{Duration, Instant},
7};
8
9use futures::{StreamExt, stream};
10use tokio::sync::{Mutex, broadcast::error::TryRecvError};
11use tokio::time;
12use usvg::Transform;
13use uuid::Uuid;
14
15use crate::{
16 Lb, LbErrKind, LbResult,
17 io::network::ApiError,
18 model::{
19 ValidationFailure,
20 access_info::UserAccessMode,
21 account::Account,
22 api::{
23 ChangeDocRequestV2, GetDocRequest, GetFileIdsRequest, GetUpdatesRequestV2,
24 GetUsernameError, GetUsernameRequest, UpsertDebugInfoRequest, UpsertRequestV2,
25 },
26 chat,
27 crypto::{AESKey, DecryptedDocument, EncryptedDocument},
28 errors::{LbErr, Unexpected},
29 file::ShareMode,
30 file_like::FileLike,
31 file_metadata::{DocumentHmac, FileDiff, FileType, Owner},
32 filename::{DocumentType, NameComponents},
33 lazy::LazyTree,
34 signed_meta::SignedMeta,
35 staged::StagedTreeLikeMut,
36 svg::{self, buffer::u_transform_to_bezier, element::Element},
37 symkey, text,
38 tree_like::TreeLike,
39 validate,
40 },
41 service::events::{Actor, Event, SyncIncrement},
42};
43
44pub type Syncer = Arc<Mutex<SyncState>>;
45
46#[derive(Default)]
47pub struct SyncState {
48 last_synced: u64,
50
51 updates_as_of: u64,
53
54 remote_changes: Vec<SignedMeta>,
56
57 new_root: Option<Uuid>,
59
60 pulled_docs: Vec<Uuid>,
62}
63
64impl Lb {
71 #[instrument(level = "debug", skip(self), err(Debug))]
72 pub async fn sync(&self) -> LbResult<()> {
73 let mut sync_state = self.syncer.lock().await;
74 self.events.sync_update(SyncIncrement::SyncStarted);
75
76 let pipeline: LbResult<()> = async {
77 self.pull_updates(&mut sync_state).await?;
78 self.push_local_changes().await?;
79 Ok(())
80 }
81 .await;
82
83 self.events.sync_update(SyncIncrement::SyncFinished(
84 pipeline.as_ref().err().map(|err| err.kind.clone()),
85 ));
86
87 self.cleanup().await?;
88
89 pipeline?;
90
91 let account = self.get_account()?.clone();
92
93 #[cfg(not(target_family = "wasm"))]
94 if account.is_beta() {
95 self.send_debug_info(account).await;
96 }
97
98 Ok(())
99 }
100
101 pub(crate) async fn pull_updates(&self, sync_state: &mut SyncState) -> LbResult<()> {
102 self.inital_sync_state(sync_state).await?;
103 self.process_deletions().await?;
104 self.fetch_meta(sync_state).await?;
105 self.fetch_required_docs(sync_state).await?;
106 self.merge(sync_state).await?;
108 self.commit_last_synced(sync_state).await?;
109 self.send_pull_events(sync_state).await?;
110
111 if !self.config.background_work {
112 self.populate_pk_cache().await?;
113 }
114
115 Ok(())
116 }
117
118 pub(crate) async fn push_local_changes(&self) -> LbResult<()> {
119 self.push_meta().await?;
120 self.push_docs().await?;
121
122 Ok(())
123 }
124
125 async fn inital_sync_state(&self, state: &mut SyncState) -> LbResult<()> {
126 let tx = self.ro_tx().await;
127 let db = tx.db();
128
129 *state = Default::default();
130 state.last_synced = db.last_synced.as_ref().copied().unwrap_or_default() as u64;
131
132 Ok(())
133 }
134
135 pub(crate) async fn process_deletions(&self) -> LbResult<()> {
136 let server_ids = self
137 .client
138 .request(self.get_account()?, GetFileIdsRequest {})
139 .await?
140 .ids;
141
142 let mut tx = self.begin_tx().await;
143 let db = tx.db();
144
145 let mut local = db.base_metadata.stage(&db.local_metadata).to_lazy();
146 let base_ids = local.tree.base.ids();
147
148 let mut prunable_ids = base_ids;
149 prunable_ids.retain(|id| !server_ids.contains(id));
150 for id in prunable_ids.clone() {
151 prunable_ids.extend(local.descendants(&id)?);
152 }
153 for id in &prunable_ids {
154 if let Some(base_file) = local.tree.base.maybe_find(id) {
155 self.docs
156 .delete(*id, base_file.document_hmac().copied())
157 .await?;
158 }
159 if let Some(local_file) = local.maybe_find(id) {
160 self.docs
161 .delete(*id, local_file.document_hmac().copied())
162 .await?;
163 }
164 }
165
166 let mut base_staged = (&mut db.base_metadata).to_lazy().stage(None);
167 base_staged.tree.removed = prunable_ids.iter().copied().collect();
168 base_staged.promote()?;
169
170 let mut local_staged = (&mut db.local_metadata).to_lazy().stage(None);
171 local_staged.tree.removed = prunable_ids.iter().copied().collect();
172 local_staged.promote()?;
173
174 if !prunable_ids.is_empty() {
175 self.events.meta_changed(Actor::Sync);
176 }
177
178 Ok(())
179 }
180
181 async fn fetch_meta(&self, state: &mut SyncState) -> LbResult<()> {
182 let updates = self
183 .client
184 .request(
185 self.get_account()?,
186 GetUpdatesRequestV2 { since_metadata_version: state.last_synced },
187 )
188 .await?;
189
190 let tx = self.ro_tx().await;
191 let db = tx.db();
192
193 let mut without_orphans = Vec::new();
195 let me = Owner(self.keychain.get_pk()?);
196 let remote = db.base_metadata.stage(updates.file_metadata).to_lazy();
197 for id in remote.tree.staged.ids() {
198 let meta = remote.find(&id)?;
199 if remote.maybe_find_parent(meta).is_some()
200 || meta
201 .user_access_keys()
202 .iter()
203 .any(|k| k.encrypted_for == me.0)
204 {
205 without_orphans.push(remote.find(&id)?.clone());
206 }
207 }
208
209 let pruned_tree = db.base_metadata.stage(without_orphans).pruned()?.to_lazy();
211 let (_, deduped_changes) = pruned_tree.unstage();
212
213 let mut root_id = None;
215 if db.root.as_ref().is_none() {
216 let root = deduped_changes
217 .all_files()?
218 .into_iter()
219 .find(|f| f.is_root())
220 .ok_or(LbErrKind::RootNonexistent)?;
221 root_id = Some(*root.id());
222 }
223
224 state.remote_changes = deduped_changes;
225 state.updates_as_of = updates.as_of_metadata_version;
226 state.new_root = root_id;
227
228 Ok(())
229 }
230
231 async fn fetch_required_docs(&self, state: &mut SyncState) -> LbResult<()> {
232 let mut docs_to_pull = vec![];
233
234 let tx = self.ro_tx().await;
235 let db = tx.db();
236
237 let mut files_with_local_edits = vec![];
238 let local = db.base_metadata.stage(&db.local_metadata);
239 for id in local.staged.ids() {
240 if let Some(base) = local.base.maybe_find(&id) {
241 if let Some(local_hmac) = local.find(&id)?.document_hmac() {
242 if Some(local_hmac) != base.document_hmac() {
243 files_with_local_edits.push(id);
244 println!("local edits found");
245 }
246 }
247 }
248 }
249
250 let mut remote = db
251 .base_metadata
252 .stage(state.remote_changes.clone())
253 .to_lazy();
254
255 for id in remote.tree.staged.ids() {
256 if remote.calculate_deleted(&id)? {
257 continue;
258 }
259 let remote_hmac = remote.find(&id)?.document_hmac().cloned();
260 let base_hmac = remote
261 .tree
262 .base
263 .maybe_find(&id)
264 .and_then(|f| f.document_hmac())
265 .cloned();
266 if base_hmac == remote_hmac {
267 continue;
268 }
269
270 if let Some(remote_hmac) = remote_hmac {
271 if self.docs.exists(id, base_hmac) && !self.docs.exists(id, Some(remote_hmac)) {
275 docs_to_pull.push((id, remote_hmac));
276 }
277
278 if files_with_local_edits.contains(&id)
281 && !docs_to_pull
282 .iter()
283 .any(|(already_pulling, _)| already_pulling == &id)
284 {
285 if let Some(base_hmac) = base_hmac {
286 if !self.docs.exists(id, Some(base_hmac)) {
287 docs_to_pull.push((id, base_hmac));
290 }
291 }
292 docs_to_pull.push((id, remote_hmac));
293 }
294 }
295 }
296 drop(tx);
297
298 let futures = docs_to_pull
299 .into_iter()
300 .map(|(id, hmac)| async move { self.ensure_doc_available(id, hmac).await.map(|_| id) });
301
302 let mut stream = stream::iter(futures).buffer_unordered(
303 thread::available_parallelism()
304 .unwrap_or(NonZeroUsize::new(4).unwrap())
305 .into(),
306 );
307
308 while let Some(fut) = stream.next().await {
309 let id = fut?;
310 state.pulled_docs.push(id);
311 }
312
313 Ok(())
314 }
315
316 pub(crate) async fn ensure_doc_available(
317 &self, id: Uuid, hmac: DocumentHmac,
318 ) -> LbResult<Option<EncryptedDocument>> {
319 if self.docs.exists(id, Some(hmac)) {
324 return Ok(None);
325 }
326
327 self.events
328 .sync_update(SyncIncrement::PullingDocument(id, true));
329 let remote_document = self
330 .client
331 .request(self.get_account()?, GetDocRequest { id, hmac })
332 .await?;
333 self.docs
334 .insert(id, Some(hmac), &remote_document.content)
335 .await?;
336 self.events
337 .sync_update(SyncIncrement::PullingDocument(id, false));
338
339 Ok(Some(remote_document.content))
340 }
341
342 pub(crate) async fn fetch_doc(
343 &self, id: Uuid, hmac: DocumentHmac,
344 ) -> LbResult<EncryptedDocument> {
345 match self.ensure_doc_available(id, hmac).await? {
346 Some(doc) => Ok(doc),
347 None => self.docs.get(id, Some(hmac)).await,
348 }
349 }
350
351 async fn merge(&self, state: &mut SyncState) -> LbResult<()> {
354 let mut tx = self.begin_tx().await;
355 let db = tx.db();
356 let start = Instant::now();
357
358 let remote_changes = &state.remote_changes;
359
360 let me = Owner(self.keychain.get_pk()?);
362
363 let merge_changes = {
365 let mut base = (&db.base_metadata).to_lazy();
367 let remote_unlazy = (&db.base_metadata).to_staged(remote_changes);
368 let mut remote = remote_unlazy.as_lazy();
369 let mut local = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
370
371 let mut files_to_unmove: HashSet<Uuid> = HashSet::new();
373 let mut files_to_unshare: HashSet<Uuid> = HashSet::new();
374 let mut links_to_delete: HashSet<Uuid> = HashSet::new();
375 let mut rename_increments: HashMap<Uuid, usize> = HashMap::new();
376 let mut duplicate_file_ids: HashMap<Uuid, (Uuid, AESKey)> = HashMap::new();
379
380 'merge_construction: loop {
381 let mut deletions = {
383 let mut deletions = remote_unlazy.stage(Vec::new()).to_lazy();
384
385 let mut deletion_creations = HashSet::new();
387 for id in db.local_metadata.ids() {
388 if remote.maybe_find(&id).is_none() && !links_to_delete.contains(&id) {
389 deletion_creations.insert(id);
390 }
391 }
392 'drain_creations: while !deletion_creations.is_empty() {
393 'choose_a_creation: for id in &deletion_creations {
394 let id = *id;
396 let local_file = local.find(&id)?.clone();
397 let result = deletions.create_unvalidated(
398 id,
399 symkey::generate_key(),
400 local_file.parent(),
401 &local.name(&id, &self.keychain)?,
402 local_file.file_type(),
403 &self.keychain,
404 );
405 match result {
406 Ok(_) => {
407 deletion_creations.remove(&id);
408 continue 'drain_creations;
409 }
410 Err(ref err) => match err.kind {
411 LbErrKind::FileParentNonexistent => {
412 continue 'choose_a_creation;
413 }
414 _ => {
415 result?;
416 }
417 },
418 }
419 }
420 return Err(LbErrKind::Unexpected(format!(
421 "sync failed to find a topomodelal order for file creations: {deletion_creations:?}"
422 ))
423 .into());
424 }
425
426 for id in db.local_metadata.ids() {
428 let local_file = local.find(&id)?.clone();
429 if let Some(base_file) = db.base_metadata.maybe_find(&id).cloned() {
430 if !local_file.explicitly_deleted()
431 && local_file.parent() != base_file.parent()
432 && !files_to_unmove.contains(&id)
433 {
434 deletions.move_unvalidated(
436 &id,
437 local_file.parent(),
438 &self.keychain,
439 )?;
440 }
441 }
442 }
443
444 for id in db.local_metadata.ids() {
446 let local_file = local.find(&id)?.clone();
447 if local_file.explicitly_deleted() {
448 deletions.delete_unvalidated(&id, &self.keychain)?;
450 }
451 }
452 deletions
453 };
454
455 let mut merge = {
457 let mut merge = remote_unlazy.stage(Vec::new()).to_lazy();
458
459 let mut creations = HashSet::new();
461 for id in db.local_metadata.ids() {
462 if deletions.maybe_find(&id).is_some()
463 && !deletions.calculate_deleted(&id)?
464 && remote.maybe_find(&id).is_none()
465 && !links_to_delete.contains(&id)
466 {
467 creations.insert(id);
468 }
469 }
470 'drain_creations: while !creations.is_empty() {
471 'choose_a_creation: for id in &creations {
472 let id = *id;
474 let local_file = local.find(&id)?.clone();
475 let result = merge.create_unvalidated(
476 id,
477 local.decrypt_key(&id, &self.keychain)?,
478 local_file.parent(),
479 &local.name(&id, &self.keychain)?,
480 local_file.file_type(),
481 &self.keychain,
482 );
483 match result {
484 Ok(_) => {
485 creations.remove(&id);
486 continue 'drain_creations;
487 }
488 Err(ref err) => match err.kind {
489 LbErrKind::FileParentNonexistent => {
490 continue 'choose_a_creation;
491 }
492 _ => {
493 result?;
494 }
495 },
496 }
497 }
498 return Err(LbErrKind::Unexpected(format!(
499 "sync failed to find a topomodelal order for file creations: {creations:?}"
500 ))
501 .into());
502 }
503
504 for id in db.local_metadata.ids() {
507 if deletions.maybe_find(&id).is_none()
509 || deletions.calculate_deleted(&id)?
510 || (remote.maybe_find(&id).is_some()
511 && remote.calculate_deleted(&id)?)
512 {
513 continue;
514 }
515
516 let local_file = local.find(&id)?.clone();
517 let local_name = local.name(&id, &self.keychain)?;
518 let maybe_base_file = base.maybe_find(&id).cloned();
519 let maybe_remote_file = remote.maybe_find(&id).cloned();
520 if let Some(ref base_file) = maybe_base_file {
521 let base_name = base.name(&id, &self.keychain)?;
522 let remote_file = remote.find(&id)?.clone();
523 let remote_name = remote.name(&id, &self.keychain)?;
524
525 if local_file.parent() != base_file.parent()
527 && remote_file.parent() == base_file.parent()
528 && !files_to_unmove.contains(&id)
529 {
530 merge.move_unvalidated(&id, local_file.parent(), &self.keychain)?;
531 }
532
533 if local_name != base_name && remote_name == base_name {
535 merge.rename_unvalidated(&id, &local_name, &self.keychain)?;
536 }
537 }
538
539 let mut remote_keys = HashMap::new();
541 if let Some(ref remote_file) = maybe_remote_file {
542 for key in remote_file.user_access_keys() {
543 remote_keys.insert(
544 (Owner(key.encrypted_by), Owner(key.encrypted_for)),
545 (key.mode, key.deleted),
546 );
547 }
548 }
549 for key in local_file.user_access_keys() {
550 let (by, for_) = (Owner(key.encrypted_by), Owner(key.encrypted_for));
551 if let Some(&(remote_mode, remote_deleted)) =
552 remote_keys.get(&(by, for_))
553 {
554 if key.mode > remote_mode || !key.deleted && remote_deleted {
556 let mode = match key.mode {
557 UserAccessMode::Read => ShareMode::Read,
558 UserAccessMode::Write => ShareMode::Write,
559 UserAccessMode::Owner => continue,
560 };
561 merge.add_share_unvalidated(id, for_, mode, &self.keychain)?;
562 }
563 if key.deleted && !remote_deleted {
565 merge.delete_share_unvalidated(
566 &id,
567 Some(for_.0),
568 &self.keychain,
569 )?;
570 }
571 } else {
572 let mode = match key.mode {
574 UserAccessMode::Read => ShareMode::Read,
575 UserAccessMode::Write => ShareMode::Write,
576 UserAccessMode::Owner => continue,
577 };
578 merge.add_share_unvalidated(id, for_, mode, &self.keychain)?;
579 }
580 }
581
582 if files_to_unshare.contains(&id) {
584 merge.delete_share_unvalidated(&id, None, &self.keychain)?;
585 }
586
587 if let Some(&rename_increment) = rename_increments.get(&id) {
589 let name = NameComponents::from(&local_name)
590 .generate_incremented(rename_increment)
591 .to_name();
592 merge.rename_unvalidated(&id, &name, &self.keychain)?;
593 }
594
595 let base_hmac = maybe_base_file.and_then(|f| f.document_hmac().cloned());
597 let remote_hmac =
598 maybe_remote_file.and_then(|f| f.document_hmac().cloned());
599 let local_hmac = local_file.document_hmac().cloned();
600 if merge.access_mode(me, &id)? >= Some(UserAccessMode::Write)
601 && local_hmac != base_hmac
602 {
603 if remote_hmac != base_hmac && remote_hmac != local_hmac {
604 let merge_name = merge.name(&id, &self.keychain)?;
606 let document_type =
607 DocumentType::from_file_name_using_extension(&merge_name);
608
609 let base_document =
612 self.read_document_helper(id, &mut base).await?;
613 let remote_document =
614 self.read_document_helper(id, &mut remote).await?;
615 let local_document =
616 self.read_document_helper(id, &mut local).await?;
617
618 match document_type {
619 DocumentType::Text => {
620 let base_document =
623 String::from_utf8_lossy(&base_document).to_string();
624 let remote_document =
625 String::from_utf8_lossy(&remote_document).to_string();
626 let local_document =
627 String::from_utf8_lossy(&local_document).to_string();
628 let merged_document =
629 text::buffer::Buffer::from(base_document.as_str())
630 .merge(local_document, remote_document);
631 let encrypted_document = merge
632 .update_document_unvalidated(
633 &id,
634 &merged_document.into_bytes(),
635 &self.keychain,
636 )?;
637 let hmac = merge.find(&id)?.document_hmac().copied();
638 self.docs.insert(id, hmac, &encrypted_document).await?;
639 }
640 DocumentType::Drawing => {
641 let base_document =
642 String::from_utf8_lossy(&base_document).to_string();
643 let remote_document =
644 String::from_utf8_lossy(&remote_document).to_string();
645 let local_document =
646 String::from_utf8_lossy(&local_document).to_string();
647
648 let base_buffer = svg::buffer::Buffer::new(&base_document);
649 let remote_buffer =
650 svg::buffer::Buffer::new(&remote_document);
651 let mut local_buffer =
652 svg::buffer::Buffer::new(&local_document);
653
654 for (_, el) in local_buffer.elements.iter_mut() {
655 if let Element::Path(path) = el {
656 path.data.apply_transform(u_transform_to_bezier(
657 &Transform::from(
658 local_buffer
659 .weak_viewport_settings
660 .master_transform,
661 ),
662 ));
663 }
664 }
665 svg::buffer::Buffer::reload(
666 &mut local_buffer.elements,
667 &mut local_buffer.weak_images,
668 &mut local_buffer.weak_path_pressures,
669 &mut local_buffer.weak_viewport_settings,
670 &base_buffer,
671 &remote_buffer,
672 );
673
674 let merged_document = local_buffer.serialize();
675 let encrypted_document = merge
676 .update_document_unvalidated(
677 &id,
678 &merged_document.into_bytes(),
679 &self.keychain,
680 )?;
681 let hmac = merge.find(&id)?.document_hmac().copied();
682 self.docs.insert(id, hmac, &encrypted_document).await?;
683 }
684 DocumentType::Chat => {
685 let merged_document = chat::Buffer::merge(
687 &base_document,
688 &local_document,
689 &remote_document,
690 );
691 let encrypted_document = merge
692 .update_document_unvalidated(
693 &id,
694 &merged_document,
695 &self.keychain,
696 )?;
697 let hmac = merge.find(&id)?.document_hmac().copied();
698 self.docs.insert(id, hmac, &encrypted_document).await?;
699 }
700 DocumentType::Other => {
701 let merge_parent = *merge.find(&id)?.parent();
703 let (duplicate_id, duplicate_key) =
704 if let Some(&tracked) = duplicate_file_ids.get(&id) {
705 tracked
706 } else {
707 let duplicate_id = Uuid::new_v4();
708 let duplicate_key = symkey::generate_key();
709 duplicate_file_ids
710 .insert(id, (duplicate_id, duplicate_key));
711 rename_increments.insert(duplicate_id, 1);
712 (duplicate_id, duplicate_key)
713 };
714
715 let mut merge_name = merge_name;
716 merge_name = NameComponents::from(&merge_name)
717 .generate_incremented(
718 rename_increments
719 .get(&duplicate_id)
720 .copied()
721 .unwrap_or_default(),
722 )
723 .to_name();
724
725 merge.create_unvalidated(
726 duplicate_id,
727 duplicate_key,
728 &merge_parent,
729 &merge_name,
730 FileType::Document,
731 &self.keychain,
732 )?;
733 let encrypted_document = merge
734 .update_document_unvalidated(
735 &duplicate_id,
736 &local_document,
737 &self.keychain,
738 )?;
739 let duplicate_hmac =
740 merge.find(&duplicate_id)?.document_hmac().copied();
741 self.docs
742 .insert(
743 duplicate_id,
744 duplicate_hmac,
745 &encrypted_document,
746 )
747 .await?;
748 }
749 }
750 } else {
751 let local_file = local.find(&id)?;
752 merge.overwrite_document_hmac_unvalidated(
753 &id,
754 local_file.document_hmac().copied(),
755 local_file.doc_size(),
756 &self.keychain,
757 )?;
758 }
759 }
760 }
761
762 for id in db.local_metadata.ids() {
765 if db.base_metadata.maybe_find(&id).is_some()
766 && deletions.calculate_deleted(&id)?
767 && !merge.calculate_deleted(&id)?
768 {
769 merge.delete_unvalidated(&id, &self.keychain)?;
771 }
772 }
773 for &id in &links_to_delete {
774 if merge.maybe_find(&id).is_some() && !merge.calculate_deleted(&id)? {
776 merge.delete_unvalidated(&id, &self.keychain)?;
777 }
778 }
779
780 merge
781 };
782
783 for link in merge.ids() {
785 if !merge.calculate_deleted(&link)? {
786 if let FileType::Link { target } = merge.find(&link)?.file_type() {
787 if merge.maybe_find(&target).is_some()
788 && merge.calculate_deleted(&target)?
789 {
790 if links_to_delete.insert(link) {
792 continue 'merge_construction;
793 } else {
794 return Err(LbErrKind::Unexpected(format!(
795 "sync failed to resolve broken link (deletion): {link:?}"
796 ))
797 .into());
798 }
799 }
800 }
801 }
802 }
803
804 let validate_result = merge.validate(me);
805 match validate_result {
806 Ok(_) => {
808 let (_, merge_changes) = merge.unstage();
809 break merge_changes;
810 }
811 Err(ref err) => match err.kind {
812 LbErrKind::Validation(ref vf) => match vf {
813 ValidationFailure::Cycle(ids) => {
815 let mut progress = false;
817 for &id in ids {
818 if db.local_metadata.maybe_find(&id).is_some()
819 && files_to_unmove.insert(id)
820 {
821 progress = true;
822 }
823 }
824 if !progress {
825 return Err(LbErrKind::Unexpected(format!(
826 "sync failed to resolve cycle: {ids:?}"
827 ))
828 .into());
829 }
830 }
831 ValidationFailure::PathConflict(ids) => {
832 let mut progress = false;
834 for &id in ids {
835 if duplicate_file_ids.values().any(|&(dup, _)| dup == id) {
836 *rename_increments.entry(id).or_insert(0) += 1;
837 progress = true;
838 break;
839 }
840 }
841 if !progress {
842 for &id in ids {
843 if db.local_metadata.maybe_find(&id).is_some() {
844 *rename_increments.entry(id).or_insert(0) += 1;
845 progress = true;
846 break;
847 }
848 }
849 }
850 if !progress {
851 return Err(LbErrKind::Unexpected(format!(
852 "sync failed to resolve path conflict: {ids:?}"
853 ))
854 .into());
855 }
856 }
857 ValidationFailure::SharedLink { link, shared_ancestor } => {
858 let mut progress = false;
860 if let Some(base_shared_ancestor) = base.maybe_find(shared_ancestor)
861 {
862 if !base_shared_ancestor.is_shared()
863 && files_to_unshare.insert(*shared_ancestor)
864 {
865 progress = true;
866 }
867 }
868 if !progress && links_to_delete.insert(*link) {
869 progress = true;
870 }
871 if !progress {
872 return Err(LbErrKind::Unexpected(format!(
873 "sync failed to resolve shared link: link: {link:?}, shared_ancestor: {shared_ancestor:?}"
874 )).into());
875 }
876 }
877 ValidationFailure::DuplicateLink { target } => {
878 let mut progress = false;
880 if let Some(link) = local.linked_by(target)? {
881 if links_to_delete.insert(link) {
882 progress = true;
883 }
884 }
885 if !progress {
886 return Err(LbErrKind::Unexpected(format!(
887 "sync failed to resolve duplicate link: target: {target:?}"
888 ))
889 .into());
890 }
891 }
892 ValidationFailure::BrokenLink(link) => {
893 if !links_to_delete.insert(*link) {
895 return Err(LbErrKind::Unexpected(format!(
896 "sync failed to resolve broken link: {link:?}"
897 ))
898 .into());
899 }
900 }
901 ValidationFailure::OwnedLink(link) => {
902 let mut progress = false;
904 if let Some(remote_link) = remote.maybe_find(link) {
905 if let FileType::Link { target } = remote_link.file_type() {
906 let remote_target = remote.find(&target)?;
907 if remote_target.owner() != me
908 && files_to_unmove.insert(target)
909 {
910 progress = true;
911 }
912 }
913 }
914 if !progress && links_to_delete.insert(*link) {
915 progress = true;
916 }
917 if !progress {
918 return Err(LbErrKind::Unexpected(format!(
919 "sync failed to resolve owned link: {link:?}"
920 ))
921 .into());
922 }
923 }
924 ValidationFailure::Orphan(_)
926 | ValidationFailure::NonFolderWithChildren(_)
927 | ValidationFailure::FileWithDifferentOwnerParent(_)
928 | ValidationFailure::FileNameTooLong(_)
929 | ValidationFailure::DeletedFileUpdated(_)
930 | ValidationFailure::NonDecryptableFileName(_) => {
931 validate_result?;
932 }
933 },
934 _ => {
936 validate_result?;
937 }
938 },
939 }
940 }
941 };
942
943 (&mut db.base_metadata)
945 .to_staged(remote_changes.clone())
946 .to_lazy()
947 .promote()?;
948 db.local_metadata.clear()?;
949 (&mut db.local_metadata)
950 .to_staged(merge_changes)
951 .to_lazy()
952 .promote()?;
953
954 db.base_metadata.stage(&mut db.local_metadata).prune()?;
957
958 if start.elapsed() > web_time::Duration::from_millis(100) {
959 warn!("sync merge held lock for {:?}", start.elapsed());
960 }
961
962 Ok(())
963 }
964
965 async fn send_pull_events(&self, state: &mut SyncState) -> LbResult<()> {
966 if state.new_root.is_some() {
967 self.events.signed_in();
968 }
969
970 if !state.remote_changes.is_empty() {
971 self.events.meta_changed(Actor::Sync);
972
973 let owner = Owner(self.keychain.get_pk()?);
974 if state.remote_changes.iter().any(|f| f.owner() != owner) {
975 self.events.pending_shares_changed();
976 }
977 }
978
979 for &doc in &state.pulled_docs {
980 self.events.doc_written(doc, Actor::Sync);
981 }
982
983 Ok(())
984 }
985
986 async fn commit_last_synced(&self, state: &mut SyncState) -> LbResult<()> {
987 let mut tx = self.begin_tx().await;
988 let db = tx.db();
989 db.last_synced.replace(state.updates_as_of as i64)?;
990
991 if let Some(root) = state.new_root {
992 db.root.replace(root)?;
993 }
994
995 Ok(())
996 }
997
998 async fn populate_pk_cache(&self) -> LbResult<()> {
999 let mut missing_owners = HashSet::new();
1001 {
1002 let tx = self.ro_tx().await;
1003 let db = tx.db();
1004 for file in db.base_metadata.iter().map(|(_, value)| value) {
1005 for user_access_key in file.user_access_keys() {
1006 let enc_by = Owner(user_access_key.encrypted_by);
1007 let enc_for = Owner(user_access_key.encrypted_for);
1008
1009 if !db.pub_key_lookup.contains_key(&enc_by) {
1010 missing_owners.insert(enc_by);
1011 }
1012
1013 if !db.pub_key_lookup.contains_key(&enc_for) {
1014 missing_owners.insert(enc_for);
1015 }
1016 }
1017 }
1018 }
1019
1020 let mut new_owners = HashMap::new();
1021 {
1022 for owner in missing_owners {
1023 let username_result = self
1024 .client
1025 .request(self.get_account().unwrap(), GetUsernameRequest { key: owner.0 })
1026 .await;
1027 new_owners.insert(owner, username_result);
1028 }
1029 }
1030
1031 let mut tx = self.begin_tx().await;
1032 let db = tx.db();
1033
1034 let have_updates = !new_owners.is_empty();
1035 for (owner, username) in new_owners {
1036 let username = match username {
1037 Err(ApiError::Endpoint(GetUsernameError::UserNotFound)) => "<unknown>".to_string(),
1038 Ok(username) => username.username,
1039 _ => continue, };
1041
1042 db.pub_key_lookup.insert(owner, username).unwrap();
1043 }
1044
1045 if have_updates {
1046 self.events.meta_changed(Actor::Sync);
1047 }
1048
1049 Ok(())
1050 }
1051
1052 async fn push_meta(&self) -> LbResult<()> {
1054 let mut updates = vec![];
1055 let mut local_changes_no_digests = Vec::new();
1056
1057 let tx = self.ro_tx().await;
1058 let db = tx.db();
1059
1060 let local = db.base_metadata.stage(&db.local_metadata).to_lazy();
1062
1063 for id in local.tree.staged.ids() {
1064 let mut local_change = local.tree.staged.find(&id)?.timestamped_value.value.clone();
1065 let maybe_base_file = local.tree.base.maybe_find(&id);
1066
1067 local_change.set_hmac_and_size(
1069 maybe_base_file.and_then(|f| f.document_hmac().copied()),
1070 maybe_base_file.and_then(|f| *f.timestamped_value.value.doc_size()),
1071 );
1072 let local_change = local_change.sign(&self.keychain)?;
1073
1074 local_changes_no_digests.push(local_change.clone());
1075 let file_diff = FileDiff { old: maybe_base_file.cloned(), new: local_change };
1076 updates.push(file_diff);
1077 }
1078
1079 drop(tx);
1080
1081 if !updates.is_empty() {
1082 self.client
1083 .request(self.get_account()?, UpsertRequestV2 { updates: updates.clone() })
1084 .await?;
1085 }
1086
1087 let mut tx = self.begin_tx().await;
1088 let db = tx.db();
1089
1090 (&mut db.base_metadata)
1092 .to_lazy()
1093 .stage(local_changes_no_digests)
1094 .promote()?;
1095 db.base_metadata.stage(&mut db.local_metadata).prune()?;
1096
1097 tx.end();
1098
1099 Ok(())
1100 }
1101
1102 async fn push_docs(&self) -> LbResult<()> {
1107 let mut updates = vec![];
1108 let mut local_changes_digests_only = vec![];
1109
1110 let tx = self.ro_tx().await;
1111 let db = tx.db();
1112 let start = Instant::now();
1113
1114 let local = db.base_metadata.stage(&db.local_metadata).to_lazy();
1115
1116 for id in local.tree.staged.ids() {
1117 let base_file = local.tree.base.find(&id)?.clone();
1118
1119 let mut local_change = base_file.timestamped_value.value.clone();
1121 local_change.set_hmac_and_size(
1122 local.find(&id)?.document_hmac().copied(),
1123 *local.find(&id)?.timestamped_value.value.doc_size(),
1124 );
1125
1126 if base_file.document_hmac() == local_change.document_hmac()
1127 || local_change.document_hmac().is_none()
1128 {
1129 continue;
1130 }
1131
1132 let local_change = local_change.sign(&self.keychain)?;
1133
1134 updates.push(FileDiff { old: Some(base_file), new: local_change.clone() });
1135 local_changes_digests_only.push(local_change);
1136 self.events
1137 .sync_update(SyncIncrement::PushingDocument(id, true));
1138 }
1139
1140 drop(tx);
1141 if start.elapsed() > web_time::Duration::from_millis(100) {
1142 warn!("sync push_docs held lock for {:?}", start.elapsed());
1143 }
1144
1145 let futures = updates.clone().into_iter().map(|diff| self.push_doc(diff));
1146
1147 let mut stream = stream::iter(futures).buffer_unordered(
1148 thread::available_parallelism()
1149 .unwrap_or(NonZeroUsize::new(4).unwrap())
1150 .into(),
1151 );
1152
1153 let mut docs_without_errors = vec![];
1154 let mut last_error: Option<LbErr> = None;
1155
1156 while let Some(fut) = stream.next().await {
1157 match fut {
1158 Ok(id) => {
1159 docs_without_errors.push(id);
1160 self.events
1161 .sync_update(SyncIncrement::PushingDocument(id, false));
1162 }
1163 Err(err) => {
1164 last_error = Some(err);
1165 }
1166 }
1167 }
1168
1169 local_changes_digests_only.retain(|f| docs_without_errors.contains(f.id()));
1170
1171 let mut tx = self.begin_tx().await;
1172 let db = tx.db();
1173 (&mut db.base_metadata)
1175 .to_lazy()
1176 .stage(local_changes_digests_only)
1177 .promote()?;
1178
1179 db.base_metadata.stage(&mut db.local_metadata).prune()?;
1180
1181 tx.end();
1182
1183 if let Some(err) = last_error { Err(err) } else { Ok(()) }
1184 }
1185
1186 async fn push_doc(&self, diff: FileDiff<SignedMeta>) -> LbResult<Uuid> {
1187 let id = *diff.new.id();
1188 let hmac = diff.new.document_hmac();
1189 let local_document_change = self.docs.get(id, hmac.copied()).await?;
1190 self.client
1191 .request(
1192 self.get_account()?,
1193 ChangeDocRequestV2 { diff, new_content: local_document_change },
1194 )
1195 .await?;
1196
1197 Ok(id)
1198 }
1199
1200 #[cfg(not(target_family = "wasm"))]
1201 async fn send_debug_info(&self, account: Account) {
1202 use crate::service::debug;
1203
1204 let max_panic_time = debug::latest_panic_time(&self.config.writeable_path)
1205 .await
1206 .unwrap_or_else(|e| {
1207 warn!("could not enumerate panic files: {e:?}");
1208 None
1209 });
1210
1211 let last_sent = {
1212 let tx = self.ro_tx().await;
1213 tx.db().last_extracted_panic.as_ref().copied()
1214 };
1215
1216 let should_send = match (last_sent, max_panic_time) {
1217 (None, _) => true,
1218 (Some(prev), Some(cur)) => cur > prev,
1219 (Some(_), None) => false,
1220 };
1221
1222 if !should_send {
1223 return;
1224 }
1225
1226 let debug_info = self
1227 .debug_info("none provided - sync".to_string(), false)
1228 .await
1229 .unwrap();
1230
1231 let new_marker = max_panic_time.unwrap_or(0);
1232 let bg_self = self.clone();
1233 let task = async move {
1234 match bg_self
1235 .client
1236 .request(&account, UpsertDebugInfoRequest { debug_info })
1237 .await
1238 {
1239 Ok(_) => {
1240 let mut tx = bg_self.begin_tx().await;
1241 if let Err(e) = tx.db().last_extracted_panic.replace(new_marker) {
1242 warn!("could not record last_extracted_panic: {e:?}");
1243 }
1244 tx.end();
1245 }
1246 Err(e) => warn!("send_debug_info failed: {e:?}"),
1247 }
1248 };
1249
1250 if self.config.background_work {
1251 tokio::spawn(task);
1252 } else {
1253 task.await;
1254 }
1255 }
1256
1257 async fn read_document_helper<T>(
1258 &self, id: Uuid, tree: &mut LazyTree<T>,
1259 ) -> LbResult<DecryptedDocument>
1260 where
1261 T: TreeLike<F = SignedMeta>,
1262 {
1263 let file = tree.find(&id)?;
1264 validate::is_document(file)?;
1265 let hmac = file.document_hmac().copied();
1266
1267 if tree.calculate_deleted(&id)? {
1268 return Err(LbErrKind::FileNonexistent.into());
1269 }
1270
1271 let doc = match hmac {
1272 Some(hmac) => {
1273 let doc = self.docs.get(id, Some(hmac)).await?;
1274 tree.decrypt_document(&id, &doc, &self.keychain)?
1275 }
1276 None => vec![],
1277 };
1278
1279 Ok(doc)
1280 }
1281
1282 #[doc(hidden)]
1284 pub async fn server_dirty_ids(&self) -> LbResult<Vec<Uuid>> {
1285 let mut state = self.syncer.lock().await;
1286 self.inital_sync_state(&mut state).await?;
1287 self.process_deletions().await?;
1288 self.fetch_meta(&mut state).await?;
1289
1290 let server_ids = state.remote_changes.iter().map(|f| *f.id()).collect();
1291
1292 Ok(server_ids)
1293 }
1294
1295 pub(crate) fn setup_syncer(&self) {
1296 if self.config.background_work {
1297 self.clone().local_change_worker();
1298 self.clone().periodic_sync_worker();
1299 self.clone().post_sync_worker();
1300 }
1301 }
1302
1303 fn local_change_worker(self) {
1304 #[cfg(not(target_family = "wasm"))]
1305 tokio::spawn(async move {
1306 let mut events = self.subscribe();
1307
1308 let sync_criteria = |e: Event| {
1309 matches!(
1310 e,
1311 Event::MetadataChanged(Actor::User(_))
1312 | Event::DocumentWritten(_, Actor::User(_))
1313 )
1314 };
1315
1316 loop {
1317 time::sleep(Duration::from_millis(500)).await;
1318 let mut should_sync = false;
1319
1320 loop {
1322 let event = events.try_recv();
1323 match event {
1324 Ok(event) => {
1325 if sync_criteria(event) {
1326 should_sync = true;
1327 }
1328 }
1329 Err(TryRecvError::Empty) => break,
1330 _ => {
1331 panic!(
1332 "unexpected broadcast receive error, returning local_change_worker"
1333 );
1334 }
1335 }
1336 }
1337
1338 if !should_sync {
1341 let event = events.recv().await.unwrap();
1342 if sync_criteria(event) {
1343 self.sync().await.map_unexpected().log_and_ignore();
1344 } else {
1345 continue;
1346 }
1347 }
1348 }
1349 });
1350 }
1351
1352 fn periodic_sync_worker(self) {
1353 #[cfg(not(target_family = "wasm"))]
1354 tokio::spawn(async move {
1355 loop {
1356 if self.user_active().await {
1357 tokio::time::sleep(Duration::from_secs(3)).await;
1358 } else {
1359 tokio::select! {
1360 _ = tokio::time::sleep(Duration::from_secs(5 * 60)) => {}
1361 _ = self.user_wake.notified() => {}
1362 }
1363 }
1364 self.sync().await.map_unexpected().log_and_ignore();
1365 }
1366 });
1367 }
1368
1369 pub(crate) async fn user_active(&self) -> bool {
1370 let last_seen = self.user_last_seen.read().await;
1371 last_seen.elapsed() < Duration::from_secs(15)
1372 }
1373
1374 fn post_sync_worker(self) {
1375 #[cfg(not(target_family = "wasm"))]
1376 tokio::spawn(async move {
1377 let mut events = self.subscribe();
1378
1379 loop {
1380 let event = events.recv().await.unwrap();
1381 if let Event::Sync(SyncIncrement::SyncFinished(_)) = event {
1382 self.fetcher().await.map_unexpected().log_and_ignore();
1383 self.populate_pk_cache()
1384 .await
1385 .map_unexpected()
1386 .log_and_ignore();
1387 };
1388 }
1389 });
1390 }
1391
1392 async fn fetcher(&self) -> LbResult<()> {
1393 let mut files_to_pull = vec![];
1394
1395 let tx = self.ro_tx().await;
1396 let db = tx.db();
1397
1398 let Some(root) = db.root.as_ref() else {
1399 return Ok(());
1400 };
1401
1402 let mut tree = db.base_metadata.stage(None).to_lazy();
1404
1405 for id in tree.descendants_using_links(root)? {
1406 let file = tree.find(&id)?;
1407 let hmac = file.document_hmac().copied();
1408
1409 if !file.is_document() {
1411 continue;
1412 }
1413
1414 if tree.calculate_deleted(&id)? {
1416 continue;
1417 }
1418
1419 if self.docs.exists(id, hmac) {
1420 continue;
1421 }
1422
1423 let name = tree.name(&id, &self.keychain)?;
1425 if !name.ends_with(".md") && !name.ends_with(".svg") {
1426 continue;
1427 }
1428
1429 files_to_pull.push((id, hmac));
1430 }
1431
1432 drop(tx);
1433
1434 for (id, hmac) in files_to_pull {
1438 if let Some(hmac) = hmac {
1439 self.ensure_doc_available(id, hmac).await?;
1440 }
1441 }
1442
1443 Ok(())
1444 }
1445}