diff --git a/app/src/main/java/com/httrack/android/HTTrackActivity.java b/app/src/main/java/com/httrack/android/HTTrackActivity.java index bad5035..19686ad 100755 --- a/app/src/main/java/com/httrack/android/HTTrackActivity.java +++ b/app/src/main/java/com/httrack/android/HTTrackActivity.java @@ -198,6 +198,10 @@ protected static class VERSION_CODES { // "Project name" pane is dirty protected boolean dirtyNamePane; + // Project whose saved profile is loaded in the map (reload-guard sentinel); null if none. + // Distinct from the map name key, which plain Back navigation writes before any load. + protected String loadedProjectName; + // Handler to execute code in UI thread private final Handler handlerUI = new Handler(); @@ -699,6 +703,7 @@ protected void onCreate(final Bundle savedInstanceState) { // Clear map (useful to get dynamic fields) mapper.setContext(this); mapper.resetMap(); + loadedProjectName = null; // Go to first pane now setPane(0); @@ -2275,9 +2280,15 @@ public void run() { }); } + /** Whether the setup pane should (re)load the selected project's saved profile; see loadedProjectName. */ + static boolean shouldReloadProfile(final String selectedName, + final String loadedName, final boolean dirty) { + return dirty || loadedName == null || !loadedName.equals(selectedName); + } + /** * Validate the current pane - * + * * @return true if the current pane is valid */ protected boolean validatePane() { @@ -2298,11 +2309,9 @@ protected boolean validatePane() { // Check project name final String name = getFieldText(R.id.fieldProjectName); if (OptionsMapper.isStringNonEmpty(name)) { - // Changed name ? - final String prevName = getMap(R.id.fieldProjectName); - if (prevName == null || !prevName.equals(name) || dirtyNamePane) { - // We need to put immediately the name in the map to be able to - // unserialize. + // Reload when the selection differs from the loaded profile (see shouldReloadProfile). + if (shouldReloadProfile(name, loadedProjectName, dirtyNamePane)) { + // Put the name in the map first so unserialize() can resolve the profile. try { mapper.resetMap(); setMap(R.id.fieldProjectName, name); @@ -2311,6 +2320,7 @@ protected boolean validatePane() { // Ignore (if not found) } finally { dirtyNamePane = false; + loadedProjectName = name; } } // A crafted winprofile.ini can overwrite the name with a path that escapes the root. @@ -2950,6 +2960,9 @@ protected void saveInstanceState(final Bundle outState) { // Map keys outState.putParcelable("com.httrack.android.map", mapper.serialize()); + // Which project's profile the map holds, so the reload guard survives recreation. + outState.putString("com.httrack.android.loadedProjectName", loadedProjectName); + // Current pane outState.putInt("com.httrack.android.pane_id", pane_id); @@ -3090,6 +3103,11 @@ protected void restoreInstanceState(final Bundle savedInstanceState) { // Load map settings loadParcelable(data); + // Restore the reload-guard sentinel alongside the map it tracks; else a post-restore + // Back/Next would treat the live map as stale and wipe the user's edits. + loadedProjectName = savedInstanceState + .getString("com.httrack.android.loadedProjectName"); + // The progress pane means a live crawl; after process death none exists, so land on the // setup pane instead, where isInterruptedProfile() defaults the action to Continue. final int paneId = (id == LAYOUT_MIRROR_PROGRESS && !hasLiveRunner()) diff --git a/app/src/test/java/com/httrack/android/ProjectReloadTest.java b/app/src/test/java/com/httrack/android/ProjectReloadTest.java new file mode 100644 index 0000000..44d9888 --- /dev/null +++ b/app/src/test/java/com/httrack/android/ProjectReloadTest.java @@ -0,0 +1,37 @@ +package com.httrack.android; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Guards HTTrackActivity.shouldReloadProfile, the decision that loads a selected project's + * saved URL/options. Regression: Select project -> Back -> Next -> Next left the URL blank + * because the guard trusted the map name key, which Back had already written. + */ +public class ProjectReloadTest { + @Test + public void reloadsWhenNothingLoadedYet() { + // Fresh selection, no profile loaded: must load, even though the map name key already + // holds the name (written by Back navigation). This is the reported bug. + assertTrue(HTTrackActivity.shouldReloadProfile("Foo", null, false)); + } + + @Test + public void reloadsWhenSelectionChanged() { + assertTrue(HTTrackActivity.shouldReloadProfile("Bar", "Foo", false)); + } + + @Test + public void reloadsWhenDirtyAfterRestore() { + assertTrue(HTTrackActivity.shouldReloadProfile("Foo", "Foo", true)); + } + + @Test + public void skipsWhenSameProfileAlreadyLoaded() { + // The one case that must NOT reload: reloading here would wipe URL/options the user + // typed on the setup pane then navigated back past. + assertFalse(HTTrackActivity.shouldReloadProfile("Foo", "Foo", false)); + } +}