solar-s3 0.1.1

Solar-s3 this is the first version of the package that allows you to work with the file system as with S3 storage.
Documentation
use std::collections::hash_map::DefaultHasher;
use std::ffi::OsStr;
use std::hash::{Hash, Hasher};
use std::io::{Error, ErrorKind, Result};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::str;
use std::time::UNIX_EPOCH;

use byte_unit::Byte;
use chrono::Utc;
use futures::future::{BoxFuture, FutureExt};
use mime_guess::mime;
use regex::Regex;
use tokio::fs;
use xattr::{get, remove, set};

use crate::storage::space::Space;

pub struct Size {
	pub available_space: u64,
	pub usage_space: u64,
}

pub struct FsItem {
	pub name: String,
	pub hash: String,
	pub size: String,
	pub file_type: String,
	pub mime_type: String,
	pub is_dir: bool,
	pub is_favorite: bool,
	pub is_delete: bool,
	pub see_time: u64,
	pub delete_at: Option<u64>,
	pub buffer: Option<Vec<u8>>,
}

pub struct PropertiesItem {
	pub name: String,
	pub hash: String,
	pub is_dir: bool,
	pub owner: u64,
	pub create_at: u64,
	pub update_at: u64,
	pub see_time: u64,
	pub is_favorite: bool,
	pub description: String,
	pub is_delete: bool,
	pub size: u64,
}

#[derive(Debug)]
pub struct FsMetadata {
	path: PathBuf,
	base_path: String,
	pub is_dir: bool,
}

impl FsMetadata {
	pub async fn new(path: impl AsRef<Path>) -> Result<FsMetadata> {
		let path_regex = Regex::new(r".*([\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})(/files/)").unwrap();
		let base_path = path_regex.replace(path.as_ref().to_str().unwrap(), "");

		return Ok(FsMetadata {
			path: path.as_ref().to_path_buf(),
			is_dir: fs::metadata(&path).await?.is_dir(),
			base_path: base_path.to_string(),
		});
	}

	pub async fn increase_size(&self, add_size: u64) -> Result<u64> {
		let current_size = &self.get_space().await.unwrap();

		let new_size = current_size.usage_space + add_size;
		if new_size > current_size.available_space {
			return Err(Error::new(ErrorKind::StorageFull, "There is no free space to Add file!"));
		}
		set(&self.path, "user.usage_space", new_size.to_string().as_bytes());

		return Ok(new_size);
	}

	pub async fn decrease_size(&self, delete_size: u64) -> Result<u64> {
		let current_size = &self.get_space().await.unwrap();

		let new_size = current_size.usage_space - delete_size;
		set(&self.path, "user.usage_space", new_size.to_string().as_bytes());
		return Ok(new_size);
	}

	pub async fn set_delete(&self) -> Result<i64> {
		let delete_at = Utc::now().timestamp();

		set(&self.path, "user.is_delete", "true".as_bytes())?;
		set(&self.path, "user.delete_time", delete_at.to_string().as_bytes())?;

		return Ok(delete_at);
	}

	pub async fn restore_delete(&self) -> Result<()> {
		remove(&self.path, "user.is_delete")?;
		remove(&self.path, "user.delete_time")?;
		return Ok(());
	}

	pub async fn get_delete_time(&self) -> Result<Option<i64>> {
		const IS_DELETE: i64 = 0;

		let metadata_delete_value = get(&self.path, "user.delete_time")
			.unwrap_or(Some(IS_DELETE.to_string().as_bytes().to_vec()))
			.unwrap_or(IS_DELETE.to_string().as_bytes().to_vec());

		let metadata_value = str::from_utf8(&metadata_delete_value)
			.unwrap()
			.parse::<i64>()
			.unwrap();

		if metadata_value != 0 {
			return Ok(Some(metadata_value));
		}

		return Ok(None);
	}

	pub async fn set_available_space(&self, available_space: u64) -> Result<()> {
		set(&self.path, "user.available_space", available_space.to_string().as_bytes());
		return Ok(());
	}

	pub async fn get_space(&self) -> Result<Size> {
		const AVAILABLE_SPACE: u64 = 0;
		const NONE_MSG: &str = "none_space";

		if !&self.is_dir {
			return Err(Error::new(ErrorKind::NotADirectory, "This is not a directory!"));
		}

		let path = PathBuf::from(&self.path);

		let available_space = get(&path, "user.available_space")
			.unwrap_or(Some(AVAILABLE_SPACE.to_string().as_bytes().to_vec()))
			.unwrap_or(AVAILABLE_SPACE.to_string().as_bytes().to_vec());
		let available_space = str::from_utf8(&available_space).unwrap();


		let usage_space = get(&path, "user.usage_space")
			.unwrap_or(Some(NONE_MSG.as_bytes().to_vec()))
			.unwrap_or(NONE_MSG.as_bytes().to_vec());
		let usage_space = str::from_utf8(&usage_space).unwrap();

		if usage_space == NONE_MSG {
			let size = Space::dir_size(&path).await;
			set(&path, "user.usage_space", size.to_string().as_bytes());
			return Ok(Size {
				available_space: available_space.parse::<u64>().unwrap(),
				usage_space: size,
			});
		}

		return Ok(Size {
			available_space: available_space.parse::<u64>().unwrap(),
			usage_space: usage_space.parse::<u64>().unwrap(),
		});
	}

	pub async fn is_delete(&self) -> Result<bool> {
		const IS_DELETE: &str = "false";

		let metadata_vec_value = get(&self.path, "user.is_delete")
			.unwrap_or(Some(IS_DELETE.as_bytes().to_vec()))
			.unwrap_or(IS_DELETE.as_bytes().to_vec());
		let metadata_value = str::from_utf8(&metadata_vec_value).unwrap();

		return Ok(metadata_value != IS_DELETE);
	}

	pub async fn set_favorite(&self) -> Result<()> {
		set(&self.path, "user.is_favorite", "true".to_string().as_bytes());

		return Ok(());
	}

	pub async fn unset_favorite(&self) -> Result<()> {
		set(&self.path, "user.is_favorite", "false".to_string().as_bytes());
		return Ok(());
	}

	pub async fn is_favorite(&self) -> Result<bool> {
		const IS_FAVORITE: &str = "false";

		let metadata_vec_value = get(&self.path, "user.is_favorite")
			.unwrap_or(Some(IS_FAVORITE.as_bytes().to_vec()))
			.unwrap_or(IS_FAVORITE.as_bytes().to_vec());
		let metadata_value = str::from_utf8(&metadata_vec_value).unwrap();

		return Ok(metadata_value != IS_FAVORITE);
	}

	pub async fn info(&self) -> Result<FsItem> {
		let metadata = fs::metadata(&self.path).await?;
		let byte = Byte::from_bytes(metadata.len() as u128);
		let size = byte.get_appropriate_unit(true);

		let ext = Path::new(&self.path)
			.extension()
			.unwrap_or(OsStr::new(""))
			.to_str()
			.unwrap_or("");

		let file_type = mime_guess::from_path(&self.path)
			.first()
			.unwrap_or(mime::TEXT_PLAIN)
			.to_string();

		let delete_at = self.get_delete_time().await.unwrap().unwrap_or(0);
		return Ok(FsItem {
			name: self.path.file_name().unwrap().to_str().unwrap().to_string(),
			hash: FsMetadata::calculate_hash(self.base_path.as_ref()),
			size: size.to_string(),
			file_type: ext.to_string(),
			mime_type: file_type,
			is_dir: metadata.is_dir(),
			is_delete: self.is_delete().await.unwrap(),
			is_favorite: self.is_favorite().await?,
			see_time: metadata.atime() as u64,
			delete_at: Some(delete_at as u64),
			buffer: None,
		});
	}

	fn move_dir<'a>(path_from: &'a PathBuf, path_to: &'a PathBuf) -> BoxFuture<'a, ()> {
		async move {
			let mut dir = fs::read_dir(&path_from).await.unwrap();

			while let Some(item) = dir.next_entry().await.unwrap() {
				let item_path_to = Path::new(path_to).join(&item.file_name());
				if item.metadata().await.unwrap().is_dir() {
					fs::create_dir_all(&item_path_to).await.unwrap();
					FsMetadata::move_dir(&item.path(), &item_path_to).await;
					continue;
				}
				fs::copy(&item.path(), &item_path_to).await.unwrap();
			}
		}.boxed()
	}

	pub async fn move_path(&self, path_to: impl AsRef<Path>, is_delete: bool) -> Result<()> {
		if self.is_dir {
			fs::create_dir_all(&path_to).await?;
			FsMetadata::move_dir(&self.path, &path_to.as_ref().to_path_buf()).await;
		} else {
			fs::copy(&self.path, &path_to).await?;
		}

		if is_delete {
			self.remove().await?
		}

		return Ok(());
	}

	pub async fn remove(&self) -> Result<()> {
		let metadata = fs::metadata(&self.path).await?;

		if metadata.is_dir() {
			fs::remove_dir_all(&self.path).await?;
			return Ok(());
		}

		fs::remove_file(&self.path).await?;
		return Ok(());
	}

	pub async fn get_properties(&self) -> Result<PropertiesItem> {
		let path_metadata = fs::metadata(&self.path).await.unwrap();
		let create_at = path_metadata
			.created()
			.unwrap()
			.duration_since(UNIX_EPOCH).unwrap()
			.as_secs();
		let update_at = path_metadata
			.modified()
			.unwrap()
			.duration_since(UNIX_EPOCH)
			.unwrap()
			.as_secs();

		return Ok(PropertiesItem {
			name: self.path.file_name().unwrap().to_str().unwrap().to_string(),
			hash: FsMetadata::calculate_hash(self.base_path.as_ref()),
			is_dir: self.is_dir,
			owner: 0,
			size: path_metadata.size(),
			create_at,
			update_at,
			is_delete: self.is_delete().await.unwrap(),
			is_favorite: self.is_favorite().await.unwrap(),
			see_time: path_metadata.atime() as u64,
			description: "".to_string(),
		})
	}

	pub fn calculate_hash(key: &str) -> String {
		let mut s = DefaultHasher::new();
		key.hash(&mut s);
		let hash = s.finish();
		return format!("{hash:X}");
	}
}