Skip to main content

lb_rs/
lib.rs

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