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 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        // an flock held across iOS suspend causes 0xdead10cc, iOS has no IPC yet
60        #[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;