Fix rename project directory (#2451)

* make rust function with lots of tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* pull thru function to tauri and app

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* one more test;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2024-05-21 19:23:43 -07:00
committed by GitHub
parent 440eb2636a
commit bcca736a8d
4 changed files with 154 additions and 7 deletions

View File

@ -25,6 +25,15 @@ const SETTINGS_FILE_NAME: &str = "settings.toml";
const PROJECT_SETTINGS_FILE_NAME: &str = "project.toml"; const PROJECT_SETTINGS_FILE_NAME: &str = "project.toml";
const PROJECT_FOLDER: &str = "zoo-modeling-app-projects"; const PROJECT_FOLDER: &str = "zoo-modeling-app-projects";
#[tauri::command]
async fn rename_project_directory(project_path: &str, new_name: &str) -> Result<PathBuf, InvokeError> {
let project_dir = std::path::Path::new(project_path);
kcl_lib::settings::types::file::rename_project_directory(project_dir, new_name)
.await
.map_err(InvokeError::from_anyhow)
}
#[tauri::command] #[tauri::command]
fn get_initial_default_dir(app: tauri::AppHandle) -> Result<PathBuf, InvokeError> { fn get_initial_default_dir(app: tauri::AppHandle) -> Result<PathBuf, InvokeError> {
let dir = match app.path().document_dir() { let dir = match app.path().document_dir() {
@ -389,6 +398,7 @@ fn main() -> Result<()> {
write_app_settings_file, write_app_settings_file,
read_project_settings_file, read_project_settings_file,
write_project_settings_file, write_project_settings_file,
rename_project_directory,
]) ])
.plugin(tauri_plugin_cli::init()) .plugin(tauri_plugin_cli::init())
.plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_deep_link::init())

View File

@ -26,6 +26,13 @@ export async function setState(state: ProjectState | undefined): Promise<void> {
return await invoke('set_state', { state }) return await invoke('set_state', { state })
} }
export async function renameProjectDirectory(
projectPath: string,
newName: string
): Promise<string> {
return invoke<string>('rename_project_directory', { projectPath, newName })
}
// Get the initial default dir for holding all projects. // Get the initial default dir for holding all projects.
export async function getInitialDefaultDir(): Promise<string> { export async function getInitialDefaultDir(): Promise<string> {
if (!isTauri()) { if (!isTauri()) {

View File

@ -1,5 +1,5 @@
import { FormEvent, useEffect } from 'react' import { FormEvent, useEffect } from 'react'
import { remove, rename } from '@tauri-apps/plugin-fs' import { remove } from '@tauri-apps/plugin-fs'
import { import {
getNextProjectIndex, getNextProjectIndex,
interpolateProjectNameWithIndex, interpolateProjectNameWithIndex,
@ -35,7 +35,11 @@ import { useLspContext } from 'components/LspProvider'
import { useRefreshSettings } from 'hooks/useRefreshSettings' import { useRefreshSettings } from 'hooks/useRefreshSettings'
import { LowerRightControls } from 'components/LowerRightControls' import { LowerRightControls } from 'components/LowerRightControls'
import { Project } from 'wasm-lib/kcl/bindings/Project' import { Project } from 'wasm-lib/kcl/bindings/Project'
import { createNewProjectDirectory, listProjects } from 'lib/tauri' import {
createNewProjectDirectory,
listProjects,
renameProjectDirectory,
} from 'lib/tauri'
// This route only opens in the Tauri desktop context for now, // This route only opens in the Tauri desktop context for now,
// as defined in Router.tsx, so we can use the Tauri APIs and types. // as defined in Router.tsx, so we can use the Tauri APIs and types.
@ -122,10 +126,9 @@ const Home = () => {
name = interpolateProjectNameWithIndex(name, nextIndex) name = interpolateProjectNameWithIndex(name, nextIndex)
} }
await rename( await renameProjectDirectory(
await join(context.defaultDirectory, oldName), await join(context.defaultDirectory, oldName),
await join(context.defaultDirectory, name), name
{}
) )
return `Successfully renamed "${oldName}" to "${name}"` return `Successfully renamed "${oldName}" to "${name}"`
}, },

View File

@ -163,8 +163,7 @@ impl ProjectRoute {
{ {
// Get the project name. // Get the project name.
if let Some(project_name) = path if let Some(project_name) = path
.strip_prefix(&configuration.settings.project.directory) .strip_prefix(&configuration.settings.project.directory)?
.unwrap()
.iter() .iter()
.next() .next()
{ {
@ -347,6 +346,39 @@ where
Ok(default_file.display().to_string()) Ok(default_file.display().to_string())
} }
#[cfg(not(target_arch = "wasm32"))]
/// Rename a directory for a project.
/// This returns the new path of the directory.
pub async fn rename_project_directory<P>(path: P, new_name: &str) -> Result<std::path::PathBuf>
where
P: AsRef<Path> + Send,
{
if new_name.is_empty() {
return Err(anyhow::anyhow!("New name for project cannot be empty"));
}
// Make sure the path is a directory.
if !path.as_ref().is_dir() {
return Err(anyhow::anyhow!("Path `{}` is not a directory", path.as_ref().display()));
}
// Make sure the new name does not exist.
let new_path = path
.as_ref()
.parent()
.ok_or_else(|| anyhow::anyhow!("Parent directory of `{}` not found", path.as_ref().display()))?
.join(new_name);
if new_path.exists() {
return Err(anyhow::anyhow!(
"Path `{}` already exists, cannot rename to an existing path",
new_path.display()
));
}
tokio::fs::rename(path.as_ref(), &new_path).await?;
Ok(new_path)
}
/// Information about a file or directory. /// Information about a file or directory.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)] #[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
#[ts(export)] #[ts(export)]
@ -722,4 +754,99 @@ mod tests {
); );
std::fs::remove_dir_all(dir).unwrap(); std::fs::remove_dir_all(dir).unwrap();
} }
#[tokio::test]
async fn test_rename_project_directory_empty_dir() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let new_dir = super::rename_project_directory(&dir, &new_name).await.unwrap();
assert_eq!(new_dir, std::env::temp_dir().join(&new_name));
std::fs::remove_dir_all(new_dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_empty_name() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
let result = super::rename_project_directory(&dir, "").await;
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "New name for project cannot be empty");
std::fs::remove_dir_all(dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_non_empty_dir() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("main.kcl"), vec![]).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let new_dir = super::rename_project_directory(&dir, &new_name).await.unwrap();
assert_eq!(new_dir, std::env::temp_dir().join(&new_name));
std::fs::remove_dir_all(new_dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_non_empty_dir_recursive() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
std::fs::create_dir_all(dir.join("assembly")).unwrap();
std::fs::write(dir.join("assembly").join("main.kcl"), vec![]).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let new_dir = super::rename_project_directory(&dir, &new_name).await.unwrap();
assert_eq!(new_dir, std::env::temp_dir().join(&new_name));
std::fs::remove_dir_all(new_dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_dir_is_file() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::write(&dir, vec![]).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let result = super::rename_project_directory(&dir, &new_name).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
format!("Path `{}` is not a directory", dir.display())
);
std::fs::remove_file(dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_new_name_exists() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let new_dir = std::env::temp_dir().join(&new_name);
std::fs::create_dir_all(&new_dir).unwrap();
let result = super::rename_project_directory(&dir, &new_name).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
format!(
"Path `{}` already exists, cannot rename to an existing path",
new_dir.display()
)
);
std::fs::remove_dir_all(new_dir).unwrap();
}
} }