Projekt-Identität und -Config neu: .ai-control/config.json mit UUID und Name
- Projekt-Config zieht von ai-control.json nach .ai-control/config.json um, das Icon liegt daneben und synct mit dem Projekt. - Jedes Projekt trägt eine UUID (id) und den Anzeigenamen (name) in der Config; die UUID ist Registry-Schlüssel und project-Parameter aller Commands, der Name reine Darstellung. - Pool-Zuordnung ist maschinenlokal und liegt in der projects.json-Registry, nicht mehr in der syncbaren Projekt-Config. - open_terminal prüft die ID im Ordner gegen die Registry (verschobene oder ersetzte Ordner scheitern laut). - Import übernimmt eine mitgebrachte ID, legt Arbeitsordner und Archiv-Home an und zieht die Archiv-Permission nach. - Migration beim App-Start: Alt-Datei umziehen, Pool in die Registry, Icons aus ~/.config/ai-control/icons ins Projekt, Registry auf UUIDs. - ProjectConfig reicht unbekannte Keys per serde(flatten) durch den Round-Trip (Regressionstest config_roundtrip_erhaelt_fremde_keys).
This commit is contained in:
+26
-16
@@ -14,9 +14,9 @@ use crate::domain::pool::{
|
||||
};
|
||||
use crate::domain::project::{
|
||||
add_project_in, add_work_dir_in, assign_pool_in, create_project_full_in, delete_project_in,
|
||||
is_running, kill_terminals, list_projects_in, project_work_dirs_in, read_project_config_in,
|
||||
remove_work_dir_in, resolve_icon_path, running_projects_using_pool, set_project_dir_in,
|
||||
set_terminal_config_in, unassign_pool_in, Project, TerminalConfig,
|
||||
display_name_in, is_running, kill_terminals, list_projects_in, project_work_dirs_in,
|
||||
read_project_config_in, remove_work_dir_in, resolve_icon_path, running_projects_using_pool,
|
||||
set_project_dir_in, set_terminal_config_in, unassign_pool_in, Project, TerminalConfig,
|
||||
};
|
||||
use crate::domain::registry::unregister_project;
|
||||
use crate::domain::settings;
|
||||
@@ -41,7 +41,7 @@ pub(crate) fn create_project_full(
|
||||
create_work_dir: bool,
|
||||
terminal: TerminalConfig,
|
||||
todo: bool,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<String, String> {
|
||||
create_project_full_in(
|
||||
&Paths::real(),
|
||||
&name,
|
||||
@@ -62,18 +62,20 @@ pub(crate) fn add_project(path: String) -> Result<(), String> {
|
||||
|
||||
/// Projekt aus der Registry nehmen; der Ordner bleibt unangetastet.
|
||||
#[tauri::command]
|
||||
pub(crate) fn remove_project(name: String) -> Result<(), String> {
|
||||
if is_running(&name) {
|
||||
pub(crate) fn remove_project(project: String) -> Result<(), String> {
|
||||
if is_running(&project) {
|
||||
let name = display_name_in(&Paths::real(), &project)?;
|
||||
return Err(format!("{name} läuft noch — erst beenden"));
|
||||
}
|
||||
unregister_project(&Paths::real(), &name)
|
||||
unregister_project(&Paths::real(), &project)
|
||||
}
|
||||
|
||||
/// Projektordner neu zuordnen; bei laufender Session gesperrt.
|
||||
#[tauri::command]
|
||||
pub(crate) fn set_project_dir(project: String, dir: String) -> Result<(), String> {
|
||||
if is_running(&project) {
|
||||
return Err(format!("{project} läuft noch — erst beenden"));
|
||||
let name = display_name_in(&Paths::real(), &project)?;
|
||||
return Err(format!("{name} läuft noch — erst beenden"));
|
||||
}
|
||||
set_project_dir_in(&Paths::real(), &project, &dir)
|
||||
}
|
||||
@@ -89,11 +91,12 @@ pub(crate) fn remove_work_dir(project: String, dir: String) -> Result<(), String
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn delete_project(name: String, delete_work_dirs: bool) -> Result<(), String> {
|
||||
if is_running(&name) {
|
||||
pub(crate) fn delete_project(project: String, delete_work_dirs: bool) -> Result<(), String> {
|
||||
if is_running(&project) {
|
||||
let name = display_name_in(&Paths::real(), &project)?;
|
||||
return Err(format!("{name} läuft noch — erst beenden"));
|
||||
}
|
||||
delete_project_in(&Paths::real(), &name, delete_work_dirs)
|
||||
delete_project_in(&Paths::real(), &project, delete_work_dirs)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -133,7 +136,7 @@ pub(crate) fn project_icon(project: String) -> Result<Option<String>, String> {
|
||||
let Some(icon) = read_project_config_in(&paths, &project)?.terminal.icon else {
|
||||
return Ok(None);
|
||||
};
|
||||
let path = resolve_icon_path(&paths, &icon);
|
||||
let path = resolve_icon_path(&paths, &project, &icon)?;
|
||||
let mime = match path.extension().and_then(|e| e.to_str()) {
|
||||
Some(e) if e.eq_ignore_ascii_case("svg") => "image/svg+xml",
|
||||
_ => "image/png",
|
||||
@@ -168,6 +171,11 @@ pub(crate) fn rename_pool(pool: String, name: String) -> Result<(), String> {
|
||||
rename_pool_in(&Paths::real(), &pool, &name)
|
||||
}
|
||||
|
||||
/// Anzeige-Namen der Projekte (für Fehlermeldungen mit Projektliste).
|
||||
fn display_names(paths: &Paths, ids: &[String]) -> Result<Vec<String>, String> {
|
||||
ids.iter().map(|id| display_name_in(paths, id)).collect()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn delete_pool(pool: String) -> Result<(), String> {
|
||||
let paths = Paths::real();
|
||||
@@ -175,7 +183,7 @@ pub(crate) fn delete_pool(pool: String) -> Result<(), String> {
|
||||
if !running.is_empty() {
|
||||
return Err(format!(
|
||||
"Pool wird noch benutzt — läuft: {}",
|
||||
running.join(", ")
|
||||
display_names(&paths, &running)?.join(", ")
|
||||
));
|
||||
}
|
||||
delete_pool_in(&paths, &KeychainStore, &pool)
|
||||
@@ -219,7 +227,7 @@ pub(crate) fn oauth_login(pool: String) -> Result<(), String> {
|
||||
if !running.is_empty() {
|
||||
return Err(format!(
|
||||
"Neuanmeldung nur bei ungenutztem Pool möglich — läuft: {}",
|
||||
running.join(", ")
|
||||
display_names(&paths, &running)?.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
@@ -246,7 +254,8 @@ pub(crate) fn link_pool_runtime(pool: String) -> Result<(), String> {
|
||||
#[tauri::command]
|
||||
pub(crate) fn stop_project(project: String) -> Result<(), String> {
|
||||
if crate::platform::terminal_pids(&project).is_empty() {
|
||||
return Err(format!("{project} läuft nicht"));
|
||||
let name = display_name_in(&Paths::real(), &project)?;
|
||||
return Err(format!("{name} läuft nicht"));
|
||||
}
|
||||
kill_terminals(&project)
|
||||
}
|
||||
@@ -259,7 +268,8 @@ pub(crate) fn restart_project(app: tauri::AppHandle, project: String) -> Result<
|
||||
let deadline = Instant::now() + Duration::from_secs(30);
|
||||
while is_running(&project) {
|
||||
if Instant::now() > deadline {
|
||||
return Err(format!("{project} hat sich nach 30 s nicht beendet"));
|
||||
let name = display_name_in(&Paths::real(), &project)?;
|
||||
return Err(format!("{name} hat sich nach 30 s nicht beendet"));
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(250));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user