Add app update detection and multi-folder project support
Feature 1 - Update Detection: Query Gitea releases API on startup (3s
delay) and every 24h, compare patch versions by platform, show pulsing
"Update" button in TopBar with dialog for release notes/downloads.
Settings: auto-check toggle, manual check, dismiss per-version.
Feature 2 - Multi-Folder Projects: Replace single `path` with
`paths: Vec<ProjectPath>` (host_path + mount_name). Each folder mounts
to `/workspace/{mount_name}`. Auto-migrate old single-path JSON on load.
Container recreation via paths-fingerprint label. AddProjectDialog and
ProjectCard support add/remove/edit of multiple folders.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,33 +19,72 @@ impl ProjectsStore {
|
||||
|
||||
let file_path = data_dir.join("projects.json");
|
||||
|
||||
let projects = if file_path.exists() {
|
||||
let (projects, needs_save) = if file_path.exists() {
|
||||
match fs::read_to_string(&file_path) {
|
||||
Ok(data) => match serde_json::from_str(&data) {
|
||||
Ok(parsed) => parsed,
|
||||
Err(e) => {
|
||||
log::error!("Failed to parse projects.json: {}. Starting with empty list.", e);
|
||||
// Back up the corrupted file
|
||||
let backup = file_path.with_extension("json.bak");
|
||||
if let Err(be) = fs::copy(&file_path, &backup) {
|
||||
log::error!("Failed to back up corrupted projects.json: {}", be);
|
||||
Ok(data) => {
|
||||
// First try to parse as Vec<Value> to run migration
|
||||
match serde_json::from_str::<Vec<serde_json::Value>>(&data) {
|
||||
Ok(raw_values) => {
|
||||
let mut migrated = false;
|
||||
let migrated_values: Vec<serde_json::Value> = raw_values
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
let has_path = v.as_object().map_or(false, |o| o.contains_key("path") && !o.contains_key("paths"));
|
||||
if has_path {
|
||||
migrated = true;
|
||||
}
|
||||
crate::models::Project::migrate_from_value(v)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Now deserialize the migrated values
|
||||
let json_str = serde_json::to_string(&migrated_values).unwrap_or_default();
|
||||
match serde_json::from_str::<Vec<crate::models::Project>>(&json_str) {
|
||||
Ok(parsed) => (parsed, migrated),
|
||||
Err(e) => {
|
||||
log::error!("Failed to parse migrated projects.json: {}. Starting with empty list.", e);
|
||||
let backup = file_path.with_extension("json.bak");
|
||||
if let Err(be) = fs::copy(&file_path, &backup) {
|
||||
log::error!("Failed to back up corrupted projects.json: {}", be);
|
||||
}
|
||||
(Vec::new(), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to parse projects.json: {}. Starting with empty list.", e);
|
||||
let backup = file_path.with_extension("json.bak");
|
||||
if let Err(be) = fs::copy(&file_path, &backup) {
|
||||
log::error!("Failed to back up corrupted projects.json: {}", be);
|
||||
}
|
||||
(Vec::new(), false)
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read projects.json: {}", e);
|
||||
Vec::new()
|
||||
(Vec::new(), false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Vec::new()
|
||||
(Vec::new(), false)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
let store = Self {
|
||||
projects: Mutex::new(projects),
|
||||
file_path,
|
||||
})
|
||||
};
|
||||
|
||||
// Persist migrated format back to disk
|
||||
if needs_save {
|
||||
log::info!("Migrated projects.json from single-path to multi-path format");
|
||||
let projects = store.lock();
|
||||
if let Err(e) = store.save(&projects) {
|
||||
log::error!("Failed to save migrated projects: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
fn lock(&self) -> std::sync::MutexGuard<'_, Vec<Project>> {
|
||||
|
||||
Reference in New Issue
Block a user