use crate::errors::{FilesystemError, OtherFilesystemErrorKind as OtherErr};
use crate::filesystem::*;
use crate::itch_api::types::UploadID;
use std::path::{Path, PathBuf};
pub const UPLOAD_ARCHIVE_NAME: &str = "download";
pub const COVER_IMAGE_DEFAULT_FILENAME: &str = "cover.png";
pub const GAME_FOLDER: &str = "Games";
pub fn get_upload_folder(game_folder: &Path, upload_id: UploadID) -> PathBuf {
game_folder.join(format!("{upload_id}"))
}
pub fn get_upload_archive_path(
game_folder: &Path,
upload_id: UploadID,
upload_filename: &str,
) -> PathBuf {
game_folder.join(format!(
"{upload_id}-{UPLOAD_ARCHIVE_NAME}-{upload_filename}"
))
}
pub fn add_part_extension(file: &Path) -> Result<PathBuf, FilesystemError> {
let filename = get_file_name(file)?;
Ok(file.with_file_name(format!("{filename}.part")))
}
pub fn get_game_folder(game_title: &str) -> Result<PathBuf, FilesystemError> {
let mut game_folder = get_basedirs()?.home_dir().join(GAME_FOLDER);
game_folder.push(game_title);
Ok(game_folder)
}
pub async fn remove_folder_if_empty(folder: &Path) -> Result<bool, FilesystemError> {
let true = is_folder_empty(folder).await? else {
return Ok(false);
};
remove_empty_dir(folder).await?;
Ok(true)
}
pub async fn remove_folder_safely(path: &Path) -> Result<(), FilesystemError> {
let canonical = get_canonical_path(path).await?;
let home = get_canonical_path(get_basedirs()?.home_dir()).await?;
if canonical == home {
return Err(OtherErr::RefusingToRemoveFolder(canonical).into());
}
remove_dir_all(&canonical).await
}
async fn copy_dir_all(from: PathBuf, to: PathBuf) -> Result<(), FilesystemError> {
ensure_is_dir(&from).await?;
create_dir(&to).await?;
let mut queue: std::collections::VecDeque<(PathBuf, PathBuf)> = std::collections::VecDeque::new();
queue.push_back((from, to));
while let Some((from, to)) = queue.pop_front() {
let mut entries = read_dir(&from).await?;
while let Some(entry) = next_entry(&mut entries, &from).await? {
let from_path = entry.path();
let to_path = to.join(entry.file_name());
if file_type(&entry, &from).await?.is_dir() {
create_dir(&to).await?;
queue.push_back((from_path, to_path));
} else {
copy_file(&from_path, &to_path).await?;
}
}
}
Ok(())
}
pub async fn move_folder(from: &Path, to: &Path) -> Result<(), FilesystemError> {
ensure_is_dir(from).await?;
create_dir(to).await?;
match rename(from, to).await {
Ok(()) => Ok(()),
Err(FilesystemError::IOError { error, .. })
if error.kind() == tokio::io::ErrorKind::CrossesDevices =>
{
copy_dir_all(from.to_owned(), to.to_owned()).await?;
remove_folder_safely(from).await?;
Ok(())
}
Err(e) => Err(e),
}
}
pub async fn find_available_path(path: &Path) -> Result<PathBuf, FilesystemError> {
let parent = parent(path)?;
let filename = get_file_name(path)?;
let mut i = 0;
loop {
let current_filename = format!("{filename}{i:x}");
let current_path: PathBuf = parent.join(current_filename);
if !exists(¤t_path).await? {
return Ok(current_path);
}
i += 1;
}
}
async fn move_folder_child(last_root: &Path, base_folder: &Path) -> Result<(), FilesystemError> {
let mut collisions: Vec<(PathBuf, PathBuf)> = Vec::new();
let mut child_entries = read_dir(last_root).await?;
while let Some(child) = next_entry(&mut child_entries, last_root).await? {
let from = child.path();
let to = base_folder.join(child.file_name());
if exists(&to).await? {
let temporal_name: PathBuf = find_available_path(&to).await?;
rename(&from, &temporal_name).await?;
collisions.push((temporal_name, to));
} else {
rename(&from, &to).await?;
}
}
let mut current_root = last_root.to_owned();
while is_folder_empty(¤t_root).await? {
let parent = parent(¤t_root)?;
remove_empty_dir(¤t_root).await?;
current_root = parent.to_owned();
}
for (src, dst) in &collisions {
rename(src, dst).await?;
}
Ok(())
}
pub async fn remove_root_folder(folder: &Path) -> Result<(), FilesystemError> {
let mut last_root: PathBuf = folder.to_path_buf();
let mut is_there_any_root: bool = false;
loop {
let mut entries = read_dir(&last_root).await?;
let Some(first) = next_entry(&mut entries, &last_root).await? else {
break;
};
if next_entry(&mut entries, &last_root).await?.is_some()
|| file_type(&first, &last_root).await?.is_file()
{
break;
}
is_there_any_root = true;
last_root = first.path();
}
if is_there_any_root {
move_folder_child(&last_root, folder).await?;
}
Ok(())
}