feat: native folder picker (Explorer) + Synth liet ke VSTi da scan + fix VU leak (ban 1.1.4)

1) Folder picker dung WINDOW EXPLORER (khong prompt nhap tay):
   - src-tauri/src/lib.rs: IPC bridge — thread watcher ipc/pick_dir.request
     -> run_on_main_thread -> dialog().file().blocking_pick_folder()
     (tauri-plugin-dialog = IFileDialog/Explorer) -> ghi pick_dir.response;
     ghi marker tauri_bridge_ready luc setup.
   - app/api/v1/plugins.py: POST /pick-dir (def sync -> threadpool, khong
     auth) — uu tien Tauri bridge, fallback PowerShell FolderBrowserDialog
     (Win) / osascript (macOS) / zenity-kdialog (Linux).
   - app.jsx pickPluginFolder: 1) pickPluginDir (native) -> 2) __TAURI__
     invoke -> 3) in-app browser -> 4) prompt (cuoi cung).

2) Nut Synth liet ke VSTi da scan (truoc day rong):
   - Root: list_available() chi quet settings.VST_DIR (mac dinh
     /opt/daw_engine/vst3) trong khi Plugin Manager scan plugin_dirs user.
   - plugins.py list_plugins: gop _scan_vst_in_dirs(plugin_dirs) (file
     .vst3/.dll/.so + folder X.vst3 Windows).
   - vst_engine.py: PluginManager.extra_vst_dirs + _scan_plugins quet them
     (ca folder .vst3) + get_plugin_manager doc plugin_dirs.json -> load_vst
     tim thay plugin user scan khi render.

3) VU meter leak: section-tab play -> sang MAIN SESSION -> track MAIN van
   animate theo am section.
   - Root: VU tick fallback _sub_ (sub-node section co analyser) chay cho
     ca canvas MAIN; startSubTabPlayback ghi node o key track MAIN.
   - Fix: fallback _sub_ chi ap dung cho canvas SECTION (isSessVu); 2 trigger
     piano-roll them prefix _sess_ theo st.parent_tab_id.

Verify: 86 tests pass; engine frozen STARTUP 1.35s, 1 engine, 0 spawn '-c';
pick-dir IPC mock tra dung path; scan VST user dirs OK.
This commit is contained in:
2026-08-09 13:05:53 +00:00
parent 5ae4fd6149
commit cc8b286f6c
7 changed files with 251 additions and 36 deletions
+48
View File
@@ -11,6 +11,7 @@
// Ngoài ra lib.rs còn dò THÊM các vị trí fallback (legacy/portable/dev)
// và ghi đầy đủ diagnostic vào %APPDATA%/SonicForgeDAW/logs/spawn.log.
use tauri::Manager;
use tauri_plugin_dialog::DialogExt;
use tauri_plugin_shell::process::CommandChild;
use tauri_plugin_shell::ShellExt;
use std::path::{Path, PathBuf};
@@ -155,6 +156,53 @@ pub fn run() {
app.manage(EngineProcess(Mutex::new(None)));
}
}
// ── Native folder picker bridge (folder picker cho Plugin Manager) ──
// UI chay tren http://127.0.0.1:8000 (engine) — KHONG co __TAURI__
// (WebView2 cung khong ho tro window.prompt) → engine goi qua file
// IPC: engine ghi ipc/pick_dir.request → thread nay mo NATIVE dialog
// (IFileDialog/Explorer — tauri-plugin-dialog) → ghi ket qua vao
// ipc/pick_dir.response → engine tra ve cho frontend.
let data_root = std::env::var("APPDATA").unwrap_or_else(|_| ".".into());
let ipc_dir = std::path::Path::new(&data_root)
.join("SonicForgeDAW")
.join("ipc");
if let Ok(()) = std::fs::create_dir_all(&ipc_dir) {
// Marker: engine biet bridge ton tai (khong phai chay standalone)
let _ = std::fs::write(ipc_dir.join("tauri_bridge_ready"), "1");
}
let ipc_dir_for_thread = ipc_dir.clone();
let app_handle = app.handle().clone();
std::thread::spawn(move || {
loop {
let req = ipc_dir_for_thread.join("pick_dir.request");
if req.exists() {
let _ = std::fs::remove_file(&req);
let resp = ipc_dir_for_thread.join("pick_dir.response");
let _ = std::fs::remove_file(&resp);
// Dialog phai chay tren main thread (GTK/Windows message loop)
let handle = app_handle.clone();
let resp_for_main = resp.clone();
let _ = handle.run_on_main_thread(move || {
let picked: Option<PathBuf> = handle
.dialog()
.file()
.blocking_pick_folder();
let val = picked
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
let _ = std::fs::write(&resp_for_main, val);
});
// Cho den khi co response (user co the de dialog mo lau)
let mut waited_ms = 0u32;
while !resp.exists() && waited_ms < 300_000 {
std::thread::sleep(std::time::Duration::from_millis(100));
waited_ms += 100;
}
}
std::thread::sleep(std::time::Duration::from_millis(150));
}
});
Ok(())
})
.on_window_event(|window, event| {