#![feature(io_error_more)]
#![allow(unused_assignments)]
use std::{path::{Path}};
use lazy_regex::{regex_find, regex_replace, regex_is_match};
mod errors;
pub use errors::FileError;
use rialight_temporal::{Local};
use rialight_util::{encode_uri, decode_uri};
#[doc(hidden)]
pub static mut __APP_DIRECTORY: Option<String> = None;
#[doc(hidden)]
pub static mut __APP_STORAGE_DIRECTORY: Option<String> = None;
#[derive(Clone, Eq, PartialEq)]
pub struct File {
m_path: String,
m_scheme: FileScheme,
}
#[derive(Clone, Eq, PartialEq)]
enum FileScheme {
File,
App,
AppStorage,
}
macro_rules! browser_behavior {
{$s:expr} => {
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
{ $s }
};
{$s:item} => {
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
$s
};
}
macro_rules! host_os_behavior {
{$s:expr} => {
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
{ $s }
};
{$s:item} => {
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
$s
};
}
impl FileScheme {
pub fn prefix(&self) -> &str {
match self {
FileScheme::File => "file:",
FileScheme::App => "app:",
FileScheme::AppStorage => "app-storage:",
}
}
}
fn std_io_error_to_file_error(arg: std::io::Error) -> FileError {
match arg.kind() {
std::io::ErrorKind::NotFound => FileError::NotFound,
std::io::ErrorKind::PermissionDenied => FileError::PermissionDenied,
std::io::ErrorKind::StorageFull => FileError::StorageFull,
std::io::ErrorKind::FileTooLarge => FileError::FileTooLarge,
std::io::ErrorKind::NotADirectory => FileError::NotADirectory,
std::io::ErrorKind::InvalidFilename => FileError::InvalidFilename,
_ => FileError::UnassignedError,
}
}
impl File {
pub fn new(url_or_path: impl AsRef<str>) -> Self {
let url_or_path = url_or_path.as_ref().to_owned();
let mut path = String::from("");
let mut scheme: FileScheme = FileScheme::File;
if url_or_path.starts_with("file:") {
path = decode_uri(url_or_path[5..].to_owned());
} else if url_or_path.starts_with("app:") {
path = decode_uri(url_or_path[4..].to_owned());
scheme = FileScheme::App;
} else if url_or_path.starts_with("app-storage:") {
path = decode_uri(url_or_path[12..].to_owned());
scheme = FileScheme::AppStorage;
} else {
path = url_or_path.to_owned();
}
if scheme == FileScheme::File {
#[cfg(target_os = "windows")]
{
path = regex_replace!(r"^[\\/]+", path.as_ref(), |_| "").into_owned();
}
#[cfg(not(target_os = "windows"))]
{
path = regex_replace!(r"^[\\/]+", path.as_ref(), |_| "/").into_owned();
}
} else {
path = regex_replace!(r"^[\\/]+", path.as_ref(), |_| "/").into_owned();
}
if scheme == FileScheme::File {
#[cfg(not(target_os = "windows"))]
if !path.starts_with("/") {
path = "/".to_owned() + &path;
}
} else if !path.starts_with("/") {
path = "/".to_owned() + &path;
}
path = rialight_util::path::resolve(path.as_ref(), "");
File { m_scheme: scheme, m_path: path }
}
pub fn url(&self) -> String {
let path = encode_uri(self.m_path.replace("\\", "/"));
self.m_scheme.prefix().to_owned() + "//" + ®ex_replace!(r"^[\\/]+", path.as_ref(), |_| "").into_owned()
}
pub fn native_path(&self) -> String {
self.m_path.clone()
}
pub fn relative_path(&self, another: &File) -> String {
rialight_util::path::relative(self.native_path().as_ref(), another.native_path().as_ref())
}
pub fn resolve_path(&self, arg: impl AsRef<str>) -> Self {
let r = rialight_util::path::resolve(&self.m_path.clone(), arg.as_ref());
File {
m_scheme: self.m_scheme.clone(),
m_path: r,
}
}
pub fn name(&self) -> String {
if !regex_is_match!(r"[\\/]", self.native_path().as_ref()) {
return self.native_path();
}
regex_find!(r"[\\/].+$", self.native_path().as_ref()).unwrap_or("/")[1..].to_owned()
}
pub fn name_without_extension(&self, extension: impl AsRef<str>) -> String {
let s = self.name();
let extension = extension.as_ref();
if s.ends_with(extension) { s[..(s.len() - extension.len())].to_owned() } else {s}
}
pub fn extension(&self) -> String {
regex_find!(r"[\\.].+$", self.native_path().as_ref()).unwrap_or("").to_owned()
}
pub fn parent(&self) -> Option<File> {
let r = self.resolve_path("..");
let p = r.native_path();
if p.len() == 0
|| p == ".".to_owned()
|| p == "/".to_owned()
|| p == "\\".to_owned()
{
None
} else {
Some(r)
}
}
pub fn application_directory() -> Self {
File { m_scheme: FileScheme::App, m_path: String::from("") }
}
pub fn application_storage_directory() -> Self {
File { m_scheme: FileScheme::AppStorage, m_path: String::from("") }
}
pub fn downloads_directory() -> Option<File> {
if let Some(r) = dirs::download_dir() {
if let Some(r) = r.to_str() {
return Some(File::new(r));
}
}
None
}
pub fn documents_directory() -> Option<File> {
if let Some(r) = dirs::document_dir() {
if let Some(r) = r.to_str() {
return Some(File::new(r));
}
}
None
}
pub fn executable_directory() -> Option<File> {
if let Some(r) = dirs::executable_dir() {
if let Some(r) = r.to_str() {
return Some(File::new(r));
}
}
None
}
pub fn user_directory() -> Option<File> {
if let Some(r) = dirs::home_dir() {
if let Some(r) = r.to_str() {
return Some(File::new(r));
}
}
None
}
pub fn pictures_directory() -> Option<File> {
if let Some(r) = dirs::picture_dir() {
if let Some(r) = r.to_str() {
return Some(File::new(r));
}
}
None
}
pub fn videos_directory() -> Option<File> {
if let Some(r) = dirs::video_dir() {
if let Some(r) = r.to_str() {
return Some(File::new(r));
}
}
None
}
pub fn working_directory() -> Option<File> {
if let Ok(r) = std::env::current_dir() {
if let Some(r) = r.to_str() {
return Some(File::new(r));
}
}
None
}
fn internal_native_path(&self) -> String {
if self.m_scheme == FileScheme::App {
let l = File::new(unsafe {__APP_DIRECTORY.clone()}.unwrap_or("".to_owned()));
let r = regex_replace!(r"^[\\/]", self.native_path().as_ref(), |_| "").to_owned().to_string();
l.resolve_path(r).native_path().clone()
} else if self.m_scheme == FileScheme::AppStorage {
let l = File::new(unsafe {__APP_STORAGE_DIRECTORY.clone()}.unwrap_or("".to_owned()));
let r = regex_replace!(r"^[\\/]", self.native_path().as_ref(), |_| "").to_owned().to_string();
l.resolve_path(r).native_path().clone()
} else {
self.native_path().clone()
}
}
pub fn exists(&self) -> bool {
host_os_behavior! {{
return Path::new(&self.internal_native_path()).exists();
}}
browser_behavior! {{
return false;
}}
}
pub fn is_directory(&self) -> bool {
host_os_behavior! {{
return Path::new(&self.internal_native_path()).is_dir();
}}
browser_behavior! {{
return false;
}}
}
pub fn is_file(&self) -> bool {
host_os_behavior! {{
return Path::new(&self.internal_native_path()).is_file();
}}
browser_behavior! {{
return false;
}}
}
pub fn is_symbolic_link(&self) -> bool {
host_os_behavior! {{
return Path::new(&self.internal_native_path()).is_symlink();
}}
browser_behavior! {{
return false;
}}
}
pub fn canonicalize(&self) -> File {
host_os_behavior! {{
if let Ok(r) = Path::new(&self.internal_native_path()).canonicalize() {
if let Some(r) = r.to_str() {
return File { m_scheme: FileScheme::File, m_path: r.to_owned() };
}
}
return self.clone();
}}
browser_behavior! {{
return self.clone();
}}
}
pub async fn canonicalize_async(&self) -> File {
host_os_behavior! {{
if let Ok(r) = tokio::fs::canonicalize(&self.internal_native_path()).await {
if let Some(r) = r.to_str() {
return File { m_scheme: FileScheme::File, m_path: r.to_owned() };
}
}
return self.clone();
}}
browser_behavior! {{
return self.clone();
}}
}
pub fn copy_to(&self, new_location: &File) -> Result<(), FileError> {
host_os_behavior! {{
return match std::fs::copy(&self.internal_native_path(), new_location.internal_native_path()) {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn copy_to_async(&self, new_location: &File) -> Result<(), FileError> {
host_os_behavior! {{
return match tokio::fs::copy(&self.internal_native_path(), new_location.internal_native_path()).await {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn create_directory(&self) -> Result<(), FileError> {
host_os_behavior! {{
return match std::fs::create_dir_all(&self.internal_native_path()) {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn create_directory_async(&self) -> Result<(), FileError> {
host_os_behavior! {{
return match tokio::fs::create_dir_all(&self.internal_native_path()).await {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn read_bytes(&self) -> Result<Vec<u8>, FileError> {
host_os_behavior! {{
return match std::fs::read(&self.internal_native_path()) {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(r) => Ok(r),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn read_bytes_async(&self) -> Result<Vec<u8>, FileError> {
host_os_behavior! {{
return match tokio::fs::read(&self.internal_native_path()).await {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(r) => Ok(r),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn read_utf8(&self) -> Result<String, FileError> {
host_os_behavior! {{
return match std::fs::read_to_string(&self.internal_native_path()) {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(r) => Ok(r),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn read_utf8_async(&self) -> Result<String, FileError> {
host_os_behavior! {{
return match tokio::fs::read_to_string(&self.internal_native_path()).await {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(r) => Ok(r),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn get_directory_listing(&self) -> Result<Vec<File>, FileError> {
host_os_behavior! {{
let mut r = Vec::<File>::new();
let read_dir_iter = match std::fs::read_dir(self.internal_native_path()) {
Err(e) => {
return Err(std_io_error_to_file_error(e));
},
Ok(r) => r,
};
for entry in read_dir_iter {
let entry = match entry {
Err(e) => {
return Err(std_io_error_to_file_error(e));
},
Ok(r) => r,
};
let path = entry.path();
let path = path.to_str();
if let Some(path) = path {
r.push(File::new(path));
}
}
return Ok(r.clone());
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn delete_empty_directory(&self) -> Result<(), FileError> {
host_os_behavior! {{
return match std::fs::remove_dir(self.internal_native_path()) {
Ok(_) => Ok(()),
Err(e) => Err(std_io_error_to_file_error(e)),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn delete_empty_directory_async(&self) -> Result<(), FileError> {
host_os_behavior! {{
return match tokio::fs::remove_dir(self.internal_native_path()).await {
Ok(_) => Ok(()),
Err(e) => Err(std_io_error_to_file_error(e)),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn delete_all_directory(&self) -> Result<(), FileError> {
host_os_behavior! {{
return match std::fs::remove_dir_all(self.internal_native_path()) {
Ok(_) => Ok(()),
Err(e) => Err(std_io_error_to_file_error(e)),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn delete_all_directory_async(&self) -> Result<(), FileError> {
host_os_behavior! {{
return match tokio::fs::remove_dir_all(self.internal_native_path()).await {
Ok(_) => Ok(()),
Err(e) => Err(std_io_error_to_file_error(e)),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn delete_file(&self) -> Result<(), FileError> {
host_os_behavior! {{
if self.is_directory() {
return Err(FileError::NotAFile);
}
return match std::fs::remove_file(self.internal_native_path()) {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn delete_file_async(&self) -> Result<(), FileError> {
host_os_behavior! {{
if self.is_directory() {
return Err(FileError::NotAFile);
}
return match tokio::fs::remove_file(self.internal_native_path()).await {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn move_to(&self, to: &File) -> Result<(), FileError> {
host_os_behavior! {{
return match std::fs::rename(self.internal_native_path(), to.internal_native_path()) {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn move_to_async(&self, to: &File) -> Result<(), FileError> {
host_os_behavior! {{
return match tokio::fs::rename(self.internal_native_path(), to.internal_native_path()).await {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn rename(&self, to: &File) -> Result<(), FileError> {
self.move_to(to)
}
pub async fn rename_async(&self, to: &File) -> Result<(), FileError> {
self.move_to_async(to).await
}
pub fn write<B: AsRef<[u8]>>(&self, b: B) -> Result<(), FileError> {
host_os_behavior! {{
return match std::fs::write(self.internal_native_path(), b.as_ref()) {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn write_async<B: AsRef<[u8]>>(&self, b: B) -> Result<(), FileError> {
host_os_behavior! {{
return match tokio::fs::write(self.internal_native_path(), b.as_ref()).await {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(_) => Ok(()),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn creation_date(&self) -> Result<rialight_temporal::DateTime<Local>, FileError> {
host_os_behavior! {{
match std::fs::metadata(self.internal_native_path()) {
Ok(metadata) => match metadata.created() {
Ok(st) => Ok(rialight_temporal::DateTime::from(st)),
Err(e) => Err(std_io_error_to_file_error(e)),
},
Err(e) => Err(std_io_error_to_file_error(e)),
}
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn creation_date_async(&self) -> Result<rialight_temporal::DateTime<Local>, FileError> {
host_os_behavior! {{
match tokio::fs::metadata(self.internal_native_path()).await {
Ok(metadata) => match metadata.created() {
Ok(st) => Ok(rialight_temporal::DateTime::from(st)),
Err(e) => Err(std_io_error_to_file_error(e)),
},
Err(e) => Err(std_io_error_to_file_error(e)),
}
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn modification_date(&self) -> Result<rialight_temporal::DateTime<Local>, FileError> {
host_os_behavior! {{
match std::fs::metadata(self.internal_native_path()) {
Ok(metadata) => match metadata.modified() {
Ok(st) => Ok(rialight_temporal::DateTime::from(st)),
Err(e) => Err(std_io_error_to_file_error(e)),
},
Err(e) => Err(std_io_error_to_file_error(e)),
}
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn modification_date_async(&self) -> Result<rialight_temporal::DateTime<Local>, FileError> {
host_os_behavior! {{
match tokio::fs::metadata(self.internal_native_path()).await {
Ok(metadata) => match metadata.modified() {
Ok(st) => Ok(rialight_temporal::DateTime::from(st)),
Err(e) => Err(std_io_error_to_file_error(e)),
},
Err(e) => Err(std_io_error_to_file_error(e)),
}
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub fn size(&self) -> Result<i64, FileError> {
host_os_behavior! {{
return match std::fs::metadata(self.internal_native_path()) {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(metadata) => Ok(metadata.len() as i64),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
pub async fn size_async(&self) -> Result<i64, FileError> {
host_os_behavior! {{
return match tokio::fs::metadata(self.internal_native_path()).await {
Err(e) => Err(std_io_error_to_file_error(e)),
Ok(metadata) => Ok(metadata.len() as i64),
};
}}
browser_behavior! {{
return Err(FileError::UnassignedError);
}}
}
}
#[cfg(test)]
mod test {
use super::File;
#[test]
fn path_creation() {
let file = File::new("foo");
assert_eq!(file.native_path().as_str(), "foo");
assert_eq!(File::new("app://foo.txt").name(), "foo.txt".to_owned());
assert_eq!(File::new("app://foo.txt").name_without_extension(".txt"), "foo".to_owned());
assert_eq!(File::new("app://foo.txt").extension(), ".txt".to_owned());
}
#[test]
fn path_relativity() {
let path = File::new("app://foo/bar").relative_path(&File::new("app://qux/foo"));
assert_eq!(path.as_str(), "../../qux/foo");
let file = File::new("app://foo/bar").resolve_path("zxc/../abc");
assert_eq!(file.url().as_str(), "app://foo/bar/abc");
}
}