Pool-Runtime (projects/todos/history) als Symlinks ins synced Repo, auch bei Pool-Anlage

This commit is contained in:
marcus.hinz
2026-07-04 20:28:12 +02:00
parent 3e022311ee
commit 09488ae5ea
+71 -2
View File
@@ -642,6 +642,44 @@ fn init_pool_config(
Ok(()) Ok(())
} }
/// Runtime, die pro Pool ins synced Repo gelinkt wird: Transkripte, Todos,
/// Prompt-Historie. (name, ist_ordner)
const SYNCED_RUNTIME: [(&str, bool); 3] =
[("projects", true), ("todos", true), ("history.jsonl", false)];
/// Zielort der synced Runtime-Daten eines Pools im claude-projects-Repo.
fn pool_data_dir(paths: &Paths, pool: &str) -> PathBuf {
paths.projects_dir().join("pool").join(pool)
}
/// Ersetzt im Pool-Ordner projects/todos/history.jsonl durch Symlinks auf das
/// synced Repo. Die Symlinks sind maschinenlokal, die Daten reisen über git.
/// Vorhandene echte Inhalte werden verworfen (kein History-Erhalt — bewusst).
fn link_pool_runtime_in(paths: &Paths, pool: &str) -> Result<(), String> {
check_name(pool)?;
let src = paths.pool_dir(pool);
let data = pool_data_dir(paths, pool);
for (name, is_dir) in SYNCED_RUNTIME {
let target = data.join(name);
if is_dir {
fs::create_dir_all(&target).map_err(|e| e.to_string())?;
} else {
fs::create_dir_all(&data).map_err(|e| e.to_string())?;
if !target.exists() {
fs::write(&target, "").map_err(|e| e.to_string())?;
}
}
let link = src.join(name);
if link.is_dir() && !link.is_symlink() {
fs::remove_dir_all(&link).map_err(|e| e.to_string())?;
} else if link.is_symlink() || link.exists() {
fs::remove_file(&link).map_err(|e| e.to_string())?;
}
std::os::unix::fs::symlink(&target, &link).map_err(|e| e.to_string())?;
}
Ok(())
}
/// Legt einen apikey-Pool an: Key-Datei (0600), settings.json mit /// Legt einen apikey-Pool an: Key-Datei (0600), settings.json mit
/// apiKeyHelper auf diese Datei, CLAUDE.md, pool.json. /// apiKeyHelper auf diese Datei, CLAUDE.md, pool.json.
fn create_apikey_pool_in(paths: &Paths, name: &str, key: &str) -> Result<(), String> { fn create_apikey_pool_in(paths: &Paths, name: &str, key: &str) -> Result<(), String> {
@@ -656,7 +694,8 @@ fn create_apikey_pool_in(paths: &Paths, name: &str, key: &str) -> Result<(), Str
&dir, &dir,
serde_json::json!({ "apiKeyHelper": format!("cat '{}'", key_path.display()) }), serde_json::json!({ "apiKeyHelper": format!("cat '{}'", key_path.display()) }),
)?; )?;
write_pool_json(&dir, name, "apikey") write_pool_json(&dir, name, "apikey")?;
link_pool_runtime_in(paths, name)
} }
/// Legt einen oauth-Pool an: Grundausstattung (leere settings.json + CLAUDE.md) /// Legt einen oauth-Pool an: Grundausstattung (leere settings.json + CLAUDE.md)
@@ -665,7 +704,8 @@ fn create_apikey_pool_in(paths: &Paths, name: &str, key: &str) -> Result<(), Str
fn create_oauth_pool_in(paths: &Paths, name: &str) -> Result<(), String> { fn create_oauth_pool_in(paths: &Paths, name: &str) -> Result<(), String> {
let dir = check_new_pool(paths, name)?; let dir = check_new_pool(paths, name)?;
init_pool_config(&dir, serde_json::json!({}))?; init_pool_config(&dir, serde_json::json!({}))?;
write_pool_json(&dir, name, "oauth") write_pool_json(&dir, name, "oauth")?;
link_pool_runtime_in(paths, name)
} }
/// Löscht einen Pool samt Ordner (inkl. Credentials). Zugeordnete Projekte /// Löscht einen Pool samt Ordner (inkl. Credentials). Zugeordnete Projekte
@@ -1096,6 +1136,17 @@ fn set_sync_setting(enabled: bool) -> Result<(), String> {
set_sync_on_session_end_in(&Paths::real(), enabled) set_sync_on_session_end_in(&Paths::real(), enabled)
} }
/// Verlinkt die synced Runtime eines bestehenden Pools. Nur bei idlem Pool —
/// sonst würde das Transkript der laufenden Session ersetzt.
#[tauri::command]
fn link_pool_runtime(pool: String) -> Result<(), String> {
let paths = Paths::real();
if !running_projects_using_pool(&paths, &pool)?.is_empty() {
return Err(format!("{pool} wird benutzt — erst Session beenden"));
}
link_pool_runtime_in(&paths, &pool)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
// generate_context! darf pro Crate nur einmal expandieren (_EMBED_INFO_PLIST). // generate_context! darf pro Crate nur einmal expandieren (_EMBED_INFO_PLIST).
@@ -1213,6 +1264,7 @@ fn main_builder() -> tauri::Builder<tauri::Wry> {
restart_project, restart_project,
sync_setting, sync_setting,
set_sync_setting, set_sync_setting,
link_pool_runtime,
oauth_login, oauth_login,
keychain_status, keychain_status,
set_apikey, set_apikey,
@@ -1647,6 +1699,23 @@ mod tests {
assert!(!p.project_config("proj").exists()); assert!(!p.project_config("proj").exists());
} }
#[test]
fn pool_anlegen_verlinkt_synced_runtime() {
let p = tmp_paths();
create_oauth_pool_in(&p, "privat").unwrap();
let pooldir = p.pool_dir("privat");
for (name, is_dir) in SYNCED_RUNTIME {
let link = pooldir.join(name);
assert!(link.is_symlink(), "{name} sollte Symlink sein");
assert_eq!(fs::read_link(&link).unwrap(), pool_data_dir(&p, "privat").join(name));
let target = pool_data_dir(&p, "privat").join(name);
assert_eq!(target.is_dir(), is_dir);
assert!(target.exists());
}
// Zieldaten liegen im claude-projects-Repo unter pool/<name>/
assert!(p.projects_dir().join("pool").join("privat").is_dir());
}
#[test] #[test]
fn sync_optin_default_aus_und_umschaltbar() { fn sync_optin_default_aus_und_umschaltbar() {
let p = tmp_paths(); let p = tmp_paths();