Skip to main content

lb_rs/service/
share.rs

1use crate::Lb;
2use crate::model::api::GetPublicKeyRequest;
3use crate::model::errors::{LbErr, LbResult};
4use crate::model::file::{File, ShareMode};
5use crate::model::file_metadata::Owner;
6use crate::model::tree_like::TreeLike;
7use crate::service::events::Actor;
8use libsecp256k1::PublicKey;
9use uuid::Uuid;
10
11impl Lb {
12    // todo: this can check whether the username is known already
13    #[instrument(level = "debug", skip(self))]
14    pub async fn share_file(&self, id: Uuid, username: &str, mode: ShareMode) -> LbResult<()> {
15        let account = self.get_account()?;
16        let username = username.to_lowercase();
17
18        let sharee = Owner(
19            self.client
20                .request(account, GetPublicKeyRequest { username: username.clone() })
21                .await
22                .map_err(LbErr::from)?
23                .key,
24        );
25
26        let mut tx = self.begin_tx().await;
27        let db = tx.db();
28
29        let mut tree = (&db.base_metadata)
30            .to_staged(&mut db.local_metadata)
31            .to_lazy();
32        db.pub_key_lookup.insert(sharee, username)?;
33
34        tree.add_share(id, sharee, mode, &self.keychain)?;
35
36        tx.end();
37
38        self.events.meta_changed(Actor::User(None));
39
40        Ok(())
41    }
42
43    /// returns pending shares -- files shared with us that we haven't accepted or rejected
44    /// this function just returns the actual files that were shared -- or the roots of shared
45    /// trees. For the full set of shares see [Self::get_pending_share_files]
46    #[instrument(level = "debug", skip(self))]
47    pub async fn get_pending_shares(&self) -> LbResult<Vec<File>> {
48        let tx = self.ro_tx().await;
49        let db = tx.db();
50
51        let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
52        let pending_roots = tree.pending_roots(&self.keychain)?.into_iter();
53
54        tree.decrypt_all(&self.keychain, pending_roots, &db.pub_key_lookup, false)
55    }
56
57    /// returns *all* the files associated with any pending shares (the share as well as it's
58    /// descendants).
59    #[instrument(level = "debug", skip(self))]
60    pub async fn get_pending_share_files(&self) -> LbResult<Vec<File>> {
61        let tx = self.ro_tx().await;
62        let db = tx.db();
63
64        let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
65        let pending_files = tree.non_deleted_pending_files(&self.keychain)?.into_iter();
66
67        tree.decrypt_all(&self.keychain, pending_files, &db.pub_key_lookup, false)
68    }
69
70    #[instrument(level = "debug", skip(self))]
71    async fn delete_share(
72        &self, id: &Uuid, maybe_encrypted_for: Option<PublicKey>,
73    ) -> LbResult<()> {
74        let mut tx = self.begin_tx().await;
75        let db = tx.db();
76
77        let mut tree = (&db.base_metadata)
78            .to_staged(&mut db.local_metadata)
79            .to_lazy();
80
81        tree.delete_share(id, maybe_encrypted_for, &self.keychain)?;
82
83        tx.end();
84        self.events.meta_changed(Actor::User(None));
85
86        Ok(())
87    }
88
89    #[instrument(level = "debug", skip(self))]
90    pub async fn known_usernames(&self) -> LbResult<Vec<String>> {
91        let db = self.ro_tx().await;
92        let db = db.db();
93
94        Ok(db
95            .pub_key_lookup
96            .iter()
97            .map(|(_, value)| value)
98            .cloned()
99            .collect())
100    }
101
102    /// Whether `username` is a known Lockbook account (local cache, then server).
103    ///
104    /// Server lookup uses [`GetPublicKeyRequest`]. Auth is only a signed envelope —
105    /// when signed out we probe with an ephemeral key so onboard can check
106    /// availability without an account. `UserNotFound` maps to `false`.
107    #[instrument(level = "debug", skip(self))]
108    pub async fn username_exists(&self, username: &str) -> LbResult<bool> {
109        use crate::DEFAULT_API_LOCATION;
110        use crate::model::account::Account;
111        use crate::model::errors::LbErrKind;
112
113        let username = username.trim().to_lowercase();
114        if username.is_empty() {
115            return Ok(false);
116        }
117        // Signed-in only: local roster can short-circuit a hit. Empty when signed out.
118        if let Ok(known) = self.known_usernames().await {
119            if known.iter().any(|u| u.eq_ignore_ascii_case(&username)) {
120                return Ok(true);
121            }
122        }
123
124        // Prefer real account for api_url; else ephemeral signer + API_URL / default.
125        let owned;
126        let account = match self.get_account() {
127            Ok(a) => a,
128            Err(_) => {
129                let api =
130                    std::env::var("API_URL").unwrap_or_else(|_| DEFAULT_API_LOCATION.to_string());
131                owned = Account::new(String::new(), api);
132                &owned
133            }
134        };
135
136        match self
137            .client
138            .request(account, GetPublicKeyRequest { username })
139            .await
140        {
141            Ok(_) => Ok(true),
142            Err(e) => {
143                let err = LbErr::from(e);
144                match err.kind {
145                    // GetPublicKey UserNotFound → AccountNonexistent in LbErr map.
146                    LbErrKind::AccountNonexistent | LbErrKind::UsernameNotFound => Ok(false),
147                    _ => Err(err),
148                }
149            }
150        }
151    }
152
153    #[instrument(level = "debug", skip(self))]
154    pub async fn reject_share(&self, id: &Uuid) -> Result<(), LbErr> {
155        let pk = self.keychain.get_pk()?;
156        self.delete_share(id, Some(pk)).await
157    }
158}