use super::{
file_system::{normalise_path_separator, upload_file_to_net},
metadata::FileMeta,
ProcessedFiles, RealPath,
};
use crate::{app::consts::*, Error, Result, Safe, XorUrl};
use log::{debug, info};
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, fs, path::Path};
pub type FilesMap = BTreeMap<String, FileInfo>;
pub type FileInfo = BTreeMap<String, String>;
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Serialize, Deserialize)]
pub enum FilesMapChange {
Added(XorUrl),
Updated(XorUrl),
Removed(XorUrl),
Failed(String),
}
impl FilesMapChange {
pub fn is_success(&self) -> bool {
match self {
Self::Added(_) | Self::Updated(_) | Self::Removed(_) => true,
Self::Failed(_) => false,
}
}
pub fn link(&self) -> Option<&XorUrl> {
match self {
Self::Added(link) | Self::Updated(link) | Self::Removed(link) => Some(link),
Self::Failed(_) => None,
}
}
pub fn is_added(&self) -> bool {
match self {
Self::Added(_) => true,
Self::Updated(_) | Self::Removed(_) | Self::Failed(_) => false,
}
}
pub fn is_updated(&self) -> bool {
match self {
Self::Updated(_) => true,
Self::Added(_) | Self::Removed(_) | Self::Failed(_) => false,
}
}
pub fn is_removed(&self) -> bool {
match self {
Self::Removed(_) => true,
Self::Added(_) | Self::Updated(_) | Self::Failed(_) => false,
}
}
}
pub trait GetAttr {
fn getattr(&self, key: &str) -> Result<&str>;
}
impl GetAttr for FileInfo {
fn getattr(&self, key: &str) -> Result<&str> {
match self.get(key) {
Some(v) => Ok(v),
None => Err(Error::EntryNotFound(format!("key not found: {key}"))),
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn add_or_update_file_item(
safe: &Safe,
file_name: &Path,
file_name_for_map: &str,
file_path: &Path,
file_meta: &FileMeta,
file_link: Option<&str>,
name_exists: bool,
files_map: &mut FilesMap,
processed_files: &mut ProcessedFiles,
) -> bool {
match gen_new_file_item(safe, file_path, file_meta, file_link).await {
Ok(new_file_item) => {
let xorurl = new_file_item
.get(PREDICATE_LINK)
.unwrap_or(&String::default())
.to_string();
let file_item_change = if name_exists {
FilesMapChange::Updated(xorurl)
} else {
FilesMapChange::Added(xorurl)
};
debug!("New FileInfo item: {:?}", new_file_item);
debug!("New FileInfo item inserted as {:?}", file_name);
files_map.insert(file_name_for_map.to_string(), new_file_item);
processed_files.insert(file_name.to_path_buf(), file_item_change);
true
}
Err(err) => {
info!("Skipping file \"{}\": {:?}", file_link.unwrap_or(""), err);
processed_files.insert(
file_name.to_path_buf(),
FilesMapChange::Failed(format!("{err}")),
);
false
}
}
}
async fn gen_new_file_item(
safe: &Safe,
file_path: &Path,
file_meta: &FileMeta,
link: Option<&str>, ) -> Result<FileInfo> {
let mut file_item = file_meta.to_file_item();
if file_meta.is_file() {
let xorurl = match link {
None => upload_file_to_net(safe, file_path).await?,
Some(link) => link.to_string(),
};
file_item.insert(PREDICATE_LINK.to_string(), xorurl);
} else if file_meta.is_symlink() {
let result = fs::metadata(file_path);
let symlink_target_type = match result {
Ok(meta) => {
if meta.is_dir() {
"dir"
} else {
"file"
}
}
Err(_) => "unknown", };
let target_path = match link {
Some(target) => target.to_string(),
None => {
let target_path = fs::read_link(file_path).map_err(|e| {
Error::FileSystemError(format!(
"Unable to read link: {}. {:#?}",
file_path.display(),
e
))
})?;
normalise_path_separator(&target_path.display().to_string())
}
};
file_item.insert("symlink_target".to_string(), target_path);
file_item.insert(
"symlink_target_type".to_string(),
symlink_target_type.to_string(),
);
}
Ok(file_item)
}
pub(crate) fn file_map_for_path(files_map: FilesMap, path: &str) -> Result<FilesMap> {
let realpath = files_map.realpath(path)?;
if let Some(file_info) = files_map.get(&realpath) {
let file_type = file_info.get("type").ok_or_else(|| {
Error::ContentError(format!(
"corrupt FileInfo: missing a \"type\" property at: {path}"
))
})?;
if FileMeta::filetype_is_symlink(file_type) {
return Err(Error::ContentError(format!(
"symlink should not be present in resolved real path: {realpath}"
)));
} else if FileMeta::filetype_is_file(file_type) {
return Ok(files_map);
}
}
let chrooted_file_map = filesmap_chroot(&realpath, &files_map)?;
Ok(chrooted_file_map)
}
pub(crate) fn get_file_link_and_metadata(
files_map: &FilesMap,
path: &str,
) -> Result<(Option<String>, Option<FileInfo>)> {
if path.is_empty() {
return Ok((None, None));
}
let realpath = files_map.realpath(path)?;
if let Some(file_info) = files_map.get(&realpath) {
let file_type = file_info.get("type").ok_or_else(|| {
Error::ContentError(format!(
"corrupt FileInfo: missing a \"type\" property at: {path}",
))
})?;
if FileMeta::filetype_is_file(file_type) {
let link = file_info.get("link").ok_or_else(|| {
Error::ContentError(format!(
"corrupt FileInfo: missing a \"link\" property at path: {path}",
))
})?;
let mut enriched_file_info = (*file_info).clone();
if let Some(filename) = Path::new(&path).file_name() {
if let Some(name) = filename.to_str() {
enriched_file_info.insert("name".to_string(), name.to_owned());
}
}
return Ok((Some(link.clone()), Some(enriched_file_info)));
}
}
Ok((None, None))
}
fn filesmap_chroot(urlpath: &str, files_map: &FilesMap) -> Result<FilesMap> {
let mut filtered_filesmap = FilesMap::default();
let folder_path = if !urlpath.ends_with('/') {
format!("{urlpath}/")
} else {
urlpath.to_string()
};
for (filepath, fileitem) in files_map.iter() {
if filepath.starts_with(&folder_path) {
let mut new_path = filepath.clone();
new_path.replace_range(..folder_path.len(), "");
filtered_filesmap.insert(new_path, fileitem.clone());
}
}
if filtered_filesmap.is_empty() {
Err(Error::ContentError(format!(
"no data found for path: {folder_path}"
)))
} else {
Ok(filtered_filesmap)
}
}