diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index d58af41b..ca7393d6 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -391,6 +391,16 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + #[test] + fn saving_a_session_without_audio_creates_its_folder() { + let dir = std::env::temp_dir().join(format!("soundcraft-new-folder-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + let path = dir.join("Song").join("Song.scraft"); + Engine::default().execute("session.save_as", &json!({"path": path.to_string_lossy()})).unwrap(); + assert!(path.exists()); + let _ = std::fs::remove_dir_all(&dir); + } + fn tone_engine() -> (Engine, u64) { let mut e = Engine::default(); e.execute("track.new", &json!({"count": 1, "name": "Tone", "format": "mono"})).unwrap(); diff --git a/crates/engine/src/io.rs b/crates/engine/src/io.rs index cb3fc18b..dd9a04af 100644 --- a/crates/engine/src/io.rs +++ b/crates/engine/src/io.rs @@ -519,6 +519,7 @@ fn write_session(session: &Session, path: &str, overwrite: bool) -> Result<(Sess s.name = stem.to_string(); } let text = s.to_json().map_err(|err| EngineError::Io(err.to_string()))?; + std::fs::create_dir_all(&dir).map_err(|err| EngineError::Io(format!("{}: {err}", dir.display())))?; // Atomic-ish write: temp file then rename. let tmp = p.with_extension("scraft.tmp"); std::fs::write(&tmp, text).map_err(|err| EngineError::Io(format!("{}: {err}", tmp.display())))?; diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index d2126346..e2faaee1 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -72,8 +72,9 @@ impl Dialogs { "file.import_audio" => Dialog::PathPrompt { cmd: id.into(), title: "Import Audio".into(), path: home, key: "path".into() }, "file.import_midi" => Dialog::PathPrompt { cmd: id.into(), title: "Import MIDI".into(), path: home, key: "path".into() }, "session.open" => Dialog::PathPrompt { cmd: id.into(), title: "Open Session".into(), path: home, key: "path".into() }, - "session.save_as" | "session.save_copy" => Dialog::PathPrompt { - cmd: id.into(), + "session.save" if e.path.is_some() => return false, + "session.save" | "session.save_as" | "session.save_copy" => Dialog::PathPrompt { + cmd: if id == "session.save_copy" { id } else { "session.save_as" }.into(), title: "Save Session As".into(), path: format!("{home}/{name}/{name}.scraft"), key: "path".into(), diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 89aca578..21d4c6ee 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -149,6 +149,7 @@ const DIALOG_COMMANDS: &[&str] = &[ "file.import_audio", "file.import_midi", "session.open", + "session.save", "session.save_as", "session.save_copy", "track.group", @@ -618,4 +619,24 @@ mod tests { invoke_menu(&mut app, "ui.bounce_dialog", "File > Bounce Mix..."); assert_eq!(app.dialogs.open_name(), Some("bounce")); } + + #[test] + fn save_asks_for_a_path_until_the_session_has_one() { + let mut app = SoundApp::new(soundcraft_engine::Engine::default(), None, Services::default()); + invoke_menu(&mut app, "session.save", "File > Save Session"); + assert!(matches!(&app.dialogs.open, Some(crate::dialogs::Dialog::PathPrompt { cmd, .. }) if cmd == "session.save_as")); + app.dialogs.open = None; + assert!(invoke_shortcut_dialog(&mut app, "session.save")); + app.dialogs.open = None; + + let dir = std::env::temp_dir().join(format!("soundcraft-save-{}", std::process::id())); + let path = dir.join("Song.scraft"); + app.run("session.save_as", json!({"path": path.to_string_lossy()})).unwrap(); + app.run("track.new", json!({})).unwrap(); + assert!(!invoke_shortcut_dialog(&mut app, "session.save")); + invoke_menu(&mut app, "session.save", "File > Save Session"); + assert!(app.dialogs.open.is_none()); + assert!(!app.engine.is_dirty()); + let _ = std::fs::remove_dir_all(&dir); + } }