mirror of
https://github.com/forkless/NotAlterra.git
synced 2026-08-22 10:42:07 +02:00
remove locate menu item, clean up deprecation code
This commit is contained in:
@@ -54,6 +54,9 @@ All notable changes to NotAlterra are documented in this file.
|
||||
rustc 1.95 nightly.
|
||||
- **`KNOWN_ISSUES.md` moved from `docs/` to project root** — reflects v0.3.0
|
||||
privacy improvements (manual path entry live, discovery deprecated).
|
||||
- **`Locate save files` menu item removed** — the deprecated auto-discovery
|
||||
entry point is gone. `Set save folder` is the only path for configuring the
|
||||
save location.
|
||||
|
||||
### Notes
|
||||
- Working copy diverged from remote after signing the previous commit
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ Planned changes for upcoming releases, ordered by priority.
|
||||
|
||||
| Target | Item |
|
||||
|--------|------|
|
||||
| v0.4.0 | Remove `discovery.rs` entirely |
|
||||
| v0.4.0 | Extract `validate_custom_path` and `derive_ini_path` from `discovery.rs`, then remove the module |
|
||||
| v0.4.0 | Replace `disclaimer_accepted` with 0-byte sentinel file |
|
||||
| v0.4.0 | Drop `save_scan` from `config.ini` |
|
||||
| v0.4.0 | Remove `config.ini` entirely — re-request path each session |
|
||||
|
||||
@@ -85,14 +85,13 @@ path manually (paste is supported). The menu is keyboard-driven:
|
||||
### Menu
|
||||
|
||||
1. **Set save folder** — enter your save folder path manually (paste supported)
|
||||
2. **Locate save files (deprecated)** — auto-scan for save folders (will be removed in v0.3.0)
|
||||
3. **Recover save file** — pick a backup, preview metadata, overwrite the live save
|
||||
4. **Create full backup** — copies all `savegame_*` files to `NotAlterra_Backups`
|
||||
5. **Restore full backup** — overwrite the save folder from a previous backup
|
||||
6. **Inspect save files** — view all GVAS properties of any `.sav` / `.bak`
|
||||
7. **Manage UE5 Config (.ini) files** — backup, restore, or delete `.ini` files
|
||||
8. **View disclaimer**
|
||||
9. **Exit**
|
||||
2. **Recover save file** — pick a backup, preview metadata, overwrite the live save
|
||||
3. **Create full backup** — copies all `savegame_*` files to `NotAlterra_Backups`
|
||||
4. **Restore full backup** — overwrite the save folder from a previous backup
|
||||
5. **Inspect save files** — view all GVAS properties of any `.sav` / `.bak`
|
||||
6. **Manage UE5 Config (.ini) files** — backup, restore, or delete `.ini` files
|
||||
7. **View disclaimer**
|
||||
8. **Exit**
|
||||
|
||||
|
||||
## Where Files Live
|
||||
|
||||
+15
-131
@@ -76,7 +76,6 @@ struct App {
|
||||
log_path: PathBuf,
|
||||
save_folder: Option<PathBuf>,
|
||||
tui_state: tui::AppState,
|
||||
discovery_deprecation_shown: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -100,7 +99,6 @@ impl App {
|
||||
log_path,
|
||||
save_folder,
|
||||
tui_state,
|
||||
discovery_deprecation_shown: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -170,9 +168,6 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Load cached path only — no auto-scan (privacy: discovery scans user profiles)
|
||||
let save_found = app.save_folder.is_some();
|
||||
|
||||
// Main menu loop
|
||||
let mut menu_state = ListState::default().with_selected(Some(0));
|
||||
|
||||
@@ -184,10 +179,10 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Result<()> {
|
||||
draw_too_small(f);
|
||||
return;
|
||||
}
|
||||
tui::draw_main_menu(f, &mut menu_state, &app.tui_state, save_found);
|
||||
tui::draw_main_menu(f, &mut menu_state, &app.tui_state);
|
||||
})?;
|
||||
|
||||
let max_idx = 8usize;
|
||||
let max_idx = 7usize;
|
||||
if event::poll(std::time::Duration::from_millis(250))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Release { continue; }
|
||||
@@ -204,18 +199,17 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Result<()> {
|
||||
let idx = menu_state.selected().unwrap_or(0);
|
||||
match idx {
|
||||
0 => action_set_save_folder(terminal, &mut app)?,
|
||||
1 => action_locate_saves(terminal, &mut app)?,
|
||||
2 => action_recover_bak(terminal, &mut app)?,
|
||||
3 => action_create_backup(terminal, &mut app)?,
|
||||
4 => action_restore_backup(terminal, &mut app)?,
|
||||
5 => action_inspect_saves(terminal, &mut app)?,
|
||||
6 => run_ini_submenu(terminal, &mut app)?,
|
||||
7 => {
|
||||
1 => action_recover_bak(terminal, &mut app)?,
|
||||
2 => action_create_backup(terminal, &mut app)?,
|
||||
3 => action_restore_backup(terminal, &mut app)?,
|
||||
4 => action_inspect_saves(terminal, &mut app)?,
|
||||
5 => run_ini_submenu(terminal, &mut app)?,
|
||||
6 => {
|
||||
if let Some(false) = run_disclaimer(terminal, &mut app)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
8 => return Ok(()), // Exit
|
||||
7 => return Ok(()), // Exit
|
||||
_ => {}
|
||||
}
|
||||
app.clear_status();
|
||||
@@ -298,7 +292,12 @@ fn action_set_save_folder<B: Backend>(terminal: &mut Terminal<B>, app: &mut App)
|
||||
match key.code {
|
||||
KeyCode::Enter => {
|
||||
if ok_selected && !input_state.input.is_empty() {
|
||||
let candidate = discovery::validate_custom_path(&input_state.input);
|
||||
// Sanitize: strip control characters to prevent
|
||||
// config.ini injection and log forgery.
|
||||
let sanitized: String = input_state.input.chars()
|
||||
.filter(|c| !c.is_control())
|
||||
.collect();
|
||||
let candidate = discovery::validate_custom_path(&sanitized);
|
||||
if let Some(path) = candidate {
|
||||
app.save_folder = Some(path.clone());
|
||||
app.config.save_path = Some(path.display().to_string());
|
||||
@@ -361,121 +360,6 @@ fn action_set_save_folder<B: Backend>(terminal: &mut Terminal<B>, app: &mut App)
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches the first match as `save_path` in config.ini.
|
||||
fn action_locate_saves<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<()> {
|
||||
// Deprecation notice — show once per session
|
||||
if !app.discovery_deprecation_shown {
|
||||
app.discovery_deprecation_shown = true;
|
||||
ok_dialog(terminal, app,
|
||||
"Deprecation Notice",
|
||||
"Auto-scan for save folders is deprecated and will\n\
|
||||
be removed in a future release.\n\
|
||||
\n\
|
||||
Please use 'Set save folder' from the main menu\n\
|
||||
to enter your save path manually. This protects\n\
|
||||
your privacy by not scanning other user profiles\n\
|
||||
and system directories.",
|
||||
)?;
|
||||
// Let the user decide — proceed with the scan anyway
|
||||
}
|
||||
// Run scan on a background thread so we can show a live elapsed timer.
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let scan_start = std::time::Instant::now();
|
||||
std::thread::spawn(move || {
|
||||
let result = discovery::discover_save_folders();
|
||||
tx.send(result).ok();
|
||||
});
|
||||
|
||||
app.set_spinner(true);
|
||||
let folders = loop {
|
||||
let elapsed = scan_start.elapsed().as_secs();
|
||||
terminal.draw(|f| {
|
||||
tui::draw_text_screen(
|
||||
f,
|
||||
&app.tui_state,
|
||||
&[
|
||||
Line::from(Span::styled(
|
||||
"Scanning for Subnautica save files…",
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(
|
||||
"The first scan checks every user profile and common",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
"install location — it may take a moment.",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
)),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(
|
||||
format!(" ⏱ {elapsed}s elapsed"),
|
||||
Style::default().fg(Color::Cyan),
|
||||
)),
|
||||
],
|
||||
"",
|
||||
);
|
||||
})?;
|
||||
|
||||
match rx.try_recv() {
|
||||
Ok(f) => break f,
|
||||
Err(std::sync::mpsc::TryRecvError::Disconnected) => break Vec::new(),
|
||||
_ => {}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
};
|
||||
app.set_spinner(false);
|
||||
|
||||
let elapsed = scan_start.elapsed();
|
||||
let elapsed_str = if elapsed.as_secs() >= 1 {
|
||||
format!("{:.1}s", elapsed.as_secs_f64())
|
||||
} else {
|
||||
format!("{}ms", elapsed.as_millis())
|
||||
};
|
||||
|
||||
if folders.is_empty() {
|
||||
app.set_status("No Subnautica 2 save folders detected.", tui::StatusStyle::Error);
|
||||
wait_for_key(terminal, app)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Cache the first result
|
||||
if let Some(first) = folders.first() {
|
||||
app.save_folder = Some(first.path.clone());
|
||||
app.config.save_path = Some(first.path.display().to_string());
|
||||
app.config.save_scan = Some(chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
|
||||
crate::config::save_config(&app.ini_path, &app.config)?;
|
||||
}
|
||||
|
||||
refresh_stats(&mut app.tui_state, app.save_folder.as_deref());
|
||||
|
||||
let msg = format!("Found {} save folder(s) in {elapsed_str}.", folders.len());
|
||||
app.set_status(&msg, tui::StatusStyle::Success);
|
||||
|
||||
// Show found folders
|
||||
let lines: Vec<Line> = std::iter::once(Line::from(Span::styled(
|
||||
format!("Found {} save folder(s):", folders.len()),
|
||||
Style::default().fg(Color::Green),
|
||||
)))
|
||||
.chain(folders.iter().map(|f| {
|
||||
Line::from(Span::styled(
|
||||
format!(" {} — {}", f.label, f.path.display()),
|
||||
Style::default().fg(Color::Gray),
|
||||
))
|
||||
}))
|
||||
.collect();
|
||||
|
||||
let prompt = "Press any key to return to menu…";
|
||||
loop {
|
||||
terminal.draw(|f| tui::draw_text_screen(f, &app.tui_state, &lines, prompt))?;
|
||||
if let Event::Key(_) = event::read()? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recover a .sav from its .bak backup with a rollback safety net.
|
||||
fn action_recover_bak<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<()> {
|
||||
let save_folder = ensure_save_folder(terminal, app)?;
|
||||
|
||||
+5
-3
@@ -76,10 +76,9 @@ impl Default for AppState {
|
||||
// ── public rendering entry points ──────────────────────────────────────────
|
||||
|
||||
/// Draw the main menu.
|
||||
pub fn draw_main_menu(f: &mut Frame, state: &mut ListState, app: &AppState, _save_found: bool) {
|
||||
pub fn draw_main_menu(f: &mut Frame, state: &mut ListState, app: &AppState) {
|
||||
let items: Vec<&str> = vec![
|
||||
" Set save folder",
|
||||
" Locate save files (deprecated)",
|
||||
" Recover save file",
|
||||
" Create full backup",
|
||||
" Restore full backup",
|
||||
@@ -90,7 +89,6 @@ pub fn draw_main_menu(f: &mut Frame, state: &mut ListState, app: &AppState, _sav
|
||||
];
|
||||
let descs: Vec<&str> = vec![
|
||||
"Enter your save folder path manually",
|
||||
"Auto-scan for save folders (deprecated — will be removed in a future release)",
|
||||
"Restore a save file from a backup",
|
||||
"Copy the savegame files to NotAlterra_Backups",
|
||||
"Restore a full backup from NotAlterra_Backups",
|
||||
@@ -797,6 +795,10 @@ pub fn draw_input_dialog(
|
||||
Paragraph::new(buttons).alignment(Alignment::Center),
|
||||
Rect { y: inner.y + 6, height: 1, width: inner.width, x: inner.x },
|
||||
);
|
||||
|
||||
// Whale
|
||||
let bar = Rect { x: 0, y: f.area().height.saturating_sub(1), width: f.area().width, height: 1 };
|
||||
draw_whale_separator(f, bar, _app);
|
||||
}
|
||||
|
||||
/// Truncate a path to show the tail (most specific directories).
|
||||
|
||||
Reference in New Issue
Block a user