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