1#[macro_use]
19extern crate tracing;
20
21pub mod blocking;
22pub mod experiments;
23pub mod io;
24pub mod ipc;
25pub mod macros;
26pub mod model;
27pub mod search;
28pub mod service;
29pub mod subscribers;
30#[cfg(target_family = "wasm")]
31pub mod wasm;
32
33#[derive(Clone)]
34pub struct Lb {
35 pub local: Arc<OnceLock<LocalLb>>,
36 pub remote: Option<Arc<RemoteLb>>,
37 pub config: Config,
38}
39
40#[derive(Clone)]
41pub struct LocalLb {
42 pub config: Config,
43 pub user_last_seen: Arc<RwLock<Instant>>,
44 pub user_wake: Arc<Notify>,
45 pub keychain: Keychain,
46 pub db: LbDb,
47 pub docs: AsyncDocs,
48 pub client: Network,
49 pub events: EventSubs,
50 pub status: StatusUpdater,
51 pub syncer: Syncer,
52}
53
54impl LocalLb {
55 #[instrument(level = "info", skip_all, err(Debug))]
56 pub async fn init(config: Config) -> LbResult<Self> {
57 let docs = AsyncDocs::from(&config);
58 let db_cfg = db_rs::Config::in_folder(&config.writeable_path);
59 #[cfg(target_os = "ios")]
61 let db_cfg = db_rs::Config { fs_locks: false, ..db_cfg };
62 let db = CoreDb::init(db_cfg).map_err(|err| LbErrKind::Unexpected(format!("{err:#?}")))?;
63 let keychain = Keychain::from(db.account.get());
64 let db = Arc::new(RwLock::new(db));
65 let client = Network { client_type: config.client_type, ..Network::default() };
66
67 let status = StatusUpdater::default();
68 let syncer = Default::default();
69 let events = EventSubs::default();
70 let user_last_seen = Arc::new(RwLock::new(Instant::now()));
71 let user_wake = Arc::new(Notify::new());
72
73 let result = Self {
74 config,
75 keychain,
76 db,
77 docs,
78 client,
79 syncer,
80 events,
81 status,
82 user_last_seen,
83 user_wake,
84 };
85
86 #[cfg(not(target_family = "wasm"))]
87 {
88 result.setup_syncer();
89 result.setup_status().await?;
90 }
91
92 Ok(result)
93 }
94}
95
96impl Lb {
97 pub async fn init(config: Config) -> LbResult<Self> {
98 let local: Arc<OnceLock<LocalLb>> = Arc::new(OnceLock::new());
99 let init_err = match LocalLb::init(config.clone()).await {
100 Ok(loc) => {
101 logging::init(&loc.config)?;
102 ipc::spawn_host(loc.clone());
103 let _ = local.set(loc);
104 return Ok(Self { local, remote: None, config });
105 }
106 Err(err) => err,
107 };
108 if let Some(remote) = ipc::connect_guest(&config).await {
109 return Ok(Self { local, remote: Some(remote), config });
110 }
111 Err(init_err)
112 }
113}
114
115impl Lb {
116 pub async fn create_account(
117 &self, username: &str, api_url: &str, welcome_doc: bool,
118 ) -> LbResult<Account> {
119 if let Some(local) = self.local.get() {
120 return local.create_account(username, api_url, welcome_doc).await;
121 }
122 let account = self
123 .call::<Account>(Request::CreateAccount {
124 username: username.to_string(),
125 api_url: api_url.to_string(),
126 welcome_doc,
127 })
128 .await?;
129 self.cache_account_on_remote(&account);
130 Ok(account)
131 }
132
133 pub async fn import_account(&self, key: &str, api_url: Option<&str>) -> LbResult<Account> {
134 if let Some(local) = self.local.get() {
135 return local.import_account(key, api_url).await;
136 }
137 let account = self
138 .call::<Account>(Request::ImportAccount {
139 key: key.to_string(),
140 api_url: api_url.map(|s| s.to_string()),
141 })
142 .await?;
143 self.cache_account_on_remote(&account);
144 Ok(account)
145 }
146
147 pub async fn import_account_private_key_v1(&self, account: Account) -> LbResult<Account> {
148 if let Some(local) = self.local.get() {
149 return local.import_account_private_key_v1(account).await;
150 }
151 let account = self
152 .call::<Account>(Request::ImportAccountPrivateKeyV1 { account })
153 .await?;
154 self.cache_account_on_remote(&account);
155 Ok(account)
156 }
157
158 pub async fn import_account_phrase(
159 &self, phrase: [&str; 24], api_url: &str,
160 ) -> LbResult<Account> {
161 if let Some(local) = self.local.get() {
162 return local.import_account_phrase(phrase, api_url).await;
163 }
164 let account = self
165 .call::<Account>(Request::ImportAccountPhrase {
166 phrase: phrase.iter().map(|s| s.to_string()).collect(),
167 api_url: api_url.to_string(),
168 })
169 .await?;
170 self.cache_account_on_remote(&account);
171 Ok(account)
172 }
173
174 fn cache_account_on_remote(&self, account: &Account) {
175 if let Some(remote) = &self.remote {
176 remote.cache_account(account.clone());
177 }
178 }
179
180 pub async fn delete_account(&self) -> LbResult<()> {
181 if let Some(local) = self.local.get() {
182 return local.delete_account().await;
183 }
184 self.call(Request::DeleteAccount).await
185 }
186
187 pub fn get_account(&self) -> LbResult<Account> {
188 if let Some(local) = self.local.get() {
189 return local.get_account().cloned();
190 }
191 self.remote
192 .as_ref()
193 .expect("get_account: remote must be set when local is unset")
194 .get_account()
195 .cloned()
196 }
197
198 pub async fn suggested_docs(&self, settings: RankingWeights) -> LbResult<Vec<Uuid>> {
199 if let Some(local) = self.local.get() {
200 return local.suggested_docs(settings).await;
201 }
202 self.call(Request::SuggestedDocs { settings }).await
203 }
204
205 pub async fn clear_suggested(&self) -> LbResult<()> {
206 if let Some(local) = self.local.get() {
207 return local.clear_suggested().await;
208 }
209 self.call(Request::ClearSuggested).await
210 }
211
212 pub async fn clear_suggested_id(&self, id: Uuid) -> LbResult<()> {
213 if let Some(local) = self.local.get() {
214 return local.clear_suggested_id(id).await;
215 }
216 self.call(Request::ClearSuggestedId { id }).await
217 }
218
219 pub fn app_foregrounded(&self) {
220 if let Some(local) = self.local.get() {
221 local.app_foregrounded();
222 return;
223 }
224 if let Some(remote) = &self.remote {
225 let r = Arc::clone(remote);
226 tokio::spawn(async move {
227 let _ = r.try_call::<()>(Request::AppForegrounded).await;
228 });
229 }
230 }
231
232 pub async fn disappear_account(&self, username: &str) -> LbResult<()> {
233 if let Some(local) = self.local.get() {
234 return local.disappear_account(username).await;
235 }
236 self.call(Request::DisappearAccount { username: username.to_string() })
237 .await
238 }
239
240 pub async fn disappear_file(&self, id: Uuid) -> LbResult<()> {
241 if let Some(local) = self.local.get() {
242 return local.disappear_file(id).await;
243 }
244 self.call(Request::DisappearFile { id }).await
245 }
246
247 pub async fn list_users(&self, filter: Option<AccountFilter>) -> LbResult<Vec<Username>> {
248 if let Some(local) = self.local.get() {
249 return local.list_users(filter).await;
250 }
251 self.call(Request::ListUsers { filter }).await
252 }
253
254 pub async fn get_account_info(&self, identifier: AccountIdentifier) -> LbResult<AccountInfo> {
255 if let Some(local) = self.local.get() {
256 return local.get_account_info(identifier).await;
257 }
258 self.call(Request::GetAccountInfo { identifier }).await
259 }
260
261 pub async fn validate_account(&self, username: &str) -> LbResult<AdminValidateAccount> {
262 if let Some(local) = self.local.get() {
263 return local.validate_account(username).await;
264 }
265 self.call(Request::AdminValidateAccount { username: username.to_string() })
266 .await
267 }
268
269 pub async fn validate_server(&self) -> LbResult<AdminValidateServer> {
270 if let Some(local) = self.local.get() {
271 return local.validate_server().await;
272 }
273 self.call(Request::AdminValidateServer).await
274 }
275
276 pub async fn file_info(&self, id: Uuid) -> LbResult<AdminFileInfoResponse> {
277 if let Some(local) = self.local.get() {
278 return local.file_info(id).await;
279 }
280 self.call(Request::AdminFileInfo { id }).await
281 }
282
283 pub async fn rebuild_index(&self, index: ServerIndex) -> LbResult<()> {
284 if let Some(local) = self.local.get() {
285 return local.rebuild_index(index).await;
286 }
287 self.call(Request::RebuildIndex { index }).await
288 }
289
290 pub async fn set_user_tier(&self, username: &str, info: AdminSetUserTierInfo) -> LbResult<()> {
291 if let Some(local) = self.local.get() {
292 return local.set_user_tier(username, info).await;
293 }
294 self.call(Request::SetUserTier { username: username.to_string(), info })
295 .await
296 }
297
298 pub async fn upgrade_account_stripe(&self, account_tier: StripeAccountTier) -> LbResult<()> {
299 if let Some(local) = self.local.get() {
300 return local.upgrade_account_stripe(account_tier).await;
301 }
302 self.call(Request::UpgradeAccountStripe { account_tier })
303 .await
304 }
305
306 pub async fn upgrade_account_google_play(
307 &self, purchase_token: &str, account_id: &str,
308 ) -> LbResult<()> {
309 if let Some(local) = self.local.get() {
310 return local
311 .upgrade_account_google_play(purchase_token, account_id)
312 .await;
313 }
314 self.call(Request::UpgradeAccountGooglePlay {
315 purchase_token: purchase_token.to_string(),
316 account_id: account_id.to_string(),
317 })
318 .await
319 }
320
321 pub async fn upgrade_account_app_store(
322 &self, original_transaction_id: String, app_account_token: String,
323 ) -> LbResult<()> {
324 if let Some(local) = self.local.get() {
325 return local
326 .upgrade_account_app_store(original_transaction_id, app_account_token)
327 .await;
328 }
329 self.call(Request::UpgradeAccountAppStore { original_transaction_id, app_account_token })
330 .await
331 }
332
333 pub async fn cancel_subscription(&self) -> LbResult<()> {
334 if let Some(local) = self.local.get() {
335 return local.cancel_subscription().await;
336 }
337 self.call(Request::CancelSubscription).await
338 }
339
340 pub async fn get_subscription_info(&self) -> LbResult<Option<SubscriptionInfo>> {
341 if let Some(local) = self.local.get() {
342 return local.get_subscription_info().await;
343 }
344 self.call(Request::GetSubscriptionInfo).await
345 }
346
347 #[cfg(not(target_family = "wasm"))]
348 pub async fn recent_panic(&self) -> LbResult<bool> {
349 if let Some(local) = self.local.get() {
350 return local.recent_panic().await;
351 }
352 self.call(Request::RecentPanic).await
353 }
354
355 #[cfg(not(target_family = "wasm"))]
356 pub async fn write_panic_to_file(&self, error_header: String, bt: String) -> LbResult<String> {
357 if let Some(local) = self.local.get() {
358 return local.write_panic_to_file(error_header, bt).await;
359 }
360 self.call(Request::WritePanicToFile { error_header, bt })
361 .await
362 }
363
364 #[cfg(not(target_family = "wasm"))]
365 pub async fn debug_info(&self, os_info: String, check_docs: bool) -> LbResult<DebugInfo> {
366 if let Some(local) = self.local.get() {
367 return local.debug_info(os_info, check_docs).await;
368 }
369 self.call(Request::DebugInfo { os_info, check_docs }).await
370 }
371
372 pub async fn read_document(
373 &self, id: Uuid, user_activity: bool,
374 ) -> LbResult<DecryptedDocument> {
375 if let Some(local) = self.local.get() {
376 return local.read_document(id, user_activity).await;
377 }
378 self.call(Request::ReadDocument { id, user_activity }).await
379 }
380
381 pub async fn write_document(&self, id: Uuid, content: &[u8]) -> LbResult<()> {
382 if let Some(local) = self.local.get() {
383 return local.write_document(id, content).await;
384 }
385 self.call(Request::WriteDocument { id, content: content.to_vec() })
386 .await
387 }
388
389 pub async fn read_document_with_hmac(
390 &self, id: Uuid, user_activity: bool,
391 ) -> LbResult<(Option<DocumentHmac>, DecryptedDocument)> {
392 if let Some(local) = self.local.get() {
393 return local.read_document_with_hmac(id, user_activity).await;
394 }
395 self.call(Request::ReadDocumentWithHmac { id, user_activity })
396 .await
397 }
398
399 pub async fn safe_write(
400 &self, id: Uuid, old_hmac: Option<DocumentHmac>, content: Vec<u8>, origin: Option<Uuid>,
401 ) -> LbResult<DocumentHmac> {
402 if let Some(local) = self.local.get() {
403 return local.safe_write(id, old_hmac, content, origin).await;
404 }
405 self.call(Request::SafeWrite { id, old_hmac, content, origin })
406 .await
407 }
408
409 pub async fn create_file(
410 &self, name: &str, parent: &Uuid, file_type: FileType,
411 ) -> LbResult<File> {
412 if let Some(local) = self.local.get() {
413 return local.create_file(name, parent, file_type).await;
414 }
415 self.call::<File>(Request::CreateFile {
416 name: name.to_string(),
417 parent: *parent,
418 file_type,
419 })
420 .await
421 }
422
423 pub async fn rename_file(&self, id: &Uuid, new_name: &str) -> LbResult<()> {
424 if let Some(local) = self.local.get() {
425 return local.rename_file(id, new_name).await;
426 }
427 self.call(Request::RenameFile { id: *id, new_name: new_name.to_string() })
428 .await
429 }
430
431 pub async fn move_file(&self, id: &Uuid, new_parent: &Uuid) -> LbResult<()> {
432 if let Some(local) = self.local.get() {
433 return local.move_file(id, new_parent).await;
434 }
435 self.call(Request::MoveFile { id: *id, new_parent: *new_parent })
436 .await
437 }
438
439 pub async fn duplicate_file(&self, id: &Uuid) -> LbResult<File> {
440 if let Some(local) = self.local.get() {
441 return local.duplicate_file(id).await;
442 }
443 self.call(Request::DuplicateFile { id: *id }).await
444 }
445
446 pub async fn delete(&self, id: &Uuid) -> LbResult<()> {
447 if let Some(local) = self.local.get() {
448 return local.delete(id).await;
449 }
450 self.call(Request::Delete { id: *id }).await
451 }
452
453 pub async fn root(&self) -> LbResult<File> {
454 if let Some(local) = self.local.get() {
455 return local.root().await;
456 }
457 self.call(Request::Root).await
458 }
459
460 pub async fn list_metadatas(&self) -> LbResult<Vec<File>> {
461 if let Some(local) = self.local.get() {
462 return local.list_metadatas().await;
463 }
464 self.call(Request::ListMetadatas).await
465 }
466
467 pub async fn get_children(&self, id: &Uuid) -> LbResult<Vec<File>> {
468 if let Some(local) = self.local.get() {
469 return local.get_children(id).await;
470 }
471 self.call(Request::GetChildren { id: *id }).await
472 }
473
474 pub async fn get_and_get_children_recursively(&self, id: &Uuid) -> LbResult<Vec<File>> {
475 if let Some(local) = self.local.get() {
476 return local.get_and_get_children_recursively(id).await;
477 }
478 self.call(Request::GetAndGetChildrenRecursively { id: *id })
479 .await
480 }
481
482 pub async fn get_file_by_id(&self, id: Uuid) -> LbResult<File> {
483 if let Some(local) = self.local.get() {
484 return local.get_file_by_id(id).await;
485 }
486 self.call(Request::GetFileById { id }).await
487 }
488
489 pub async fn get_file_link_url(&self, id: Uuid) -> LbResult<String> {
490 if let Some(local) = self.local.get() {
491 return local.get_file_link_url(id).await;
492 }
493 self.call(Request::GetFileLinkUrl { id }).await
494 }
495
496 pub async fn local_changes(&self) -> Vec<Uuid> {
497 if let Some(local) = self.local.get() {
498 return local.local_changes().await;
499 }
500 self.call::<_>(Request::LocalChanges)
501 .await
502 .unwrap_or_default()
503 }
504
505 pub async fn test_repo_integrity(&self, check_docs: bool) -> LbResult<Vec<Warning>> {
506 if let Some(local) = self.local.get() {
507 return local.test_repo_integrity(check_docs).await;
508 }
509 self.call(Request::TestRepoIntegrity { check_docs }).await
510 }
511
512 pub async fn create_link_at_path(&self, path: &str, target_id: Uuid) -> LbResult<File> {
513 if let Some(local) = self.local.get() {
514 return local.create_link_at_path(path, target_id).await;
515 }
516 self.call(Request::CreateLinkAtPath { path: path.to_string(), target_id })
517 .await
518 }
519
520 pub async fn create_at_path(&self, path: &str) -> LbResult<File> {
521 if let Some(local) = self.local.get() {
522 return local.create_at_path(path).await;
523 }
524 self.call(Request::CreateAtPath { path: path.to_string() })
525 .await
526 }
527
528 pub async fn get_by_path(&self, path: &str) -> LbResult<File> {
529 if let Some(local) = self.local.get() {
530 return local.get_by_path(path).await;
531 }
532 self.call(Request::GetByPath { path: path.to_string() })
533 .await
534 }
535
536 pub async fn get_path_by_id(&self, id: Uuid) -> LbResult<String> {
537 if let Some(local) = self.local.get() {
538 return local.get_path_by_id(id).await;
539 }
540 self.call(Request::GetPathById { id }).await
541 }
542
543 pub async fn list_paths(&self, filter: Option<Filter>) -> LbResult<Vec<String>> {
544 if let Some(local) = self.local.get() {
545 return local.list_paths(filter).await;
546 }
547 self.call(Request::ListPaths { filter }).await
548 }
549
550 pub async fn list_paths_with_ids(
551 &self, filter: Option<Filter>,
552 ) -> LbResult<Vec<(Uuid, String)>> {
553 if let Some(local) = self.local.get() {
554 return local.list_paths_with_ids(filter).await;
555 }
556 self.call(Request::ListPathsWithIds { filter }).await
557 }
558
559 pub async fn share_file(&self, id: Uuid, username: &str, mode: ShareMode) -> LbResult<()> {
560 if let Some(local) = self.local.get() {
561 return local.share_file(id, username, mode).await;
562 }
563 self.call(Request::ShareFile { id, username: username.to_string(), mode })
564 .await
565 }
566
567 pub async fn get_pending_shares(&self) -> LbResult<Vec<File>> {
568 if let Some(local) = self.local.get() {
569 return local.get_pending_shares().await;
570 }
571 self.call(Request::GetPendingShares).await
572 }
573
574 pub async fn get_pending_share_files(&self) -> LbResult<Vec<File>> {
575 if let Some(local) = self.local.get() {
576 return local.get_pending_share_files().await;
577 }
578 self.call(Request::GetPendingShareFiles).await
579 }
580
581 pub async fn known_usernames(&self) -> LbResult<Vec<String>> {
582 if let Some(local) = self.local.get() {
583 return local.known_usernames().await;
584 }
585 self.call(Request::KnownUsernames).await
586 }
587
588 pub async fn reject_share(&self, id: &Uuid) -> LbResult<()> {
589 if let Some(local) = self.local.get() {
590 return local.reject_share(id).await;
591 }
592 self.call(Request::RejectShare { id: *id }).await
593 }
594
595 pub async fn pin_file(&self, id: Uuid) -> LbResult<()> {
596 if let Some(local) = self.local.get() {
597 return local.pin_file(id).await;
598 }
599 self.call(Request::PinFile { id }).await
600 }
601
602 pub async fn unpin_file(&self, id: Uuid) -> LbResult<()> {
603 if let Some(local) = self.local.get() {
604 return local.unpin_file(id).await;
605 }
606 self.call(Request::UnpinFile { id }).await
607 }
608
609 pub async fn list_pinned(&self) -> LbResult<Vec<Uuid>> {
610 if let Some(local) = self.local.get() {
611 return local.list_pinned().await;
612 }
613 self.call(Request::ListPinned).await
614 }
615
616 pub async fn get_usage(&self) -> LbResult<UsageMetrics> {
617 if let Some(local) = self.local.get() {
618 return local.get_usage().await;
619 }
620 self.call(Request::GetUsage).await
621 }
622
623 pub async fn sync(&self) -> LbResult<()> {
624 if let Some(local) = self.local.get() {
625 return local.sync().await;
626 }
627 self.call(Request::Sync).await
628 }
629
630 pub async fn status(&self) -> Status {
631 if let Some(local) = self.local.get() {
632 return local.status().await;
633 }
634 self.call::<_>(Request::Status).await.unwrap_or_default()
635 }
636
637 pub async fn get_last_synced(&self) -> LbResult<i64> {
638 if let Some(local) = self.local.get() {
639 return local.get_last_synced().await;
640 }
641 self.call(Request::GetLastSynced).await
642 }
643
644 pub async fn get_last_synced_human(&self) -> LbResult<String> {
645 if let Some(local) = self.local.get() {
646 return local.get_last_synced_human().await;
647 }
648 self.call(Request::GetLastSyncedHuman).await
649 }
650
651 pub fn config(&self) -> &Config {
652 &self.config
653 }
654
655 pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver<service::events::Event> {
656 if let Some(local) = self.local.get() {
657 return local.subscribe();
658 }
659 self.remote
660 .as_ref()
661 .expect("subscribe: remote must be set when local is unset")
662 .subscribe()
663 }
664
665 pub fn get_timestamp_human_string(&self, timestamp: i64) -> String {
666 use basic_human_duration::ChronoHumanDuration;
667 if timestamp != 0 {
668 time::Duration::milliseconds(crate::model::clock::get_time().0 - timestamp)
669 .format_human()
670 .to_string()
671 } else {
672 "never".to_string()
673 }
674 }
675}
676pub fn get_code_version() -> &'static str {
677 env!("CARGO_PKG_VERSION")
678}
679
680pub static DEFAULT_API_LOCATION: &str = "https://app.lockbook.net";
681pub static CORE_CODE_VERSION: &str = env!("CARGO_PKG_VERSION");
682
683use crate::io::CoreDb;
684use crate::ipc::client::RemoteLb;
685use crate::subscribers::syncer::Syncer;
686use db_rs::Db;
687
688use crate::service::logging;
689use io::LbDb;
690use io::docs::AsyncDocs;
691use io::network::Network;
692use model::core_config::Config;
693pub use model::errors::{LbErrKind, LbResult};
694use service::events::EventSubs;
695use service::keychain::Keychain;
696use std::sync::{Arc, OnceLock};
697use subscribers::status::StatusUpdater;
698use tokio::sync::{Notify, RwLock};
699pub use uuid::Uuid;
700use web_time::Instant;
701
702use crate::ipc::protocol::Request;
703use crate::model::account::{Account, Username};
704use crate::model::api::{
705 AccountFilter, AccountIdentifier, AccountInfo, AdminFileInfoResponse, AdminSetUserTierInfo,
706 AdminValidateAccount, AdminValidateServer, ServerIndex, StripeAccountTier, SubscriptionInfo,
707};
708use crate::model::crypto::DecryptedDocument;
709use crate::model::errors::Warning;
710use crate::model::file::{File, ShareMode};
711use crate::model::file_metadata::{DocumentHmac, FileType};
712use crate::model::path_ops::Filter;
713use crate::service::activity::RankingWeights;
714#[cfg(not(target_family = "wasm"))]
715use crate::service::debug::DebugInfo;
716use crate::service::usage::UsageMetrics;
717use crate::subscribers::status::Status;