use crate::Lb;
use crate::model::api::GetPublicKeyRequest;
use crate::model::errors::{LbErr, LbErrKind, LbResult};
use crate::model::file::{File, ShareMode};
use crate::model::file_like::FileLike;
use crate::model::file_metadata::Owner;
use crate::model::tree_like::TreeLike;
use crate::service::events::Actor;
use libsecp256k1::PublicKey;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SharingContact {
pub username: String,
pub outgoing_file_count: u64,
pub incoming_file_count: u64,
pub total_file_count: u64,
}
impl Lb {
#[instrument(level = "debug", skip(self))]
pub async fn get_sharing_contacts(&self) -> LbResult<Vec<SharingContact>> {
let me = self.keychain.get_pk()?;
let tx = self.ro_tx().await;
let db = tx.db();
let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
let mut counts: HashMap<Owner, (u64, u64, u64)> = HashMap::new();
let root = db.root.as_ref().ok_or(LbErrKind::RootNonexistent)?;
let mut document_ids = tree.descendants_using_links(root)?;
document_ids.extend(tree.non_deleted_pending_files(&self.keychain)?);
for id in document_ids {
if !tree.find(&id)?.is_document() {
continue;
}
let mut outgoing = HashSet::new();
let mut incoming = HashSet::new();
let mut visited = HashSet::new();
let mut current = id;
let mut deleted = false;
while visited.insert(current) {
let Some(file) = tree.maybe_find(¤t) else { break };
if file.explicitly_deleted() {
deleted = true;
break;
}
for access in file.user_access_keys() {
if access.deleted || access.encrypted_by == access.encrypted_for {
continue;
}
if access.encrypted_by == me {
outgoing.insert(Owner(access.encrypted_for));
} else if access.encrypted_for == me {
incoming.insert(Owner(access.encrypted_by));
}
}
if file.is_root() {
break;
}
current = *file.parent();
}
if deleted {
continue;
}
for user in outgoing.union(&incoming) {
let count = counts.entry(*user).or_default();
count.0 += u64::from(outgoing.contains(user));
count.1 += u64::from(incoming.contains(user));
count.2 += 1;
}
}
let mut contacts: Vec<_> = counts
.into_iter()
.filter_map(|(owner, (outgoing, incoming, total))| {
let username = db.pub_key_lookup.get(&owner)?.clone();
let trimmed = username.trim();
if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("<unknown>") {
return None;
}
Some(SharingContact {
username,
outgoing_file_count: outgoing,
incoming_file_count: incoming,
total_file_count: total,
})
})
.collect();
contacts.sort_by(|a, b| {
b.total_file_count
.cmp(&a.total_file_count)
.then_with(|| a.username.cmp(&b.username))
});
Ok(contacts)
}
#[instrument(level = "debug", skip(self))]
pub async fn share_file(&self, id: Uuid, username: &str, mode: ShareMode) -> LbResult<()> {
let account = self.get_account()?;
let username = username.to_lowercase();
let sharee = Owner(
self.client
.request(account, GetPublicKeyRequest { username: username.clone() })
.await
.map_err(LbErr::from)?
.key,
);
let mut tx = self.begin_tx().await;
let db = tx.db();
let mut tree = (&db.base_metadata)
.to_staged(&mut db.local_metadata)
.to_lazy();
db.pub_key_lookup.insert(sharee, username)?;
tree.add_share(id, sharee, mode, &self.keychain)?;
tx.end();
self.events.meta_changed(Actor::User(None));
Ok(())
}
#[instrument(level = "debug", skip(self))]
pub async fn get_pending_shares(&self) -> LbResult<Vec<File>> {
let tx = self.ro_tx().await;
let db = tx.db();
let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
let pending_roots = tree.pending_roots(&self.keychain)?.into_iter();
tree.decrypt_all(&self.keychain, pending_roots, &db.pub_key_lookup, false)
}
#[instrument(level = "debug", skip(self))]
pub async fn get_pending_share_files(&self) -> LbResult<Vec<File>> {
let tx = self.ro_tx().await;
let db = tx.db();
let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
let pending_files = tree.non_deleted_pending_files(&self.keychain)?.into_iter();
tree.decrypt_all(&self.keychain, pending_files, &db.pub_key_lookup, false)
}
#[instrument(level = "debug", skip(self))]
async fn delete_share(
&self, id: &Uuid, maybe_encrypted_for: Option<PublicKey>,
) -> LbResult<()> {
let mut tx = self.begin_tx().await;
let db = tx.db();
let mut tree = (&db.base_metadata)
.to_staged(&mut db.local_metadata)
.to_lazy();
tree.delete_share(id, maybe_encrypted_for, &self.keychain)?;
tx.end();
self.events.meta_changed(Actor::User(None));
Ok(())
}
#[instrument(level = "debug", skip(self))]
pub async fn known_usernames(&self) -> LbResult<Vec<String>> {
let db = self.ro_tx().await;
let db = db.db();
Ok(db
.pub_key_lookup
.iter()
.map(|(_, value)| value)
.cloned()
.collect())
}
#[instrument(level = "debug", skip(self))]
pub async fn username_exists(&self, username: &str) -> LbResult<bool> {
use crate::DEFAULT_API_LOCATION;
use crate::model::account::Account;
use crate::model::errors::LbErrKind;
let username = username.trim().to_lowercase();
if username.is_empty() {
return Ok(false);
}
if let Ok(known) = self.known_usernames().await {
if known.iter().any(|u| u.eq_ignore_ascii_case(&username)) {
return Ok(true);
}
}
let owned;
let account = match self.get_account() {
Ok(a) => a,
Err(_) => {
let api =
std::env::var("API_URL").unwrap_or_else(|_| DEFAULT_API_LOCATION.to_string());
owned = Account::new(String::new(), api);
&owned
}
};
match self
.client
.request(account, GetPublicKeyRequest { username })
.await
{
Ok(_) => Ok(true),
Err(e) => {
let err = LbErr::from(e);
match err.kind {
LbErrKind::AccountNonexistent | LbErrKind::UsernameNotFound => Ok(false),
_ => Err(err),
}
}
}
}
#[instrument(level = "debug", skip(self))]
pub async fn reject_share(&self, id: &Uuid) -> Result<(), LbErr> {
let pk = self.keychain.get_pk()?;
self.delete_share(id, Some(pk)).await
}
}