use super::constants::{
CONTENT_ADDED_SIGN, CONTENT_DELETED_SIGN, CONTENT_ERROR_SIGN, CONTENT_UPDATED_SIGN,
FAKE_RDF_PREDICATE_CREATED, FAKE_RDF_PREDICATE_LINK, FAKE_RDF_PREDICATE_MODIFIED,
FAKE_RDF_PREDICATE_SIZE, FAKE_RDF_PREDICATE_TYPE,
};
use super::helpers::{gen_timestamp_nanos, gen_timestamp_secs};
use super::xorurl::{SafeContentType, SafeDataType};
use super::{Error, ResultReturn, Safe, SafeApp, XorUrl, XorUrlEncoder};
use log::{debug, info, warn};
use mime_guess;
use relative_path::RelativePath;
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
use walkdir::{DirEntry, WalkDir};
pub type FileItem = BTreeMap<String, String>;
pub type FilesMap = BTreeMap<String, FileItem>;
pub type ProcessedFiles = BTreeMap<String, (String, String)>;
const FILES_CONTAINER_TYPE_TAG: u64 = 1_100;
const ERROR_MSG_NO_FILES_CONTAINER_FOUND: &str = "No FilesContainer found at this address";
const MAX_RECURSIVE_DEPTH: usize = 10_000;
#[allow(dead_code)]
impl Safe {
pub fn files_container_create(
&mut self,
location: &str,
dest: Option<&str>,
recursive: bool,
dry_run: bool,
) -> ResultReturn<(XorUrl, ProcessedFiles, FilesMap)> {
let processed_files = file_system_dir_walk(self, location, recursive, !dry_run)?;
let files_map = files_map_create(&processed_files, location, dest)?;
let xorurl = if dry_run {
"".to_string()
} else {
let serialised_files_map = serde_json::to_string(&files_map).map_err(|err| {
Error::Unexpected(format!(
"Couldn't serialise the FilesMap generated: {:?}",
err
))
})?;
let now = gen_timestamp_nanos();
let files_container_data = vec![(
now.into_bytes().to_vec(),
serialised_files_map.as_bytes().to_vec(),
)];
let xorname = self.safe_app.put_seq_append_only_data(
files_container_data,
None,
FILES_CONTAINER_TYPE_TAG,
None,
)?;
XorUrlEncoder::encode(
xorname,
FILES_CONTAINER_TYPE_TAG,
SafeDataType::PublishedSeqAppendOnlyData,
SafeContentType::FilesContainer,
None,
None,
None,
&self.xorurl_base,
)?
};
Ok((xorurl, processed_files, files_map))
}
pub fn files_container_get(&self, url: &str) -> ResultReturn<(u64, FilesMap)> {
debug!("Getting files container from: {:?}", url);
let (xorurl_encoder, _) = self.parse_and_resolve_url(url)?;
let data = xorurl_encoder.content_version().map_or_else(
|| {
self.safe_app.get_latest_seq_append_only_data(
xorurl_encoder.xorname(),
FILES_CONTAINER_TYPE_TAG,
)
},
|content_version| {
let (key, value) = self
.safe_app
.get_seq_append_only_data(
xorurl_encoder.xorname(),
FILES_CONTAINER_TYPE_TAG,
content_version,
)
.map_err(|_| {
Error::VersionNotFound(format!(
"Version '{}' is invalid for FilesContainer found at \"{}\"",
content_version, url,
))
})?;
Ok((content_version, (key, value)))
},
);
match data {
Ok((version, (_key, value))) => {
debug!("Files map retrieved.... v{:?}", &version);
let files_map = serde_json::from_str(&String::from_utf8_lossy(&value.as_slice()))
.map_err(|err| {
Error::ContentError(format!(
"Couldn't deserialise the FilesMap stored in the FilesContainer: {:?}",
err
))
})?;
Ok((version, files_map))
}
Err(Error::EmptyContent(_)) => {
warn!("FilesContainer found at \"{:?}\" was empty", url);
Ok((0, FilesMap::default()))
}
Err(Error::ContentNotFound(_)) => Err(Error::ContentNotFound(
ERROR_MSG_NO_FILES_CONTAINER_FOUND.to_string(),
)),
Err(Error::VersionNotFound(msg)) => Err(Error::VersionNotFound(msg)),
Err(err) => Err(Error::NetDataError(format!(
"Failed to get current version: {}",
err
))),
}
}
pub fn files_container_sync(
&mut self,
location: &str,
url: &str,
recursive: bool,
delete: bool,
update_nrs: bool,
dry_run: bool,
) -> ResultReturn<(u64, ProcessedFiles, FilesMap)> {
if delete && !recursive {
return Err(Error::InvalidInput(
"'delete' is not allowed if --recursive is not set".to_string(),
));
}
let xorurl_encoder = Safe::parse_url(url)?;
if xorurl_encoder.content_version().is_some() {
return Err(Error::InvalidInput(format!(
"The target URL cannot cannot contain a version: {}",
url
)));
};
if update_nrs && xorurl_encoder.content_type() != SafeContentType::NrsMapContainer {
return Err(Error::InvalidInput(
"'update-nrs' is not allowed since the URL provided is not an NRS URL".to_string(),
));
}
let (mut xorurl_encoder, is_nrs_resolved) = self.parse_and_resolve_url(url)?;
if is_nrs_resolved {
xorurl_encoder.set_content_version(None);
}
let (current_version, current_files_map): (u64, FilesMap) =
self.files_container_get(&xorurl_encoder.to_string()?)?;
let processed_files = file_system_dir_walk(self, location, recursive, false)?;
let dest_path = Some(xorurl_encoder.path());
let (processed_files, new_files_map, success_count): (ProcessedFiles, FilesMap, u64) =
files_map_sync(
self,
current_files_map,
location,
processed_files,
dest_path,
delete,
!dry_run,
false,
true,
)?;
let version = self.append_version_to_nrs_map_container(
success_count,
current_version,
&new_files_map,
url,
xorurl_encoder,
dry_run,
update_nrs,
)?;
Ok((version, processed_files, new_files_map))
}
pub fn files_container_add(
&mut self,
source_file: &str,
url: &str,
force: bool,
update_nrs: bool,
dry_run: bool,
) -> ResultReturn<(u64, ProcessedFiles, FilesMap)> {
let (xorurl_encoder, current_version, current_files_map) =
validate_files_add_params(self, source_file, url, update_nrs)?;
let dest_path = xorurl_encoder.path();
let (processed_files, new_files_map, success_count) = if source_file.starts_with("safe://")
{
files_map_add_link(self, current_files_map, source_file, dest_path, force)?
} else {
let processed_files = file_system_single_file(self, source_file, false)?;
files_map_sync(
self,
current_files_map,
source_file,
processed_files,
Some(dest_path),
false,
!dry_run,
force,
false,
)?
};
let version = self.append_version_to_nrs_map_container(
success_count,
current_version,
&new_files_map,
url,
xorurl_encoder,
dry_run,
update_nrs,
)?;
Ok((version, processed_files, new_files_map))
}
pub fn files_container_add_from_raw(
&mut self,
data: &[u8],
url: &str,
force: bool,
update_nrs: bool,
dry_run: bool,
) -> ResultReturn<(u64, ProcessedFiles, FilesMap)> {
let (xorurl_encoder, current_version, current_files_map) =
validate_files_add_params(self, "", url, update_nrs)?;
let dest_path = xorurl_encoder.path();
let new_file_xorurl = self.files_put_published_immutable(data, None)?;
let (processed_files, new_files_map, success_count) =
files_map_add_link(self, current_files_map, &new_file_xorurl, dest_path, force)?;
let version = self.append_version_to_nrs_map_container(
success_count,
current_version,
&new_files_map,
url,
xorurl_encoder,
dry_run,
update_nrs,
)?;
Ok((version, processed_files, new_files_map))
}
#[allow(clippy::too_many_arguments)]
fn append_version_to_nrs_map_container(
&mut self,
success_count: u64,
current_version: u64,
new_files_map: &FilesMap,
url: &str,
mut xorurl_encoder: XorUrlEncoder,
dry_run: bool,
update_nrs: bool,
) -> ResultReturn<u64> {
let version = if success_count == 0 {
current_version
} else if dry_run {
current_version + 1
} else {
let serialised_files_map = serde_json::to_string(new_files_map).map_err(|err| {
Error::Unexpected(format!(
"Couldn't serialise the FilesMap generated: {:?}",
err
))
})?;
let now = gen_timestamp_nanos();
let files_container_data = vec![(
now.into_bytes().to_vec(),
serialised_files_map.as_bytes().to_vec(),
)];
let xorname = xorurl_encoder.xorname();
let type_tag = xorurl_encoder.type_tag();
let new_version = self.safe_app.append_seq_append_only_data(
files_container_data,
current_version + 1,
xorname,
type_tag,
)?;
if update_nrs {
xorurl_encoder.set_content_version(Some(new_version));
let new_link_for_nrs = xorurl_encoder.to_string()?;
let _ = self.nrs_map_container_add(url, &new_link_for_nrs, false, true, false)?;
}
new_version
};
Ok(version)
}
pub fn files_put_published_immutable(
&mut self,
data: &[u8],
media_type: Option<&str>,
) -> ResultReturn<XorUrl> {
let content_type = media_type.map_or_else(
|| Ok(SafeContentType::Raw),
|media_type_str| {
if XorUrlEncoder::is_media_type_supported(media_type_str) {
Ok(SafeContentType::MediaType(media_type_str.to_string()))
} else {
Err(Error::InvalidMediaType(format!(
"Media-type '{}' not supported. You can pass 'None' as the 'media_type' for this content to be treated as raw",
media_type_str
)))
}
},
)?;
let xorname = self.safe_app.files_put_published_immutable(&data)?;
XorUrlEncoder::encode(
xorname,
0,
SafeDataType::PublishedImmutableData,
content_type,
None,
None,
None,
&self.xorurl_base,
)
}
pub fn files_get_published_immutable(&self, url: &str) -> ResultReturn<Vec<u8>> {
let (xorurl_encoder, _) = self.parse_and_resolve_url(url)?;
self.safe_app
.files_get_published_immutable(xorurl_encoder.xorname())
}
}
fn validate_files_add_params(
safe: &mut Safe,
source_file: &str,
url: &str,
update_nrs: bool,
) -> ResultReturn<(XorUrlEncoder, u64, FilesMap)> {
let xorurl_encoder = Safe::parse_url(url)?;
if xorurl_encoder.content_version().is_some() {
return Err(Error::InvalidInput(format!(
"The target URL cannot cannot contain a version: {}",
url
)));
};
if update_nrs && xorurl_encoder.content_type() != SafeContentType::NrsMapContainer {
return Err(Error::InvalidInput(
"'update-nrs' is not allowed since the URL provided is not an NRS URL".to_string(),
));
}
let (mut xorurl_encoder, is_nrs_resolved) = safe.parse_and_resolve_url(url)?;
if is_nrs_resolved {
xorurl_encoder.set_content_version(None);
}
let (current_version, current_files_map): (u64, FilesMap) =
safe.files_container_get(&xorurl_encoder.to_string()?)?;
let dest_path = xorurl_encoder.path().to_string();
if source_file.starts_with("safe://") {
let source_xorurl_encoder = Safe::parse_url(source_file)?;
if source_xorurl_encoder.data_type() != SafeDataType::PublishedImmutableData {
return Err(Error::InvalidInput(format!(
"The source URL should target a file ('{}'), but the URL provided targets a '{}'",
SafeDataType::PublishedImmutableData,
source_xorurl_encoder.content_type()
)));
}
if dest_path.is_empty() {
return Err(Error::InvalidInput(
"The destination URL should include a target file path since we are adding a link"
.to_string(),
));
}
}
Ok((xorurl_encoder, current_version, current_files_map))
}
fn normalise_path_separator(from: &str) -> String {
str::replace(&from, "\\", "/").to_string()
}
fn get_base_paths(location: &str, dest_path: Option<&str>) -> ResultReturn<(String, String)> {
let location_base_path = if location == "." {
"./".to_string()
} else {
normalise_path_separator(location)
};
let new_dest_path = match dest_path {
Some(path) => {
if path.is_empty() {
"/".to_string()
} else {
path.to_string()
}
}
None => "/".to_string(),
};
let dest_base_path = if new_dest_path.ends_with('/') {
if location_base_path.ends_with('/') {
new_dest_path
} else {
let parts_vec: Vec<&str> = location_base_path.split('/').collect();
let dir_name = parts_vec[parts_vec.len() - 1];
format!("{}{}", new_dest_path, dir_name)
}
} else {
format!("{}/", new_dest_path)
};
Ok((location_base_path, dest_base_path))
}
fn gen_new_file_item(
safe: &mut Safe,
file_path: &Path,
file_type: &str,
file_size: &str,
file_created: Option<&str>,
link: Option<&str>,
) -> Result<FileItem, String> {
let now = gen_timestamp_secs();
let mut file_item = FileItem::new();
let xorurl = match link {
None => upload_file_to_net(safe, file_path)?,
Some(link) => link.to_string(),
};
file_item.insert(FAKE_RDF_PREDICATE_LINK.to_string(), xorurl);
file_item.insert(FAKE_RDF_PREDICATE_TYPE.to_string(), file_type.to_string());
file_item.insert(FAKE_RDF_PREDICATE_SIZE.to_string(), file_size.to_string());
file_item.insert(FAKE_RDF_PREDICATE_MODIFIED.to_string(), now.clone());
let created = file_created.unwrap_or_else(|| &now);
file_item.insert(FAKE_RDF_PREDICATE_CREATED.to_string(), created.to_string());
Ok(file_item)
}
#[allow(clippy::too_many_arguments)]
fn add_or_update_file_item(
safe: &mut Safe,
file_name: &str,
file_name_for_map: &str,
file_path: &Path,
file_type: &str,
file_size: &str,
file_created: Option<&str>,
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_type,
file_size,
file_created,
file_link,
) {
Ok(new_file_item) => {
let content_added_sign = if name_exists {
CONTENT_UPDATED_SIGN.to_string()
} else {
CONTENT_ADDED_SIGN.to_string()
};
debug!("New FileItem item: {:?}", new_file_item);
debug!("New FileItem item inserted as {:?}", file_name);
files_map.insert(file_name_for_map.to_string(), new_file_item.clone());
processed_files.insert(
file_name.to_string(),
(
content_added_sign,
new_file_item[FAKE_RDF_PREDICATE_LINK].clone(),
),
);
true
}
Err(err) => {
processed_files.insert(
file_name.to_string(),
(CONTENT_ERROR_SIGN.to_string(), format!("<{}>", err)),
);
info!(
"Skipping file \"{}\": {:?}",
file_link.unwrap_or_else(|| ""),
err
);
false
}
}
}
#[allow(clippy::too_many_arguments)]
fn files_map_sync(
safe: &mut Safe,
mut current_files_map: FilesMap,
location: &str,
new_content: ProcessedFiles,
dest_path: Option<&str>,
delete: bool,
upload_files: bool,
force: bool,
compare_file_content: bool,
) -> ResultReturn<(ProcessedFiles, FilesMap, u64)> {
let (location_base_path, dest_base_path) = get_base_paths(location, dest_path)?;
let mut updated_files_map = FilesMap::new();
let mut processed_files = ProcessedFiles::new();
let mut success_count = 0;
for (local_file_name, _) in new_content
.iter()
.filter(|(_, (change, _))| change != CONTENT_ERROR_SIGN)
{
let file_path = Path::new(&local_file_name);
let (metadata, file_type) = get_metadata(&file_path)?;
let file_size = metadata.len().to_string();
let file_name = RelativePath::new(
&local_file_name
.to_string()
.replace(&location_base_path, &dest_base_path),
)
.normalize();
let normalised_file_name = format!("/{}", normalise_path_separator(file_name.as_str()));
let link = if upload_files { None } else { Some("") };
match current_files_map.get(&normalised_file_name) {
None => {
if add_or_update_file_item(
safe,
&local_file_name,
&normalised_file_name,
&file_path,
&file_type,
&file_size,
None,
link,
false,
&mut updated_files_map,
&mut processed_files,
) {
success_count += 1;
}
}
Some(file_item) => {
if force
|| (compare_file_content
&& (file_item[FAKE_RDF_PREDICATE_SIZE] != file_size
|| file_item[FAKE_RDF_PREDICATE_TYPE] != file_type))
{
if add_or_update_file_item(
safe,
&local_file_name,
&normalised_file_name,
&file_path,
&file_type,
&file_size,
Some(&file_item[FAKE_RDF_PREDICATE_CREATED]),
link,
true,
&mut updated_files_map,
&mut processed_files,
) {
success_count += 1;
}
} else {
updated_files_map.insert(normalised_file_name.to_string(), file_item.clone());
if !force && !compare_file_content {
processed_files.insert(
local_file_name.to_string(),
(
CONTENT_ERROR_SIGN.to_string(),
format!(
"File named \"{}\" already exists on target. Use the 'force' flag to replace it",
normalised_file_name
),
),
);
info!("Skipping file \"{}\" since a file with name \"{}\" already exists on target. You can use the 'force' flag to replace the existing file with the new one", local_file_name, normalised_file_name);
}
}
current_files_map.remove(&normalised_file_name);
}
}
}
current_files_map.iter().for_each(|(file_name, file_item)| {
if !delete {
updated_files_map.insert(file_name.to_string(), file_item.clone());
} else {
processed_files.insert(
file_name.to_string(),
(
CONTENT_DELETED_SIGN.to_string(),
file_item[FAKE_RDF_PREDICATE_LINK].clone(),
),
);
success_count += 1;
}
});
Ok((processed_files, updated_files_map, success_count))
}
fn files_map_add_link(
safe: &mut Safe,
mut files_map: FilesMap,
file_link: &str,
file_name: &str,
force: bool,
) -> ResultReturn<(ProcessedFiles, FilesMap, u64)> {
let mut processed_files = ProcessedFiles::new();
let mut success_count = 0;
match XorUrlEncoder::from_url(file_link) {
Err(err) => {
processed_files.insert(
file_link.to_string(),
(CONTENT_ERROR_SIGN.to_string(), format!("<{}>", err)),
);
info!("Skipping file \"{}\". {}", file_link, err);
Ok((processed_files, files_map, success_count))
}
Ok(xorurl_encoder) => {
let file_path = Path::new("");
let file_type = match xorurl_encoder.content_type() {
SafeContentType::MediaType(media_type) => media_type,
other => format!("{}", other),
};
let file_size = "";
match files_map.get(file_name) {
Some(current_file_item) => {
if current_file_item[FAKE_RDF_PREDICATE_LINK] != file_link {
if force {
if add_or_update_file_item(
safe,
file_name,
file_name,
&file_path,
&file_type,
&file_size,
None,
Some(file_link),
true,
&mut files_map,
&mut processed_files,
) {
success_count += 1;
}
} else {
processed_files.insert(file_name.to_string(), (CONTENT_ERROR_SIGN.to_string(), format!("File named \"{}\" already exists on target. Use the 'force' flag to replace it", file_name)));
info!("Skipping file \"{}\" since a file with name \"{}\" already exists on target. You can use the 'force' flag to replace the existing file with the new one", file_link, file_name);
}
} else {
processed_files.insert(
file_link.to_string(),
(
CONTENT_ERROR_SIGN.to_string(),
format!(
"File named \"{}\" already exists on target with same link",
file_name
),
),
);
info!("Skipping file \"{}\" since a file with name \"{}\" already exists on target with the same link", file_link, file_name);
}
}
None => {
if add_or_update_file_item(
safe,
file_name,
file_name,
&file_path,
&file_type,
&file_size,
None,
Some(file_link),
false,
&mut files_map,
&mut processed_files,
) {
success_count += 1;
}
}
};
Ok((processed_files, files_map, success_count))
}
}
}
fn upload_file_to_net(safe: &mut Safe, path: &Path) -> ResultReturn<XorUrl> {
let data = fs::read(path).map_err(|err| {
Error::InvalidInput(format!("Failed to read file from local location: {}", err))
})?;
let mime_type = mime_guess::from_path(&path);
safe.files_put_published_immutable(&data, mime_type.first_raw())
.or_else(|err| {
if let Error::InvalidMediaType(_) = err {
safe.files_put_published_immutable(&data, None)
} else {
Err(err)
}
})
}
fn get_metadata(path: &Path) -> ResultReturn<(fs::Metadata, String)> {
let metadata = fs::metadata(path).map_err(|err| {
Error::FilesSystemError(format!(
"Couldn't read metadata from source path ('{}'): {}",
path.display(),
err
))
})?;
debug!("Metadata for location: {:?}", metadata);
let mime_type = mime_guess::from_path(&path);
let media_type = mime_type.first_raw().unwrap_or("Raw");
Ok((metadata, media_type.to_string()))
}
fn file_system_dir_walk(
safe: &mut Safe,
location: &str,
recursive: bool,
upload_files: bool,
) -> ResultReturn<ProcessedFiles> {
let file_path = Path::new(location);
let (metadata, _) = get_metadata(&file_path)?;
info!("Reading files from {}", file_path.display());
if metadata.is_dir() || !recursive {
let max_depth = if recursive { MAX_RECURSIVE_DEPTH } else { 1 };
let mut processed_files = BTreeMap::new();
WalkDir::new(file_path)
.follow_links(true)
.into_iter()
.filter_entry(|e| not_hidden_and_valid_depth(e, max_depth))
.filter_map(|v| v.ok())
.for_each(|child| {
let current_file_path = child.path();
let current_path_str = current_file_path.to_str().unwrap_or_else(|| "").to_string();
info!("Processing {}...", current_path_str);
let normalised_path = normalise_path_separator(¤t_path_str);
match fs::metadata(¤t_file_path) {
Ok(metadata) => {
if metadata.is_dir() {
} else if upload_files {
match upload_file_to_net(safe, ¤t_file_path) {
Ok(xorurl) => {
processed_files.insert(normalised_path, (CONTENT_ADDED_SIGN.to_string(), xorurl));
}
Err(err) => {
processed_files.insert(normalised_path.clone(), (CONTENT_ERROR_SIGN.to_string(), format!("<{}>", err)));
info!(
"Skipping file \"{}\". {}",
normalised_path, err);
},
};
} else {
processed_files.insert(normalised_path.clone(), (CONTENT_ADDED_SIGN.to_string(), "".to_string()));
}
},
Err(err) => {
processed_files.insert(normalised_path.clone(), (CONTENT_ERROR_SIGN.to_string(), format!("<{}>", err)));
info!(
"Skipping file \"{}\" since no metadata could be read from local location: {:?}",
normalised_path, err);
}
}
});
Ok(processed_files)
} else {
Err(Error::InvalidInput(format!(
"'{}' is not a directory. The \"--recursive\" arg is only supported for folders.",
location
)))
}
}
fn not_hidden_and_valid_depth(entry: &DirEntry, max_depth: usize) -> bool {
entry
.file_name()
.to_str()
.map(|s| entry.depth() <= max_depth && (entry.depth() == 0 || !s.starts_with('.')))
.unwrap_or(false)
}
fn file_system_single_file(
safe: &mut Safe,
location: &str,
upload_files: bool,
) -> ResultReturn<ProcessedFiles> {
let file_path = Path::new(location);
info!("Reading file {}", file_path.display());
let mut processed_files = BTreeMap::new();
let normalised_path = normalise_path_separator(file_path.to_str().unwrap_or_else(|| ""));
match fs::metadata(&file_path) {
Ok(metadata) => {
if metadata.is_dir() {
Err(Error::InvalidInput(format!(
"'{}' is a directory, only individual files can be added. Use files sync operation for uploading folders",
location
)))
} else if upload_files {
match upload_file_to_net(safe, &file_path) {
Ok(xorurl) => {
processed_files
.insert(normalised_path, (CONTENT_ADDED_SIGN.to_string(), xorurl));
}
Err(err) => {
processed_files.insert(
normalised_path.clone(),
(CONTENT_ERROR_SIGN.to_string(), format!("<{}>", err)),
);
info!("Skipping file \"{}\". {}", normalised_path, err);
}
};
Ok(processed_files)
} else {
processed_files.insert(
normalised_path,
(CONTENT_ADDED_SIGN.to_string(), "".to_string()),
);
Ok(processed_files)
}
}
Err(err) => {
processed_files.insert(
normalised_path.clone(),
(CONTENT_ERROR_SIGN.to_string(), format!("<{}>", err)),
);
info!(
"Skipping file \"{}\" since no metadata could be read from local location: {:?}",
normalised_path, err
);
Ok(processed_files)
}
}
}
fn files_map_create(
content: &ProcessedFiles,
location: &str,
dest_path: Option<&str>,
) -> ResultReturn<FilesMap> {
let mut files_map = FilesMap::default();
let now = gen_timestamp_secs();
let (location_base_path, dest_base_path) = get_base_paths(location, dest_path)?;
for (file_name, (_change, link)) in content
.iter()
.filter(|(_, (change, _))| change != CONTENT_ERROR_SIGN)
{
debug!("FileItem item name:{:?}", &file_name);
let mut file_item = FileItem::new();
let file_path = Path::new(&file_name);
let (metadata, file_type) = get_metadata(&file_path)?;
file_item.insert(FAKE_RDF_PREDICATE_LINK.to_string(), link.to_string());
file_item.insert(FAKE_RDF_PREDICATE_TYPE.to_string(), file_type);
let file_size = &metadata.len().to_string();
file_item.insert(FAKE_RDF_PREDICATE_SIZE.to_string(), file_size.to_string());
file_item.insert(FAKE_RDF_PREDICATE_MODIFIED.to_string(), now.clone());
file_item.insert(FAKE_RDF_PREDICATE_CREATED.to_string(), now.clone());
debug!("FileItem item: {:?}", file_item);
let new_file_name = RelativePath::new(
&file_name
.to_string()
.replace(&location_base_path, &dest_base_path),
)
.normalize();
let final_name = format!("/{}", normalise_path_separator(new_file_name.as_str()));
debug!("FileItem item inserted with filename {:?}", &final_name);
files_map.insert(final_name.to_string(), file_item);
}
Ok(files_map)
}
#[test]
fn test_files_map_create() {
use unwrap::unwrap;
let mut processed_files = ProcessedFiles::new();
processed_files.insert(
"./testdata/test.md".to_string(),
(
CONTENT_ADDED_SIGN.to_string(),
"safe://top_xorurl".to_string(),
),
);
processed_files.insert(
"./testdata/subfolder/subexists.md".to_string(),
(
CONTENT_ADDED_SIGN.to_string(),
"safe://second_xorurl".to_string(),
),
);
let files_map = unwrap!(files_map_create(&processed_files, "./testdata", Some("")));
assert_eq!(files_map.len(), 2);
let file_item1 = &files_map["/testdata/test.md"];
assert_eq!(file_item1[FAKE_RDF_PREDICATE_LINK], "safe://top_xorurl");
assert_eq!(file_item1[FAKE_RDF_PREDICATE_TYPE], "text/x-markdown");
assert_eq!(file_item1[FAKE_RDF_PREDICATE_SIZE], "12");
let file_item2 = &files_map["/testdata/subfolder/subexists.md"];
assert_eq!(file_item2[FAKE_RDF_PREDICATE_LINK], "safe://second_xorurl");
assert_eq!(file_item2[FAKE_RDF_PREDICATE_TYPE], "text/x-markdown");
assert_eq!(file_item2[FAKE_RDF_PREDICATE_SIZE], "23");
}
#[test]
fn test_files_container_create_file() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let filename = "testdata/test.md";
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create(filename, None, false, false));
assert!(xorurl.starts_with("safe://"));
assert_eq!(processed_files.len(), 1);
assert_eq!(files_map.len(), 1);
let file_path = "/test.md";
assert_eq!(processed_files[filename].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename].1,
files_map[file_path][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_create_dry_run() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let filename = "./testdata/";
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create(filename, None, true, true));
assert!(xorurl.is_empty());
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let filename1 = "./testdata/test.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert!(processed_files[filename1].1.is_empty());
assert_eq!(
processed_files[filename1].1,
files_map["/test.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/another.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert!(processed_files[filename2].1.is_empty());
assert_eq!(
processed_files[filename2].1,
files_map["/another.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert!(processed_files[filename3].1.is_empty());
assert_eq!(
processed_files[filename3].1,
files_map["/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename4 = "./testdata/noextension";
assert_eq!(processed_files[filename4].0, CONTENT_ADDED_SIGN);
assert!(processed_files[filename4].1.is_empty());
assert_eq!(
processed_files[filename4].1,
files_map["/noextension"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_create_folder_without_trailing_slash() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("testdata", None, true, false));
assert!(xorurl.starts_with("safe://"));
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let filename1 = "testdata/test.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
files_map["/testdata/test.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "testdata/another.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
files_map["/testdata/another.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename3].1,
files_map["/testdata/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename4 = "testdata/noextension";
assert_eq!(processed_files[filename4].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename4].1,
files_map["/testdata/noextension"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_create_folder_with_trailing_slash() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/", None, true, false));
assert!(xorurl.starts_with("safe://"));
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let filename1 = "./testdata/test.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
files_map["/test.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/another.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
files_map["/another.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename3].1,
files_map["/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename4 = "./testdata/noextension";
assert_eq!(processed_files[filename4].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename4].1,
files_map["/noextension"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_create_dest_path_without_trailing_slash() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata", Some("/myroot"), true, false));
assert!(xorurl.starts_with("safe://"));
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let filename1 = "./testdata/test.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
files_map["/myroot/test.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/another.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
files_map["/myroot/another.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename3].1,
files_map["/myroot/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename4 = "./testdata/noextension";
assert_eq!(processed_files[filename4].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename4].1,
files_map["/myroot/noextension"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_create_dest_path_with_trailing_slash() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata", Some("/myroot/"), true, false));
assert!(xorurl.starts_with("safe://"));
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let filename1 = "./testdata/test.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
files_map["/myroot/testdata/test.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/another.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
files_map["/myroot/testdata/another.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename3].1,
files_map["/myroot/testdata/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename4 = "./testdata/noextension";
assert_eq!(processed_files[filename4].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename4].1,
files_map["/myroot/testdata/noextension"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_sync() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/", None, true, false));
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_sync(
"./testdata/subfolder/",
&xorurl,
true,
false,
false,
false
));
assert_eq!(version, 1);
assert_eq!(new_processed_files.len(), 2);
assert_eq!(new_files_map.len(), 7);
let filename1 = "./testdata/test.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
new_files_map["/test.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/another.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
new_files_map["/another.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename3].1,
new_files_map["/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename4 = "./testdata/noextension";
assert_eq!(processed_files[filename4].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename4].1,
new_files_map["/noextension"][FAKE_RDF_PREDICATE_LINK]
);
let filename5 = "./testdata/subfolder/subexists.md";
assert_eq!(new_processed_files[filename5].0, CONTENT_ADDED_SIGN);
assert_eq!(
new_processed_files[filename5].1,
new_files_map["/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename6 = "./testdata/subfolder/sub2.md";
assert_eq!(new_processed_files[filename6].0, CONTENT_ADDED_SIGN);
assert_eq!(
new_processed_files[filename6].1,
new_files_map["/sub2.md"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_sync_dry_run() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/", None, true, false));
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_sync(
"./testdata/subfolder/",
&xorurl,
true,
false,
false,
true
));
assert_eq!(version, 1);
assert_eq!(new_processed_files.len(), 2);
assert_eq!(new_files_map.len(), 7);
let filename1 = "./testdata/test.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
new_files_map["/test.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/another.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
new_files_map["/another.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename3].1,
new_files_map["/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename4 = "./testdata/noextension";
assert_eq!(processed_files[filename4].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename4].1,
new_files_map["/noextension"][FAKE_RDF_PREDICATE_LINK]
);
let filename5 = "./testdata/subfolder/subexists.md";
assert_eq!(new_processed_files[filename5].0, CONTENT_ADDED_SIGN);
assert!(new_processed_files[filename5].1.is_empty());
assert_eq!(
new_processed_files[filename5].1,
new_files_map["/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename6 = "./testdata/subfolder/sub2.md";
assert_eq!(new_processed_files[filename6].0, CONTENT_ADDED_SIGN);
assert!(new_processed_files[filename6].1.is_empty());
assert_eq!(
new_processed_files[filename6].1,
new_files_map["/sub2.md"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_sync_with_versioned_target() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, _, _) = unwrap!(safe.files_container_create("./testdata/", None, true, false));
let versioned_xorurl = format!("{}?v=5", xorurl);
match safe.files_container_sync(
"./testdata/subfolder/",
&versioned_xorurl,
false,
false,
true,
false,
) {
Ok(_) => panic!("Sync was unexpectdly successful"),
Err(err) => assert_eq!(
err,
Error::InvalidInput(format!(
"The target URL cannot cannot contain a version: {}",
versioned_xorurl
))
),
};
}
#[test]
fn test_files_container_sync_with_delete() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/", None, true, false));
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_sync(
"./testdata/subfolder/",
&xorurl,
true,
true,
false,
false
));
assert_eq!(version, 1);
assert_eq!(new_processed_files.len(), 7);
assert_eq!(new_files_map.len(), 2);
let file_path1 = "/test.md";
assert_eq!(new_processed_files[file_path1].0, CONTENT_DELETED_SIGN);
assert_eq!(
new_processed_files[file_path1].1,
files_map[file_path1][FAKE_RDF_PREDICATE_LINK]
);
let file_path2 = "/another.md";
assert_eq!(new_processed_files[file_path2].0, CONTENT_DELETED_SIGN);
assert_eq!(
new_processed_files[file_path2].1,
files_map[file_path2][FAKE_RDF_PREDICATE_LINK]
);
let file_path3 = "/subfolder/subexists.md";
assert_eq!(new_processed_files[file_path3].0, CONTENT_DELETED_SIGN);
assert_eq!(
new_processed_files[file_path3].1,
files_map[file_path3][FAKE_RDF_PREDICATE_LINK]
);
let file_path4 = "/noextension";
assert_eq!(new_processed_files[file_path4].0, CONTENT_DELETED_SIGN);
assert_eq!(
new_processed_files[file_path4].1,
files_map[file_path4][FAKE_RDF_PREDICATE_LINK]
);
let filename5 = "./testdata/subfolder/subexists.md";
assert_eq!(new_processed_files[filename5].0, CONTENT_ADDED_SIGN);
assert_eq!(
new_processed_files[filename5].1,
new_files_map["/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_sync_delete_without_recursive() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
match safe.files_container_sync(
"./testdata/subfolder/",
"some-url",
false,
true,
false,
false,
) {
Ok(_) => panic!("Sync was unexpectdly successful"),
Err(err) => assert_eq!(
err,
Error::InvalidInput("'delete' is not allowed if --recursive is not set".to_string())
),
};
}
#[test]
fn test_files_container_sync_update_nrs_unversioned_link() {
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, _, _) = unwrap!(safe.files_container_create("./testdata/", None, true, false));
let nrsurl: String = thread_rng().sample_iter(&Alphanumeric).take(15).collect();
let mut xorurl_encoder = unwrap!(XorUrlEncoder::from_url(&xorurl));
xorurl_encoder.set_content_version(None);
let unversioned_link = unwrap!(xorurl_encoder.to_string());
match safe.nrs_map_container_create(&nrsurl, &unversioned_link, false, true, false) {
Ok(_) => panic!("NRS create was unexpectdly successful"),
Err(err) => assert_eq!(
err,
Error::InvalidInput(format!(
"The linked content (FilesContainer) is versionable, therefore NRS requires the link to specify a version: \"{}\"",
unversioned_link
))
),
};
}
#[test]
fn test_files_container_sync_update_nrs_with_xorurl() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, _, _) = unwrap!(safe.files_container_create("./testdata/", None, true, false));
match safe.files_container_sync(
"./testdata/subfolder/",
&xorurl,
false,
false,
true,
false,
) {
Ok(_) => panic!("Sync was unexpectdly successful"),
Err(err) => assert_eq!(
err,
Error::InvalidInput(
"'update-nrs' is not allowed since the URL provided is not an NRS URL".to_string()
)
),
};
}
#[test]
fn test_files_container_sync_update_nrs_versioned_link() {
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, _, _) = unwrap!(safe.files_container_create("./testdata/", None, true, false));
let nrsurl: String = thread_rng().sample_iter(&Alphanumeric).take(15).collect();
let mut xorurl_encoder = unwrap!(XorUrlEncoder::from_url(&xorurl));
xorurl_encoder.set_content_version(Some(0));
let _ = unwrap!(safe.nrs_map_container_create(
&nrsurl,
&unwrap!(xorurl_encoder.to_string()),
false,
true,
false
));
let _ = unwrap!(safe.files_container_sync(
"./testdata/subfolder/",
&nrsurl,
false,
false,
true,
false,
));
let mut xorurl_encoder = unwrap!(XorUrlEncoder::from_url(&xorurl));
xorurl_encoder.set_content_version(Some(1));
let (new_link, _) = unwrap!(safe.parse_and_resolve_url(&nrsurl));
assert_eq!(
unwrap!(new_link.to_string()),
unwrap!(xorurl_encoder.to_string())
);
}
#[test]
fn test_files_container_sync_target_path_without_trailing_slash() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/", None, true, false));
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let mut xorurl_encoder = unwrap!(XorUrlEncoder::from_url(&xorurl));
xorurl_encoder.set_path("path/when/sync");
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_sync(
"./testdata/subfolder",
&unwrap!(xorurl_encoder.to_string()),
true,
false,
false,
false
));
assert_eq!(version, 1);
assert_eq!(new_processed_files.len(), 2);
assert_eq!(new_files_map.len(), 7);
let filename1 = "./testdata/test.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
new_files_map["/test.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/another.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
new_files_map["/another.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename3].1,
new_files_map["/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename4 = "./testdata/noextension";
assert_eq!(processed_files[filename4].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename4].1,
new_files_map["/noextension"][FAKE_RDF_PREDICATE_LINK]
);
let filename5 = "./testdata/subfolder/subexists.md";
assert_eq!(new_processed_files[filename5].0, CONTENT_ADDED_SIGN);
assert_eq!(
new_processed_files[filename5].1,
new_files_map["/path/when/sync/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_sync_target_path_with_trailing_slash() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/", None, true, false));
assert_eq!(processed_files.len(), 5);
assert_eq!(files_map.len(), 5);
let mut xorurl_encoder = unwrap!(XorUrlEncoder::from_url(&xorurl));
xorurl_encoder.set_path("/path/when/sync/");
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_sync(
"./testdata/subfolder",
&unwrap!(xorurl_encoder.to_string()),
true,
false,
false,
false,
));
assert_eq!(version, 1);
assert_eq!(new_processed_files.len(), 2);
assert_eq!(new_files_map.len(), 7);
let filename1 = "./testdata/test.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
new_files_map["/test.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/another.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
new_files_map["/another.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename3].1,
new_files_map["/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename4 = "./testdata/noextension";
assert_eq!(processed_files[filename4].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename4].1,
new_files_map["/noextension"][FAKE_RDF_PREDICATE_LINK]
);
let filename5 = "./testdata/subfolder/subexists.md";
assert_eq!(new_processed_files[filename5].0, CONTENT_ADDED_SIGN);
assert_eq!(
new_processed_files[filename5].1,
new_files_map["/path/when/sync/subfolder/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_get() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, _processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/", None, true, false));
let (version, fetched_files_map) = unwrap!(safe.files_container_get(&xorurl));
assert_eq!(version, 0);
assert_eq!(fetched_files_map.len(), 5);
assert_eq!(files_map.len(), fetched_files_map.len());
assert_eq!(files_map["/test.md"], fetched_files_map["/test.md"]);
assert_eq!(files_map["/another.md"], fetched_files_map["/another.md"]);
assert_eq!(
files_map["/subfolder/subexists.md"],
fetched_files_map["/subfolder/subexists.md"]
);
assert_eq!(files_map["/noextension"], fetched_files_map["/noextension"]);
}
#[test]
fn test_files_container_version() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, _, _) = unwrap!(safe.files_container_create("./testdata/", None, true, false));
let (version, _) = unwrap!(safe.files_container_get(&xorurl));
assert_eq!(version, 0);
let (version, _, _) = unwrap!(safe.files_container_sync(
"./testdata/subfolder/",
&xorurl,
true,
true,
false,
false,
));
assert_eq!(version, 1);
let mut xorurl_encoder = unwrap!(XorUrlEncoder::from_url(&xorurl));
xorurl_encoder.set_content_version(None);
let (version, _) = unwrap!(safe.files_container_get(&unwrap!(xorurl_encoder.to_string())));
assert_eq!(version, 1);
}
#[test]
fn test_files_container_get_with_version() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, _processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/", None, true, false));
let (_version, _new_processed_files, new_files_map) = unwrap!(safe.files_container_sync(
"./testdata/subfolder/",
&xorurl,
true,
true,
false,
false
));
let mut xorurl_encoder = unwrap!(XorUrlEncoder::from_url(&xorurl));
xorurl_encoder.set_content_version(Some(0));
let (version, v0_files_map) =
unwrap!(safe.files_container_get(&unwrap!(xorurl_encoder.to_string())));
assert_eq!(version, 0);
assert_eq!(files_map, v0_files_map);
let file_path1 = "/test.md";
assert_eq!(
files_map[file_path1][FAKE_RDF_PREDICATE_LINK],
v0_files_map[file_path1][FAKE_RDF_PREDICATE_LINK]
);
xorurl_encoder.set_content_version(Some(1));
let (version, v1_files_map) =
unwrap!(safe.files_container_get(&unwrap!(xorurl_encoder.to_string())));
assert_eq!(version, 1);
assert_eq!(new_files_map, v1_files_map);
let file_path2 = "/another.md";
let file_path3 = "/subfolder/subexists.md";
let file_path4 = "/noextension";
assert!(v1_files_map.get(file_path1).is_none());
assert!(v1_files_map.get(file_path2).is_none());
assert!(v1_files_map.get(file_path3).is_none());
assert!(v1_files_map.get(file_path4).is_none());
xorurl_encoder.set_content_version(Some(2));
match safe.files_container_get(&unwrap!(xorurl_encoder.to_string())) {
Ok(_) => panic!("unexpectdly retrieved verion 3 of container"),
Err(Error::VersionNotFound(msg)) => assert_eq!(
msg,
format!(
"Version '2' is invalid for FilesContainer found at \"{}\"",
xorurl_encoder
)
),
other => panic!(format!(
"error returned is not the expected one: {:?}",
other
)),
};
}
#[test]
fn test_files_container_sync_with_nrs_url() {
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, _, _) =
unwrap!(safe.files_container_create("./testdata/test.md", None, false, false));
let nrsurl: String = thread_rng().sample_iter(&Alphanumeric).take(15).collect();
let mut xorurl_encoder = unwrap!(XorUrlEncoder::from_url(&xorurl));
xorurl_encoder.set_content_version(Some(0));
let _ = unwrap!(safe.nrs_map_container_create(
&nrsurl,
&unwrap!(xorurl_encoder.to_string()),
false,
true,
false
));
let _ = unwrap!(safe.files_container_sync(
"./testdata/subfolder/",
&xorurl,
false,
false,
false,
false,
));
let _ = unwrap!(safe.files_container_sync(
"./testdata/",
&nrsurl,
false,
false,
true,
false,
));
let (version, fetched_files_map) = unwrap!(safe.files_container_get(&xorurl));
assert_eq!(version, 2);
assert_eq!(fetched_files_map.len(), 5);
}
#[test]
fn test_files_container_add() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/subfolder/", None, false, false));
assert_eq!(processed_files.len(), 2);
assert_eq!(files_map.len(), 2);
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_add(
"./testdata/test.md",
&format!("{}/new_filename_test.md", xorurl),
false,
false,
false,
));
assert_eq!(version, 1);
assert_eq!(new_processed_files.len(), 1);
assert_eq!(new_files_map.len(), 3);
let filename1 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
new_files_map["/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/subfolder/sub2.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
new_files_map["/sub2.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename3 = "./testdata/test.md";
assert_eq!(new_processed_files[filename3].0, CONTENT_ADDED_SIGN);
assert_eq!(
new_processed_files[filename3].1,
new_files_map["/new_filename_test.md"][FAKE_RDF_PREDICATE_LINK]
);
}
#[test]
fn test_files_container_add_dir() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/subfolder/", None, false, false));
assert_eq!(processed_files.len(), 2);
assert_eq!(files_map.len(), 2);
match safe.files_container_add(
"./testdata",
&xorurl,
false,
false,
false
) {
Ok(_) => panic!("unexpectdly added a folder to files container"),
Err(Error::InvalidInput(msg)) => assert_eq!(
msg,
"'./testdata' is a directory, only individual files can be added. Use files sync operation for uploading folders".to_string(),
),
other => panic!(format!(
"error returned is not the expected one: {:?}",
other
)),
}
}
#[test]
fn test_files_container_add_existing_name() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/subfolder/", None, false, false));
assert_eq!(processed_files.len(), 2);
assert_eq!(files_map.len(), 2);
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_add(
"./testdata/test.md",
&format!("{}/sub2.md", xorurl),
false,
false,
false
));
assert_eq!(version, 0);
assert_eq!(new_processed_files.len(), 1);
assert_eq!(new_files_map.len(), 2);
assert_eq!(
new_processed_files["./testdata/test.md"].1,
"File named \"/sub2.md\" already exists on target. Use the \'force\' flag to replace it"
);
assert_eq!(files_map, new_files_map);
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_add(
"./testdata/test.md",
&format!("{}/sub2.md", xorurl),
true,
false,
false
));
assert_eq!(version, 1);
assert_eq!(new_processed_files.len(), 1);
assert_eq!(new_files_map.len(), 2);
assert_eq!(
new_processed_files["./testdata/test.md"].0,
CONTENT_UPDATED_SIGN
);
assert_eq!(
new_processed_files["./testdata/test.md"].1,
new_files_map["/sub2.md"]["link"]
);
}
#[test]
fn test_files_container_add_a_url() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/subfolder/", None, false, false));
assert_eq!(processed_files.len(), 2);
assert_eq!(files_map.len(), 2);
let data = b"0123456789";
let file_xorurl = unwrap!(safe.files_put_published_immutable(data, None));
let new_filename = "/new_filename_test.md";
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_add(
&file_xorurl,
&format!("{}{}", xorurl, new_filename),
false,
false,
false
));
assert_eq!(version, 1);
assert_eq!(new_processed_files.len(), 1);
assert_eq!(new_files_map.len(), 3);
let filename1 = "./testdata/subfolder/subexists.md";
assert_eq!(processed_files[filename1].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename1].1,
new_files_map["/subexists.md"][FAKE_RDF_PREDICATE_LINK]
);
let filename2 = "./testdata/subfolder/sub2.md";
assert_eq!(processed_files[filename2].0, CONTENT_ADDED_SIGN);
assert_eq!(
processed_files[filename2].1,
new_files_map["/sub2.md"][FAKE_RDF_PREDICATE_LINK]
);
assert_eq!(new_processed_files[new_filename].0, CONTENT_ADDED_SIGN);
assert_eq!(
new_processed_files[new_filename].1,
new_files_map[new_filename][FAKE_RDF_PREDICATE_LINK]
);
assert_eq!(
new_files_map[new_filename][FAKE_RDF_PREDICATE_LINK],
file_xorurl
);
let data = b"9876543210";
let other_file_xorurl = unwrap!(safe.files_put_published_immutable(data, None));
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_add(
&other_file_xorurl,
&format!("{}{}", xorurl, new_filename),
true,
false,
false
));
assert_eq!(version, 2);
assert_eq!(new_processed_files.len(), 1);
assert_eq!(new_files_map.len(), 3);
assert_eq!(new_processed_files[new_filename].0, CONTENT_UPDATED_SIGN);
assert_eq!(
new_processed_files[new_filename].1,
new_files_map[new_filename][FAKE_RDF_PREDICATE_LINK]
);
assert_eq!(
new_files_map[new_filename][FAKE_RDF_PREDICATE_LINK],
other_file_xorurl
);
}
#[test]
fn test_files_container_add_from_raw() {
use unwrap::unwrap;
let mut safe = Safe::new("base32z");
unwrap!(safe.connect("", Some("fake-credentials")));
let (xorurl, processed_files, files_map) =
unwrap!(safe.files_container_create("./testdata/subfolder/", None, false, false));
assert_eq!(processed_files.len(), 2);
assert_eq!(files_map.len(), 2);
let data = b"0123456789";
let new_filename = "/new_filename_test.md";
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_add_from_raw(
data,
&format!("{}{}", xorurl, new_filename),
false,
false,
false
));
assert_eq!(version, 1);
assert_eq!(new_processed_files.len(), 1);
assert_eq!(new_files_map.len(), 3);
assert_eq!(new_processed_files[new_filename].0, CONTENT_ADDED_SIGN);
assert_eq!(
new_processed_files[new_filename].1,
new_files_map[new_filename][FAKE_RDF_PREDICATE_LINK]
);
let data = b"9876543210";
let (version, new_processed_files, new_files_map) = unwrap!(safe.files_container_add_from_raw(
data,
&format!("{}{}", xorurl, new_filename),
true,
false,
false
));
assert_eq!(version, 2);
assert_eq!(new_processed_files.len(), 1);
assert_eq!(new_files_map.len(), 3);
assert_eq!(new_processed_files[new_filename].0, CONTENT_UPDATED_SIGN);
assert_eq!(
new_processed_files[new_filename].1,
new_files_map[new_filename][FAKE_RDF_PREDICATE_LINK]
);
}