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