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