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:
@ -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())
|
||||||
|
@ -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()) {
|
||||||
|
@ -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}"`
|
||||||
},
|
},
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user