diff --git a/app/src/main/java/com/httrack/android/CleanupActivity.java b/app/src/main/java/com/httrack/android/CleanupActivity.java index 3b08725..d8ef3c5 100755 --- a/app/src/main/java/com/httrack/android/CleanupActivity.java +++ b/app/src/main/java/com/httrack/android/CleanupActivity.java @@ -37,6 +37,8 @@ import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; @@ -45,6 +47,7 @@ import android.widget.CheckBox; import android.widget.ListView; import android.widget.SimpleAdapter; +import android.widget.Toast; import com.httrack.android.jni.HTTrackLib; @@ -59,6 +62,7 @@ public class CleanupActivity extends ListActivity { private String[] projects; private int action; private final HashSet toBeDeleted = new HashSet(); + private volatile boolean deleteInProgress; public static final int ACTION_CLEANUP = 1; public static final int ACTION_SELECT = 2; @@ -208,66 +212,89 @@ public boolean accept(final File dir, final String filename) { } } - protected boolean deleteProjects() { - boolean success = true; - for (final int position : toBeDeleted) { - // Delete project. - final String name = projects[position]; - final File target = new File(projectRootFile, name); - if (deleteRecursively(target)) { - // Mark item as disabled. - final View item = list.getChildAt(position); - final View o = item.findViewById(R.id.blocCheck); - o.setBackgroundResource(R.color.gray); - final CheckBox cb = (CheckBox) item.findViewById(R.id.check); - cb.setClickable(false); - cb.setEnabled(false); - } else { - success = false; - } - + /** + * Delete the selected projects off the UI thread: one delete() per file over a whole mirror + * plus a native index rebuild blows past the ANR budget. The selection is snapshotted here and + * the result posted back to finish the activity; a second run while one is going is ignored. + */ + protected void deleteProjects() { + if (deleteInProgress) { + return; } - // Everything was deleted + final HashSet positions = new HashSet(toBeDeleted); toBeDeleted.clear(); + deleteInProgress = true; + Toast.makeText(this, "Deleting projects…", Toast.LENGTH_SHORT).show(); + new Thread(new Runnable() { + @Override + public void run() { + final HashSet deleted = new HashSet(); + for (final int position : positions) { + if (deleteRecursively(new File(projectRootFile, projects[position]))) { + deleted.add(position); + } + } - // Root path can be deleted ? - final boolean deleteRootPath = pathIsEmpty(projectRootFile); - if (deleteRootPath) { - if (deleteRecursively(projectRootFile)) { - Log.d(getClass().getSimpleName(), "successfully deleted root path: " - + projectRootFile); - } else { - Log.w(getClass().getSimpleName(), "could not delet root path: " - + projectRootFile); - } - - // Delete top-level parent if a "HTTrack" folder was found. This ensure - // that we fully cleanup user-data. - final File parentRoot = projectRootFile.getParentFile(); - if (parentRoot != null && parentRoot.getName().equals("HTTrack") - && pathIsEmpty(parentRoot)) { - if (deleteRecursively(parentRoot)) { - Log.d(getClass().getSimpleName(), "successfully deleted root path: " - + parentRoot); + // Root path can be deleted ? + final boolean deleteRootPath = pathIsEmpty(projectRootFile); + if (deleteRootPath) { + if (deleteRecursively(projectRootFile)) { + Log.d(getClass().getSimpleName(), "successfully deleted root path: " + + projectRootFile); + } else { + Log.w(getClass().getSimpleName(), "could not delet root path: " + + projectRootFile); + } + + // Delete top-level parent if a "HTTrack" folder was found. This ensure + // that we fully cleanup user-data. + final File parentRoot = projectRootFile.getParentFile(); + if (parentRoot != null && parentRoot.getName().equals("HTTrack") + && pathIsEmpty(parentRoot)) { + if (deleteRecursively(parentRoot)) { + Log.d(getClass().getSimpleName(), "successfully deleted root path: " + + parentRoot); + } else { + Log.w(getClass().getSimpleName(), "could not delet root path: " + + parentRoot); + } + } } else { - Log.w(getClass().getSimpleName(), "could not delet root path: " - + parentRoot); + // Rebuild top index + HTTrackLib.buildTopIndex(projectRootFile, resourceFile); } - } - } else { - // Rebuild top index - HTTrackLib.buildTopIndex(projectRootFile, resourceFile); - } - // Push result - final Intent intent = new Intent(); - intent.putExtra("com.httrack.android.rootPathWasDeleted", deleteRootPath); - setResult(Activity.RESULT_OK, intent); + deliverDeleteOutcome(deleted, deleteRootPath); + } + }, "cleanup-delete").start(); + } - // Finish - finish(); + /** Finish with the result on the main thread; touch the list only if we still own it. */ + private void deliverDeleteOutcome(final HashSet deleted, + final boolean rootPathWasDeleted) { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + deleteInProgress = false; + if (!isFinishing() && !isDestroyed()) { + for (final int position : deleted) { + final View item = list.getChildAt(position); + if (item == null) { + continue; + } + item.findViewById(R.id.blocCheck).setBackgroundResource(R.color.gray); + final CheckBox cb = (CheckBox) item.findViewById(R.id.check); + cb.setClickable(false); + cb.setEnabled(false); + } + } - return success; + final Intent intent = new Intent(); + intent.putExtra("com.httrack.android.rootPathWasDeleted", rootPathWasDeleted); + setResult(Activity.RESULT_OK, intent); + finish(); + } + }); } public void onClickDelete(final View v) { diff --git a/app/src/main/java/com/httrack/android/HTTrackActivity.java b/app/src/main/java/com/httrack/android/HTTrackActivity.java index 7ece708..cf8aef5 100755 --- a/app/src/main/java/com/httrack/android/HTTrackActivity.java +++ b/app/src/main/java/com/httrack/android/HTTrackActivity.java @@ -2267,6 +2267,7 @@ private void startLegacyMirrorImport() { // Process-wide: a rotation recreates the activity, but the detached worker keeps copying. private static final AtomicBoolean importInProgress = new AtomicBoolean(); + private volatile boolean browseAllInProgress; /** * Copy the picked tree into our Websites directory, entirely off the UI thread: even walking @@ -2649,8 +2650,31 @@ public void onBrowse(final View view) { * "Browse All Websites" */ public void onBrowseAll(final View view) { - buildTopIndex(); - browse(getProjectRootIndexFile()); + if (browseAllInProgress) { + return; + } + // Regenerating the aggregate index walks every project's cache; keep it off the click handler. + final File index = getProjectRootIndexFile(); + browseAllInProgress = true; + showNotification(getString(R.string.browse_all_building)); + new Thread(new Runnable() { + @Override + public void run() { + try { + buildTopIndex(); + } finally { + browseAllInProgress = false; + } + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + if (!isFinishing() && !isDestroyed()) { + browse(index); + } + } + }); + } + }, "browse-all-index").start(); } /* diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 06eb7ef..e7085c9 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -143,6 +143,7 @@ Import existing mirrors Pick the folder that holds your older HTTrack mirrors (for example HTTrack on the internal storage). Their contents will be copied into this app\'s storage; the originals are left untouched. Importing mirrors… + Building index… Imported %1$d files (%2$d already present). Imported %1$d files; %2$d could not be copied. You can run the import again. Nothing to import from that folder.