diff --git a/.gitignore b/.gitignore index 3968dc7..92684da 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ dist/ target/ gen/ target-engine/ -public/mock/ src-tauri/binaries/ installer/src-tauri/payload/ *.bak diff --git a/public/mock/icon.jpg b/public/mock/icon.jpg new file mode 100644 index 0000000..9334661 Binary files /dev/null and b/public/mock/icon.jpg differ diff --git a/public/mock/pic0.jpg b/public/mock/pic0.jpg new file mode 100644 index 0000000..0176c5b Binary files /dev/null and b/public/mock/pic0.jpg differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png deleted file mode 100644 index 28d403a..0000000 Binary files a/src-tauri/icons/64x64.png and /dev/null differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns deleted file mode 100644 index ff84722..0000000 Binary files a/src-tauri/icons/icon.icns and /dev/null differ diff --git a/src-tauri/src/ftp.rs b/src-tauri/src/ftp.rs index f5f2581..e2bc8bc 100644 --- a/src-tauri/src/ftp.rs +++ b/src-tauri/src/ftp.rs @@ -288,8 +288,8 @@ mod tests { #[test] fn parses_ftpsrv_listing() { assert_eq!( - parse_list_line("-rw-rw-rw- 1 0 0 71733805056 Oct 10 13:53 PPSA05678.ffpfsc"), - Some(Entry { name: "PPSA05678.ffpfsc".into(), size: 71733805056, is_dir: false }) + parse_list_line("-rw-rw-rw- 1 0 0 71733805056 Oct 10 13:53 PPSA01234.ffpfsc"), + Some(Entry { name: "PPSA01234.ffpfsc".into(), size: 71733805056, is_dir: false }) ); assert_eq!( parse_list_line("drwxrwxrwx 2 0 0 65536 Oct 09 19:48 My Game Folder"), diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5ce2e51..1eb377d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -145,7 +145,6 @@ pub fn run() { .plugin(tauri_plugin_updater::Builder::new().build()) .setup(|app| { paths::init(app.path().app_config_dir()?, app.path().app_data_dir()?); - settings::migrate_legacy(); let handle = app.handle().clone(); let emitter: Emitter = Arc::new(move |event, payload| { let _ = handle.emit(event, payload); diff --git a/src-tauri/src/paths.rs b/src-tauri/src/paths.rs index 46e4a26..ae74263 100644 --- a/src-tauri/src/paths.rs +++ b/src-tauri/src/paths.rs @@ -32,12 +32,3 @@ pub fn data_dir() -> PathBuf { .or_else(|| DATA.get().cloned()) .unwrap_or_else(|| home().join(".local/share/prospack")) } - -/// Where the app lived while it was called Packer (Linux only). -pub fn legacy_config_dir() -> PathBuf { - home().join(".config/ps5-packer") -} - -pub fn legacy_data_dir() -> PathBuf { - home().join(".local/share/ps5-packer") -} diff --git a/src-tauri/src/ps5.rs b/src-tauri/src/ps5.rs index 8fcfa04..68ced5d 100644 --- a/src-tauri/src/ps5.rs +++ b/src-tauri/src/ps5.rs @@ -346,23 +346,23 @@ mod tests { #[test] fn fnv_matches_game_compressor_file_names() { - // Names Game Compressor actually wrote on the console. - assert_eq!(format!("{:08x}", fnv1a32("/data/homebrew/PPSA05678.ffpfsc")), "17b59d6b"); + // Same FNV-1a 32 Game Compressor uses for its validations/-<hash>.json names. assert_eq!(format!("{:08x}", fnv1a32("/data/homebrew/PPSA01234.ffpfsc")), "844067eb"); + assert_eq!(format!("{:08x}", fnv1a32("/data/homebrew/PPSA05678.ffpfsc")), "17b59d6b"); } #[test] fn gc_record_has_the_fields_gc_reads() { let r = GcRecord { - title_id: "PPSA09012", - image_remote: "/data/homebrew/PPSA09012.ffpfsc", + title_id: "PPSA01234", + image_remote: "/data/homebrew/PPSA01234.ffpfsc", image_bytes: 53_800_000_000, source_bytes: 86_900_000_000, apr_indexed: true, files_verified: 35, }; let v: serde_json::Value = serde_json::from_str(&gc_record_json(&r, 1)).unwrap(); - assert_eq!(v["path"], "/data/homebrew/PPSA09012.ffpfsc"); + assert_eq!(v["path"], "/data/homebrew/PPSA01234.ffpfsc"); assert_eq!(v["sourceSize"], 53_800_000_000u64); assert_eq!(v["compressionSavedBytes"], 33_100_000_000u64); assert_eq!(v["aprIndexed"], true); @@ -372,13 +372,13 @@ mod tests { #[test] fn manual_list_replaces_same_title_entries() { - let before = "/data/homebrew/PPSA05678.ffpfsc\n/data/homebrew/PPSA09012-app\n\n/data/homebrew/PPSA090120.ffpfsc\n"; - let after = manual_list_with(before, "PPSA09012", "/data/homebrew/PPSA09012.ffpfsc"); + let before = "/data/homebrew/PPSA05678.ffpfsc\n/data/homebrew/PPSA01234-app\n\n/data/homebrew/PPSA012340.ffpfsc\n"; + let after = manual_list_with(before, "PPSA01234", "/data/homebrew/PPSA01234.ffpfsc"); assert_eq!( after, - "/data/homebrew/PPSA05678.ffpfsc\n/data/homebrew/PPSA090120.ffpfsc\n/data/homebrew/PPSA09012.ffpfsc\n" + "/data/homebrew/PPSA05678.ffpfsc\n/data/homebrew/PPSA012340.ffpfsc\n/data/homebrew/PPSA01234.ffpfsc\n" ); - assert_eq!(manual_list_with("", "PPSA09012", "/x/PPSA09012.ffpfsc"), "/x/PPSA09012.ffpfsc\n"); + assert_eq!(manual_list_with("", "PPSA01234", "/x/PPSA01234.ffpfsc"), "/x/PPSA01234.ffpfsc\n"); } #[test] diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 6b9e129..1562520 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -1,6 +1,6 @@ use crate::paths; use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] @@ -82,94 +82,3 @@ pub fn save(s: &Settings) -> Result<(), String> { std::fs::write(&tmp, serde_json::to_vec_pretty(s).unwrap()).map_err(|e| e.to_string())?; std::fs::rename(&tmp, &path).map_err(|e| e.to_string()) } - -/// Carries settings and backups over from the app's earlier name ("Packer", ps5-packer dirs). -/// Returns true when settings were written. Never touches the old folders. -pub fn migrate_from(old_cfg: &Path, old_data: &Path, new_cfg: &Path, new_data: &Path) -> Result<bool, String> { - let new_settings = new_cfg.join("settings.json"); - let mut wrote = false; - if !new_settings.exists() { - let old_settings = old_cfg.join("settings.json"); - let migrated = if let Ok(raw) = std::fs::read_to_string(&old_settings) { - // They configured the old app on purpose, so the wizard isn't needed. - let mut s: Settings = serde_json::from_str(&raw).map_err(|e| e.to_string())?; - s.setup_done = true; - Some(s) - } else if old_data.is_dir() { - // Used the old app with its built-in defaults: keep those, but show the wizard. - let old_images = paths::home().join("PS5 Images"); - Some(Settings { - ps5_host: "192.168.0.50".into(), - output_dir: if old_images.is_dir() { - old_images.to_string_lossy().into_owned() - } else { - default_output_dir().to_string_lossy().into_owned() - }, - ..Settings::default() - }) - } else { - None - }; - if let Some(s) = migrated { - std::fs::create_dir_all(new_cfg).map_err(|e| e.to_string())?; - std::fs::write(&new_settings, serde_json::to_vec_pretty(&s).unwrap()).map_err(|e| e.to_string())?; - wrote = true; - } - } - let (old_b, new_b) = (old_data.join("backups"), new_data.join("backups")); - if old_b.is_dir() && !new_b.exists() { - std::fs::create_dir_all(&new_b).map_err(|e| e.to_string())?; - for e in std::fs::read_dir(&old_b).map_err(|e| e.to_string())?.flatten() { - let _ = std::fs::copy(e.path(), new_b.join(e.file_name())); - } - } - Ok(wrote) -} - -pub fn migrate_legacy() { - if let Err(e) = migrate_from(&paths::legacy_config_dir(), &paths::legacy_data_dir(), &config_dir(), &data_dir()) { - eprintln!("settings migration failed: {e}"); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn migrates_old_ps5_packer_settings_once() { - let t = tempfile::tempdir().unwrap(); - let (old_cfg, old_data) = (t.path().join("old/cfg"), t.path().join("old/data")); - std::fs::create_dir_all(old_data.join("backups")).unwrap(); - std::fs::write(old_data.join("backups/1-config.ini"), "x").unwrap(); - std::fs::create_dir_all(&old_cfg).unwrap(); - std::fs::write(old_cfg.join("settings.json"), r#"{"ps5Host":"10.0.0.5","ftpPort":2121}"#).unwrap(); - let (new_cfg, new_data) = (t.path().join("new/cfg"), t.path().join("new/data")); - assert!(migrate_from(&old_cfg, &old_data, &new_cfg, &new_data).unwrap()); - let s: Settings = serde_json::from_str(&std::fs::read_to_string(new_cfg.join("settings.json")).unwrap()).unwrap(); - assert_eq!(s.ps5_host, "10.0.0.5"); - assert!(s.setup_done, "people who configured Packer skip the wizard"); - assert!(new_data.join("backups/1-config.ini").is_file()); - assert!(!migrate_from(&old_cfg, &old_data, &new_cfg, &new_data).unwrap(), "second run is a no-op"); - } - - #[test] - fn old_defaults_user_gets_old_host_and_the_wizard() { - let t = tempfile::tempdir().unwrap(); - let old_data = t.path().join("old/data"); - std::fs::create_dir_all(&old_data).unwrap(); - let new_cfg = t.path().join("new/cfg"); - assert!(migrate_from(&t.path().join("old/cfg"), &old_data, &new_cfg, &t.path().join("new/data")).unwrap()); - let s: Settings = serde_json::from_str(&std::fs::read_to_string(new_cfg.join("settings.json")).unwrap()).unwrap(); - assert_eq!(s.ps5_host, "192.168.0.50"); - assert!(!s.setup_done); - } - - #[test] - fn fresh_install_writes_nothing() { - let t = tempfile::tempdir().unwrap(); - let new_cfg = t.path().join("new/cfg"); - assert!(!migrate_from(&t.path().join("a"), &t.path().join("b"), &new_cfg, &t.path().join("c")).unwrap()); - assert!(!new_cfg.join("settings.json").exists()); - } -} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 1656179..3d5b9b1 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -40,7 +40,8 @@ "icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", - "icons/icon.png" + "icons/icon.png", + "icons/icon.ico" ], "externalBin": [ "binaries/prospack-engine" diff --git a/src/lib/mock.js b/src/lib/mock.js index 8318299..b5fef0b 100644 --- a/src/lib/mock.js +++ b/src/lib/mock.js @@ -8,16 +8,16 @@ const STAGES = ["compress", "verify", "upload", "mount"]; let settings = { setupDone: new URLSearchParams(location.search).has("setup") ? false : true, ps5Host: "192.168.0.50", ftpPort: 2121, webPort: 8888, remoteDir: "/data/homebrew", - outputDir: "/home/you/PS5 Images", compressionLevel: 7, cpuCount: 0, verifyWorkers: 0, + outputDir: "/home/you/Prospack Images", compressionLevel: 7, cpuCount: 0, verifyWorkers: 0, autoUpload: true, writeSmpHints: true, markGcValidated: true, checkUpdates: true, deleteLocalAfterUpload: false, }; const sample = { path: "/games/PPSA01234-app", titleId: "PPSA01234", titleName: "Sample Game", - version: "01.019.000", fileCount: 166733, totalBytes: 159841147026, apr: true, hasAmprIndex: true, accent: "#3d7bff", + version: "01.004.000", fileCount: 166733, totalBytes: 159841147026, apr: true, hasAmprIndex: true, accent: "#7b5cff", }; -const flag = { - path: "/games/PPSA05678-app0", titleId: "PPSA05678", titleName: "Another Game", +const another = { + path: "/games/PPSA05678-app", titleId: "PPSA05678", titleName: "Another Game", version: "01.000.000", fileCount: 299, totalBytes: 130982284503, apr: true, hasAmprIndex: true, accent: "#c9a46a", }; @@ -28,7 +28,7 @@ let jobs = [ { id: 1, dump: sample, status: "running", stage: 1, stages: blankStages(), pct: 0, doneBytes: 0, totalBytes: 0, filesDone: 0, filesTotal: 166733, rate: 0, rateHistory: [], rateLabel: "verify", etaSecs: null, stageSecs: 0, - imagePath: "/home/you/PS5 Images/PPSA01234.ffpfsc", imageBytes: 91_700_000_000, remotePath: null, mismatches: 0, + imagePath: "/home/you/Prospack Images/PPSA01234.ffpfsc", imageBytes: 91_700_000_000, remotePath: null, mismatches: 0, problems: [], warnings: [], error: null, log: [ log("Added " + sample.path, "hl"), log("Compress started", "hl"), @@ -38,9 +38,9 @@ let jobs = [ ], }, { - id: 2, dump: flag, status: "done", stage: 3, stages: blankStages(), pct: 100, doneBytes: 0, totalBytes: 0, + id: 2, dump: another, status: "done", stage: 3, stages: blankStages(), pct: 100, doneBytes: 0, totalBytes: 0, filesDone: 0, filesTotal: 0, rate: 0, rateHistory: [], rateLabel: "", etaSecs: null, stageSecs: 0, - imagePath: "/home/you/PS5 Images/PPSA05678.ffpfsc", imageBytes: 71_733_805_056, + imagePath: "/home/you/Prospack Images/PPSA05678.ffpfsc", imageBytes: 71_733_805_056, remotePath: "/data/homebrew/PPSA05678.ffpfsc", mismatches: 0, problems: [], warnings: [], error: null, log: [log("Finished", "ok")], }, @@ -118,7 +118,7 @@ export const mock = { installEngine: async () => { for (const l of ["Creating Python environment", "Installing MkPFS 1.1.0"]) { emit("engine-log", l); await delay(500); } return mock.engineStatus(); }, inspectDump: async () => { throw new Error("preview"); }, addJob: async () => { throw "Adding dumps needs the desktop app (this is a browser preview)"; }, - jobArt: async (path) => path.includes("SampleGame") ? { icon: "/mock/icon.jpg", hero: "/mock/pic0.jpg" } : { icon: null, hero: null }, + jobArt: async () => ({ icon: null, hero: null }), listJobs: async () => structuredClone(jobs), cancelJob: async (id) => { const j = jobs.find((x) => x.id === id); if (j) { j.status = "cancelled"; j.stages[j.stage].state = "pending"; } emit("jobs", structuredClone(jobs)); }, removeJob: async (id) => { jobs = jobs.filter((x) => x.id !== id); emit("jobs", structuredClone(jobs)); },