#[cfg(feature = "async")]
use crate::v1::download_dir_request_async;
use crate::{
queries, utils, v1,
v1::{
bool_from_int, bool_to_int, bool_to_string, crypto, download_dir, download_dir_request, files, fs,
response_payload, Backtrace, CryptoError, DownloadDirRequestPayload, FileProperties, FileStorageInfo,
HasFileMetadata, HasLocationName, HasPublicKey, HasUuid, ItemKind, LocationColor, LocationNameMetadata,
ParentOrNone, PlainResponsePayload,
},
FilenSettings, SettingsBundle,
};
use secstr::SecUtf8;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use snafu::{ResultExt, Snafu};
use std::cmp::Ordering;
use strum::{Display, EnumString};
use uuid::Uuid;
use super::FilenResponse;
type Result<T, E = Error> = std::result::Result<T, E>;
const SHARE_PATH: &str = "/v1/share";
const SHARE_DIR_STATUS_PATH: &str = "/v1/share/dir/status";
const USER_SHARED_IN_PATH: &str = "/v1/user/shared/in";
const USER_SHARED_OUT_PATH: &str = "/v1/user/shared/out";
const USER_SHARED_ITEM_RENAME_PATH: &str = "/v1/user/shared/item/rename";
const USER_SHARED_ITEM_STATUS_PATH: &str = "/v1/user/shared/item/status";
const USER_SHARED_ITEM_IN_REMOVE_PATH: &str = "/v1/user/shared/item/in/remove";
const USER_SHARED_ITEM_OUT_REMOVE_PATH: &str = "/v1/user/shared/item/out/remove";
#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("Filen cannot share file '{}': {}", uuid, message))]
CannotShareFile {
uuid: Uuid,
message: String,
backtrace: Backtrace,
},
#[snafu(display("Filen cannot share folder '{}': {}", uuid, message))]
CannotShareFolder {
uuid: Uuid,
message: String,
backtrace: Backtrace,
},
#[snafu(display("{}", source))]
CannotGetUserFolderContents { source: v1::Error },
#[snafu(display("Failed to decrypt file metadata '{}': {}", metadata, source))]
DecryptFileMetadataFailed { metadata: String, source: files::Error },
#[snafu(display("Failed to decrypt location name {}: {}", metadata, source))]
DecryptLocationNameFailed { metadata: String, source: fs::Error },
#[snafu(display("download_dir_request() failed: {}", source))]
DownloadDirRequestFailed { source: download_dir::Error },
#[snafu(display("Failed to encrypt file metadata '{}' using RSA: {}", metadata, source))]
EncryptFileMetadataRsaFailed { metadata: String, source: files::Error },
#[snafu(display("Failed to encrypt folder metadata '{}' using RSA: {}", metadata, source))]
EncryptFolderMetadataRsaFailed { metadata: String, source: crypto::Error },
#[snafu(display("{} query failed: {}", SHARE_DIR_STATUS_PATH, source))]
ShareDirStatusQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", SHARE_PATH, source))]
ShareQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", USER_SHARED_IN_PATH, source))]
UserSharedInQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", USER_SHARED_OUT_PATH, source))]
UserSharedOutQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", USER_SHARED_ITEM_IN_REMOVE_PATH, source))]
UserSharedItemInRemoveQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", USER_SHARED_ITEM_OUT_REMOVE_PATH, source))]
UserSharedItemOutRemoveQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", USER_SHARED_ITEM_RENAME_PATH, source))]
UserSharedItemRenameQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", USER_SHARED_ITEM_STATUS_PATH, source))]
UserSharedItemStatusQueryFailed { source: queries::Error },
}
#[derive(Clone, Copy, Debug, Deserialize, Display, EnumString, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "lowercase")]
#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
pub enum ShareTarget {
File,
Folder,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ShareRequestPayload<'share> {
#[serde(rename = "apiKey")]
pub api_key: &'share SecUtf8,
pub email: &'share str,
pub metadata: String,
pub parent: ParentOrNone,
#[serde(rename = "type")]
pub share_type: ShareTarget,
pub uuid: Uuid,
}
utils::display_from_json_with_lifetime!('share, ShareRequestPayload);
impl<'share> ShareRequestPayload<'share> {
pub fn from_file_data<T: HasFileMetadata + HasUuid>(
api_key: &'share SecUtf8,
file_data: &T,
parent: ParentOrNone,
receiver_email: &'share str,
receiver_public_key_bytes: &[u8],
master_keys: &[SecUtf8],
) -> Result<Self> {
let file_properties = file_data
.decrypt_file_metadata(master_keys)
.context(DecryptFileMetadataFailedSnafu {
metadata: file_data.file_metadata_ref().to_owned(),
})?;
Self::from_file_properties(
api_key,
*file_data.uuid_ref(),
&file_properties,
parent,
receiver_email,
receiver_public_key_bytes,
)
.context(EncryptFileMetadataRsaFailedSnafu {
metadata: file_data.file_metadata_ref().to_owned(),
})
}
pub fn from_file_properties(
api_key: &'share SecUtf8,
file_uuid: Uuid,
file_properties: &FileProperties,
parent: ParentOrNone,
email: &'share str,
rsa_public_key_bytes: &[u8],
) -> Result<Self, files::Error> {
let metadata = file_properties.to_metadata_rsa_string(rsa_public_key_bytes)?;
Ok(Self {
api_key,
email,
metadata,
parent,
share_type: ShareTarget::File,
uuid: file_uuid,
})
}
pub fn from_folder_data<T: HasLocationName + HasUuid>(
api_key: &'share SecUtf8,
folder_data: &T,
parent: ParentOrNone,
receiver_email: &'share str,
receiver_public_key_bytes: &[u8],
master_keys: &[SecUtf8],
) -> Result<Self> {
let folder_name = folder_data
.decrypt_name_metadata(master_keys)
.context(DecryptLocationNameFailedSnafu {
metadata: folder_data.name_metadata_ref().to_owned(),
})?;
Self::from_folder_name(
api_key,
*folder_data.uuid_ref(),
&folder_name,
parent,
receiver_email,
receiver_public_key_bytes,
)
.context(EncryptFolderMetadataRsaFailedSnafu {
metadata: folder_data.name_metadata_ref().to_owned(),
})
}
pub fn from_folder_name(
api_key: &'share SecUtf8,
folder_uuid: Uuid,
folder_name: &str,
parent: ParentOrNone,
email: &'share str,
rsa_public_key_bytes: &[u8],
) -> Result<Self, CryptoError> {
let metadata = LocationNameMetadata::encrypt_name_to_metadata_rsa(folder_name, rsa_public_key_bytes)?;
Ok(Self {
api_key,
email,
metadata,
parent,
share_type: ShareTarget::Folder,
uuid: folder_uuid,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ShareDirStatusRequestPayload<'share_dir_status> {
#[serde(rename = "apiKey")]
pub api_key: &'share_dir_status SecUtf8,
pub uuid: Uuid,
}
utils::display_from_json_with_lifetime!('share_dir_status, ShareDirStatusRequestPayload);
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserEmailWithPublicKey {
pub email: String,
#[serde(rename = "publicKey")]
pub public_key: String,
}
utils::display_from_json!(UserEmailWithPublicKey);
impl HasPublicKey for UserEmailWithPublicKey {
fn public_key_ref(&self) -> Option<&str> {
Some(&self.public_key)
}
}
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ShareDirStatusResponseData {
pub sharing: bool,
#[serde(default)]
pub users: Vec<UserEmailWithPublicKey>,
}
utils::display_from_json!(ShareDirStatusResponseData);
response_payload!(
ShareDirStatusResponsePayload<ShareDirStatusResponseData>
);
#[derive(Clone, Copy, Debug, Deserialize, Display, EnumString, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
pub enum SharedContentKind {
SharedIn,
SharedOut,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct UserSharedInRequestPayload<'user_shared_in> {
#[serde(rename = "apiKey")]
pub api_key: &'user_shared_in SecUtf8,
pub uuid: SharedContentKind,
pub folders: String,
pub page: i32,
#[serde(serialize_with = "bool_to_string")]
pub app: bool,
}
utils::display_from_json_with_lifetime!('user_shared_in, UserSharedInRequestPayload);
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct UserSharedOutRequestPayload<'user_shared_out> {
#[serde(rename = "apiKey")]
pub api_key: &'user_shared_out SecUtf8,
pub uuid: SharedContentKind,
pub folders: String,
pub page: i32,
#[serde(rename = "receiverId")]
pub receiver_id: u64,
#[serde(serialize_with = "bool_to_string")]
pub app: bool,
}
utils::display_from_json_with_lifetime!('user_shared_out, UserSharedOutRequestPayload);
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserSharedFile {
pub uuid: Uuid,
pub metadata: String,
#[serde(rename = "type")]
pub item_type: ItemKind,
#[serde(flatten)]
pub storage: FileStorageInfo,
pub version: u32,
pub parent: Option<Uuid>,
#[serde(rename = "sharerEmail")]
pub sharer_email: Option<String>,
#[serde(rename = "sharerId")]
pub sharer_id: Option<u32>,
#[serde(rename = "receiverEmail")]
pub receiver_email: Option<String>,
#[serde(rename = "receiverId")]
pub receiver_id: Option<u32>,
#[serde(
rename = "writeAccess",
deserialize_with = "bool_from_int",
serialize_with = "bool_to_int"
)]
pub write_access: bool,
pub timestamp: u64,
}
utils::display_from_json!(UserSharedFile);
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserSharedFolder {
pub uuid: Uuid,
pub metadata: String,
#[serde(rename = "type")]
pub item_type: ItemKind,
pub bucket: Option<String>,
pub region: Option<String>,
pub chunks: Option<u32>,
pub parent: Option<Uuid>,
#[serde(rename = "sharerEmail")]
pub sharer_email: Option<String>,
#[serde(rename = "sharerId")]
pub sharer_id: Option<u32>,
#[serde(rename = "receiverEmail")]
pub receiver_email: Option<String>,
#[serde(rename = "receiverId")]
pub receiver_id: Option<u32>,
#[serde(
rename = "writeAccess",
deserialize_with = "bool_from_int",
serialize_with = "bool_to_int"
)]
pub write_access: bool,
pub color: Option<LocationColor>,
pub timestamp: u64,
#[serde(deserialize_with = "bool_from_int", serialize_with = "bool_to_int")]
pub is_default: bool,
#[serde(deserialize_with = "bool_from_int", serialize_with = "bool_to_int")]
pub is_sync: bool,
}
utils::display_from_json!(UserSharedFolder);
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserSharedFolderInfo {
pub uuid: Uuid,
#[serde(rename = "name")]
pub name_metadata: String,
pub color: Option<LocationColor>,
}
utils::display_from_json!(UserSharedFolderInfo);
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserSharedInOrOutResponseData {
pub uploads: Vec<UserSharedFile>,
pub folders: Vec<UserSharedFolder>,
#[serde(rename = "foldersInfo")]
pub folders_info: Vec<UserSharedFolderInfo>,
#[serde(rename = "totalUploads")]
pub total_uploads: u64,
#[serde(rename = "perPage")]
pub per_page: u32,
pub page: u32,
}
utils::display_from_json!(UserSharedInOrOutResponseData);
response_payload!(
UserSharedInOrOutResponsePayload<UserSharedInOrOutResponseData>
);
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct UserSharedItemRenameRequestPayload<'user_shared_item_rename> {
#[serde(rename = "apiKey")]
pub api_key: &'user_shared_item_rename SecUtf8,
pub uuid: Uuid,
#[serde(rename = "receiverId")]
pub receiver_id: u64,
pub metadata: String,
}
utils::display_from_json_with_lifetime!('user_shared_item_rename, UserSharedItemRenameRequestPayload);
impl<'user_shared_item_rename> UserSharedItemRenameRequestPayload<'user_shared_item_rename> {
pub fn from_file_properties(
api_key: &'user_shared_item_rename SecUtf8,
receiver_id: u64,
file_uuid: Uuid,
file_properties: &FileProperties,
rsa_public_key_bytes: &[u8],
) -> Result<Self, files::Error> {
let metadata = file_properties.to_metadata_rsa_string(rsa_public_key_bytes)?;
Ok(Self {
api_key,
uuid: file_uuid,
receiver_id,
metadata,
})
}
pub fn from_folder_name(
api_key: &'user_shared_item_rename SecUtf8,
receiver_id: u64,
folder_uuid: Uuid,
folder_name: &str,
rsa_public_key_bytes: &[u8],
) -> Result<Self, CryptoError> {
let metadata = LocationNameMetadata::encrypt_name_to_metadata_rsa(folder_name, rsa_public_key_bytes)?;
Ok(Self {
api_key,
uuid: folder_uuid,
receiver_id,
metadata,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct UserSharedItemRemoveRequestPayload<'user_shared_item_remove> {
#[serde(rename = "apiKey")]
pub api_key: &'user_shared_item_remove SecUtf8,
#[serde(rename = "receiverId")]
pub receiver_id: u64,
pub uuid: Uuid,
}
utils::display_from_json_with_lifetime!('user_shared_item_remove, UserSharedItemRemoveRequestPayload);
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct UserSharedItemStatusRequestPayload<'user_shared_item_status> {
#[serde(rename = "apiKey")]
pub api_key: &'user_shared_item_status SecUtf8,
pub uuid: Uuid,
}
utils::display_from_json_with_lifetime!('user_shared_item_status, UserSharedItemStatusRequestPayload);
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserIdWithPublicKey {
pub id: u32,
#[serde(rename = "publicKey")]
pub public_key: String,
}
utils::display_from_json!(UserIdWithPublicKey);
impl HasPublicKey for UserIdWithPublicKey {
fn public_key_ref(&self) -> Option<&str> {
Some(&self.public_key)
}
}
impl Ord for UserIdWithPublicKey {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl PartialOrd for UserIdWithPublicKey {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserSharedItemStatusResponseData {
pub sharing: bool,
#[serde(default)]
pub users: Vec<UserIdWithPublicKey>,
}
utils::display_from_json!(UserSharedItemStatusResponseData);
response_payload!(
UserSharedItemStatusResponsePayload<UserSharedItemStatusResponseData>
);
pub fn share_dir_status_request(
payload: &ShareDirStatusRequestPayload,
filen_settings: &FilenSettings,
) -> Result<ShareDirStatusResponsePayload> {
queries::query_filen_api(SHARE_DIR_STATUS_PATH, payload, filen_settings).context(ShareDirStatusQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn share_dir_status_request_async(
payload: &ShareDirStatusRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<ShareDirStatusResponsePayload> {
queries::query_filen_api_async(SHARE_DIR_STATUS_PATH, payload, filen_settings)
.await
.context(ShareDirStatusQueryFailedSnafu {})
}
pub fn share_request(payload: &ShareRequestPayload, filen_settings: &FilenSettings) -> Result<PlainResponsePayload> {
queries::query_filen_api(SHARE_PATH, payload, filen_settings).context(ShareQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn share_request_async(
payload: &ShareRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api_async(SHARE_PATH, payload, filen_settings)
.await
.context(ShareQueryFailedSnafu {})
}
pub fn user_shared_in_request(
payload: &UserSharedInRequestPayload,
filen_settings: &FilenSettings,
) -> Result<UserSharedInOrOutResponsePayload> {
queries::query_filen_api(USER_SHARED_IN_PATH, payload, filen_settings).context(UserSharedInQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn user_shared_in_request_async(
payload: &UserSharedInRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<UserSharedInOrOutResponsePayload> {
queries::query_filen_api_async(USER_SHARED_IN_PATH, payload, filen_settings)
.await
.context(UserSharedInQueryFailedSnafu {})
}
pub fn user_shared_out_request(
payload: &UserSharedOutRequestPayload,
filen_settings: &FilenSettings,
) -> Result<UserSharedInOrOutResponsePayload> {
queries::query_filen_api(USER_SHARED_OUT_PATH, payload, filen_settings).context(UserSharedOutQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn user_shared_out_request_async(
payload: &UserSharedOutRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<UserSharedInOrOutResponsePayload> {
queries::query_filen_api_async(USER_SHARED_OUT_PATH, payload, filen_settings)
.await
.context(UserSharedOutQueryFailedSnafu {})
}
pub fn user_shared_item_in_remove_request(
payload: &UserSharedItemRemoveRequestPayload,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api(USER_SHARED_ITEM_IN_REMOVE_PATH, payload, filen_settings)
.context(UserSharedItemInRemoveQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn user_shared_item_in_rename_request_async(
payload: &UserSharedItemRemoveRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api_async(USER_SHARED_ITEM_IN_REMOVE_PATH, payload, filen_settings)
.await
.context(UserSharedItemInRemoveQueryFailedSnafu {})
}
pub fn user_shared_item_out_remove_request(
payload: &UserSharedItemRemoveRequestPayload,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api(USER_SHARED_ITEM_OUT_REMOVE_PATH, payload, filen_settings)
.context(UserSharedItemOutRemoveQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn user_shared_item_out_remove_request_async(
payload: &UserSharedItemRemoveRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api_async(USER_SHARED_ITEM_OUT_REMOVE_PATH, payload, filen_settings)
.await
.context(UserSharedItemOutRemoveQueryFailedSnafu {})
}
pub fn user_shared_item_rename_request(
payload: &UserSharedItemRenameRequestPayload,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api(USER_SHARED_ITEM_RENAME_PATH, payload, filen_settings)
.context(UserSharedItemRenameQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn user_shared_item_rename_request_async(
payload: &UserSharedItemRenameRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api_async(USER_SHARED_ITEM_RENAME_PATH, payload, filen_settings)
.await
.context(UserSharedItemRenameQueryFailedSnafu {})
}
pub fn user_shared_item_status_request(
payload: &UserSharedItemStatusRequestPayload,
filen_settings: &FilenSettings,
) -> Result<UserSharedItemStatusResponsePayload> {
queries::query_filen_api(USER_SHARED_ITEM_STATUS_PATH, payload, filen_settings)
.context(UserSharedItemStatusQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn user_shared_item_status_request_async(
payload: &UserSharedItemStatusRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<UserSharedItemStatusResponsePayload> {
queries::query_filen_api_async(USER_SHARED_ITEM_STATUS_PATH, payload, filen_settings)
.await
.context(UserSharedItemStatusQueryFailedSnafu {})
}
pub fn share_file<T: HasFileMetadata + HasUuid>(
api_key: &SecUtf8,
file_data: &T,
parent: ParentOrNone,
receiver_email: &str,
receiver_public_key_bytes: &[u8],
master_keys: &[SecUtf8],
filen_settings: &FilenSettings,
) -> Result<String> {
let file_properties = file_data
.decrypt_file_metadata(master_keys)
.context(DecryptFileMetadataFailedSnafu {
metadata: file_data.file_metadata_ref().to_owned(),
})?;
let share_payload = ShareRequestPayload::from_file_properties(
api_key,
*file_data.uuid_ref(),
&file_properties,
parent,
receiver_email,
receiver_public_key_bytes,
)
.context(EncryptFileMetadataRsaFailedSnafu {
metadata: file_data.file_metadata_ref().to_owned(),
})?;
let response = share_request(&share_payload, filen_settings)?;
if response.status {
Ok(response.message.unwrap_or_default())
} else {
CannotShareFileSnafu {
uuid: *file_data.uuid_ref(),
message: format!("{:?}", response.message),
}
.fail()
}
}
#[cfg(feature = "async")]
pub async fn share_file_async<T: HasFileMetadata + HasUuid + Sync>(
api_key: &SecUtf8,
file_data: &T,
parent: ParentOrNone,
receiver_email: &str,
receiver_public_key_bytes: &[u8],
master_keys: &[SecUtf8],
filen_settings: &FilenSettings,
) -> Result<String> {
let share_payload = ShareRequestPayload::from_file_data(
api_key,
file_data,
parent,
receiver_email,
receiver_public_key_bytes,
master_keys,
)?;
let response = share_request_async(&share_payload, filen_settings).await?;
if response.status {
Ok(response.message.unwrap_or_default())
} else {
CannotShareFileSnafu {
uuid: *file_data.uuid_ref(),
message: format!("{:?}", response.message),
}
.fail()
}
}
pub fn share_folder<T: HasLocationName + HasUuid>(
api_key: &SecUtf8,
folder_data: &T,
parent: ParentOrNone,
receiver_email: &str,
receiver_public_key_bytes: &[u8],
master_keys: &[SecUtf8],
filen_settings: &FilenSettings,
) -> Result<String> {
let share_payload = ShareRequestPayload::from_folder_data(
api_key,
folder_data,
parent,
receiver_email,
receiver_public_key_bytes,
master_keys,
)?;
let response = share_request(&share_payload, filen_settings)?;
if response.status {
Ok(response.message.unwrap_or_default())
} else {
CannotShareFolderSnafu {
uuid: *folder_data.uuid_ref(),
message: format!("{:?}", response.message),
}
.fail()
}
}
#[cfg(feature = "async")]
pub async fn share_folder_async<T: HasLocationName + HasUuid + Sync>(
api_key: &SecUtf8,
folder_data: &T,
parent: ParentOrNone,
receiver_email: &str,
receiver_public_key_bytes: &[u8],
master_keys: &[SecUtf8],
filen_settings: &FilenSettings,
) -> Result<String> {
let share_payload = ShareRequestPayload::from_folder_data(
api_key,
folder_data,
parent,
receiver_email,
receiver_public_key_bytes,
master_keys,
)?;
let response = share_request_async(&share_payload, filen_settings).await?;
if response.status {
Ok(response.message.unwrap_or_default())
} else {
CannotShareFolderSnafu {
uuid: *folder_data.uuid_ref(),
message: format!("{:?}", response.message),
}
.fail()
}
}
pub fn share_folder_recursively(
api_key: &SecUtf8,
folder_uuid: Uuid,
receiver_email: &str,
receiver_public_key_bytes: &[u8],
master_keys: &[SecUtf8],
settings: &SettingsBundle,
) -> Result<()> {
let content_payload = DownloadDirRequestPayload {
api_key,
uuid: folder_uuid,
};
let contents_response = settings
.retry
.call(|| download_dir_request(&content_payload, &settings.filen))
.context(DownloadDirRequestFailedSnafu {})?;
let contents = contents_response
.data_ref_or_err()
.context(CannotGetUserFolderContentsSnafu {})?;
contents
.folders
.iter()
.map(|folder| {
settings.retry.call(|| {
share_folder(
api_key,
folder,
folder.parent.as_parent_or_none(),
receiver_email,
receiver_public_key_bytes,
master_keys,
&settings.filen,
)
.map(|_| ())
})
})
.collect::<Result<Vec<()>>>()?;
contents
.files
.iter()
.map(|file| {
settings.retry.call(|| {
share_file(
api_key,
file,
ParentOrNone::Folder(file.parent),
receiver_email,
receiver_public_key_bytes,
master_keys,
&settings.filen,
)
.map(|_| ())
})
})
.collect::<Result<Vec<()>>>()?;
Ok(())
}
#[cfg(feature = "async")]
pub async fn share_folder_recursively_async(
api_key: &SecUtf8,
folder_uuid: Uuid,
receiver_email: &str,
receiver_public_key_bytes: &[u8],
master_keys: &[SecUtf8],
settings: &SettingsBundle,
) -> Result<()> {
let content_payload = DownloadDirRequestPayload {
api_key,
uuid: folder_uuid,
};
let contents_response = settings
.retry
.call_async(|| download_dir_request_async(&content_payload, &settings.filen))
.await
.context(DownloadDirRequestFailedSnafu {})?;
let contents = contents_response
.data_ref_or_err()
.context(CannotGetUserFolderContentsSnafu {})?;
let folder_futures = contents.folders.iter().map(|folder| {
settings.retry.call_async(move || async move {
share_folder_async(
api_key,
folder,
folder.parent.as_parent_or_none(),
receiver_email,
receiver_public_key_bytes,
master_keys,
&settings.filen,
)
.await
.map(|_| ())
})
});
futures::future::try_join_all(folder_futures).await?;
let file_futures = contents.files.iter().map(|file| {
settings.retry.call_async(move || async move {
share_file_async(
api_key,
file,
ParentOrNone::Folder(file.parent),
receiver_email,
receiver_public_key_bytes,
master_keys,
&settings.filen,
)
.await
.map(|_| ())
})
});
futures::future::try_join_all(file_futures).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::validate_contract;
#[cfg(feature = "async")]
use crate::test_utils::validate_contract_async;
use once_cell::sync::Lazy;
use secstr::SecUtf8;
static API_KEY: Lazy<SecUtf8> =
Lazy::new(|| SecUtf8::from("bYZmrwdVEbHJSqeA1RfnPtKiBcXzUpRdKGRkjw9m1o1eqSGP1s6DM11CDnklpFq6"));
#[test]
fn share_dir_status_request_should_have_proper_contract_for_shared_folder() {
let request_payload = ShareDirStatusRequestPayload {
api_key: &API_KEY,
uuid: Uuid::nil(),
};
validate_contract(
SHARE_DIR_STATUS_PATH,
request_payload,
"tests/resources/responses/share_dir_status.json",
|request_payload, filen_settings| share_dir_status_request(&request_payload, &filen_settings),
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn share_dir_status_request_async_should_have_proper_contract_for_shared_folder() {
let request_payload = ShareDirStatusRequestPayload {
api_key: &API_KEY,
uuid: Uuid::nil(),
};
validate_contract_async(
SHARE_DIR_STATUS_PATH,
request_payload,
"tests/resources/responses/share_dir_status.json",
|request_payload, filen_settings| async move {
share_dir_status_request_async(&request_payload, &filen_settings).await
},
)
.await;
}
#[test]
fn share_dir_status_request_should_have_proper_contract_for_non_shared_folder() {
let request_payload = ShareDirStatusRequestPayload {
api_key: &API_KEY,
uuid: Uuid::nil(),
};
validate_contract(
SHARE_DIR_STATUS_PATH,
request_payload,
"tests/resources/responses/share_dir_status_not_shared.json",
|request_payload, filen_settings| share_dir_status_request(&request_payload, &filen_settings),
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn share_dir_status_request_async_should_have_proper_contract_for_non_shared_folder() {
let request_payload = ShareDirStatusRequestPayload {
api_key: &API_KEY,
uuid: Uuid::nil(),
};
validate_contract_async(
SHARE_DIR_STATUS_PATH,
request_payload,
"tests/resources/responses/share_dir_status_not_shared.json",
|request_payload, filen_settings| async move {
share_dir_status_request_async(&request_payload, &filen_settings).await
},
)
.await;
}
#[test]
fn user_shared_in_request_should_have_proper_contract() {
let request_payload = UserSharedInRequestPayload {
api_key: &API_KEY,
uuid: SharedContentKind::SharedIn,
folders: "[\"5c86494b-36ec-4d39-a839-9f391474ad00\"]".to_owned(),
page: 1,
app: true,
};
validate_contract(
USER_SHARED_IN_PATH,
request_payload,
"tests/resources/responses/user_shared_in.json",
|request_payload, filen_settings| user_shared_in_request(&request_payload, &filen_settings),
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn user_shared_in_request_async_should_have_proper_contract() {
let request_payload = UserSharedInRequestPayload {
api_key: &API_KEY,
uuid: SharedContentKind::SharedIn,
folders: "[\"5c86494b-36ec-4d39-a839-9f391474ad00\"]".to_owned(),
page: 1,
app: true,
};
validate_contract_async(
USER_SHARED_IN_PATH,
request_payload,
"tests/resources/responses/user_shared_in.json",
|request_payload, filen_settings| async move {
user_shared_in_request_async(&request_payload, &filen_settings).await
},
)
.await;
}
#[test]
fn user_shared_out_request_should_have_proper_contract() {
let request_payload = UserSharedOutRequestPayload {
api_key: &API_KEY,
uuid: SharedContentKind::SharedOut,
folders: "[\"5c86494b-36ec-4d39-a839-9f391474ad00\"]".to_owned(),
page: 1,
receiver_id: 4947,
app: true,
};
validate_contract(
USER_SHARED_OUT_PATH,
request_payload,
"tests/resources/responses/user_shared_out.json",
|request_payload, filen_settings| user_shared_out_request(&request_payload, &filen_settings),
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn user_shared_out_request_async_should_have_proper_contract() {
let request_payload = UserSharedOutRequestPayload {
api_key: &API_KEY,
uuid: SharedContentKind::SharedOut,
folders: "[\"5c86494b-36ec-4d39-a839-9f391474ad00\"]".to_owned(),
page: 1,
receiver_id: 4947,
app: true,
};
validate_contract_async(
USER_SHARED_OUT_PATH,
request_payload,
"tests/resources/responses/user_shared_out.json",
|request_payload, filen_settings| async move {
user_shared_out_request_async(&request_payload, &filen_settings).await
},
)
.await;
}
#[test]
fn user_shared_item_status_request_should_have_proper_contract_for_shared_folder() {
let request_payload = UserSharedItemStatusRequestPayload {
api_key: &API_KEY,
uuid: Uuid::nil(),
};
validate_contract(
USER_SHARED_ITEM_STATUS_PATH,
request_payload,
"tests/resources/responses/user_shared_item_status.json",
|request_payload, filen_settings| user_shared_item_status_request(&request_payload, &filen_settings),
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn user_shared_item_status_request_async_should_have_proper_contract_for_shared_folder() {
let request_payload = UserSharedItemStatusRequestPayload {
api_key: &API_KEY,
uuid: Uuid::nil(),
};
validate_contract_async(
USER_SHARED_ITEM_STATUS_PATH,
request_payload,
"tests/resources/responses/user_shared_item_status.json",
|request_payload, filen_settings| async move {
user_shared_item_status_request_async(&request_payload, &filen_settings).await
},
)
.await;
}
}