diff --git a/app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java b/app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java
index da038dd10..96f701328 100644
--- a/app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java
+++ b/app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java
@@ -1,13 +1,18 @@
package org.fdroid.fdroid;
+import android.app.Instrumentation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
+import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
+import java.io.IOException;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -137,4 +142,34 @@ public class UtilsTest {
}
// TODO write tests that work with a Certificate
+
+ @Test
+ public void testClearOldFiles() throws IOException, InterruptedException {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ File dir = new File(TestUtils.getWriteableDir(instrumentation), "clearOldFiles");
+ FileUtils.deleteQuietly(dir);
+ dir.mkdirs();
+ assertTrue(dir.isDirectory());
+
+ File first = new File(dir, "first");
+ File second = new File(dir, "second");
+ assertFalse(first.exists());
+ assertFalse(second.exists());
+
+ first.createNewFile();
+ assertTrue(first.exists());
+
+ Thread.sleep(7000);
+ second.createNewFile();
+ assertTrue(second.exists());
+
+ Utils.clearOldFiles(dir, 3);
+ assertFalse(first.exists());
+ assertTrue(second.exists());
+
+ Thread.sleep(7000);
+ Utils.clearOldFiles(dir, 3);
+ assertFalse(first.exists());
+ assertFalse(second.exists());
+ }
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c499b50d7..b9b3419c2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -445,6 +445,9 @@
+
diff --git a/app/src/main/java/org/fdroid/fdroid/CleanCacheService.java b/app/src/main/java/org/fdroid/fdroid/CleanCacheService.java
new file mode 100644
index 000000000..48d5910c3
--- /dev/null
+++ b/app/src/main/java/org/fdroid/fdroid/CleanCacheService.java
@@ -0,0 +1,70 @@
+package org.fdroid.fdroid;
+
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+
+/**
+ * Handles cleaning up caches files that are not going to be used, and do not
+ * block the operation of the app itself. For things that must happen before
+ * F-Droid starts normal operation, that should go into
+ * {@link FDroidApp#onCreate()}
+ */
+public class CleanCacheService extends IntentService {
+ public static final String TAG = "CleanCacheService";
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, CleanCacheService.class);
+ context.startService(intent);
+ }
+
+ public CleanCacheService() {
+ super("CleanCacheService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
+
+ int cachetime;
+ if (Preferences.get().shouldCacheApks()) {
+ cachetime = Integer.MAX_VALUE;
+ } else {
+ cachetime = 3600; // keep for 1 hour to allow resumable downloads
+ }
+ Utils.clearOldFiles(Utils.getApkCacheDir(this), cachetime);
+ deleteStrayIndexFiles();
+ }
+
+ /**
+ * Delete index files which were downloaded, but not removed (e.g. due to F-Droid being
+ * force closed during processing of the file, before getting a chance to delete). This
+ * may include both "index-*-downloaded" and "index-*-extracted.xml" files.
+ *
+ * Note that if the SD card is not ready, then the cache directory will probably not be
+ * available. In this situation no files will be deleted (and thus they may still exist
+ * after the SD card becomes available).
+ */
+ private void deleteStrayIndexFiles() {
+ File cacheDir = getCacheDir();
+ if (cacheDir == null) {
+ return;
+ }
+
+ final File[] files = cacheDir.listFiles();
+ if (files == null) {
+ return;
+ }
+
+ for (File f : files) {
+ if (f.getName().startsWith("index-")) {
+ FileUtils.deleteQuietly(f);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java
index 6ec2355b8..4213dce38 100644
--- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java
+++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java
@@ -221,29 +221,7 @@ public class FDroidApp extends Application {
}
});
- // Clear cached apk files. We used to just remove them after they'd
- // been installed, but this causes problems for proprietary gapps
- // users since the introduction of verification (on pre-4.2 Android),
- // because the install intent says it's finished when it hasn't.
- if (!Preferences.get().shouldCacheApks()) {
- Utils.deleteFiles(Utils.getApkCacheDir(this), null, ".apk");
- }
-
- // Index files which downloaded, but were not removed (e.g. due to F-Droid being force
- // closed during processing of the file, before getting a chance to delete). This may
- // include both "index-*-downloaded" and "index-*-extracted.xml" files. The first is from
- // either signed or unsigned repos, and the later is from signed repos.
- Utils.deleteFiles(getCacheDir(), "index-", null);
-
- // As above, but for legacy F-Droid clients that downloaded under a different name, and
- // extracted to the files directory rather than the cache directory.
- // TODO: This can be removed in a a few months or a year (e.g. 2016) because people will
- // have upgraded their clients, this code will have executed, and they will not have any
- // left over files any more. Even if they do hold off upgrading until this code is removed,
- // the only side effect is that they will have a few more MiB of storage taken up on their
- // device until they uninstall and re-install F-Droid.
- Utils.deleteFiles(getCacheDir(), "dl-", null);
- Utils.deleteFiles(getFilesDir(), "index-", null);
+ CleanCacheService.start(this);
UpdateService.schedule(getApplicationContext());
bluetoothAdapter = getBluetoothAdapter();
diff --git a/app/src/main/java/org/fdroid/fdroid/Utils.java b/app/src/main/java/org/fdroid/fdroid/Utils.java
index c538274e4..fa6148ece 100644
--- a/app/src/main/java/org/fdroid/fdroid/Utils.java
+++ b/app/src/main/java/org/fdroid/fdroid/Utils.java
@@ -37,6 +37,7 @@ import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.utils.StorageUtils;
+import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.compat.FileCompat;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.Repo;
@@ -336,6 +337,26 @@ public final class Utils {
return apkCacheDir;
}
+ /**
+ * Recursively delete files in {@code dir} that were last modified
+ * {@code secondsAgo} seconds ago, e.g. when it was downloaded.
+ *
+ * @param dir The directory to recurse in
+ * @param secondsAgo The number of seconds old that marks a file for deletion.
+ */
+ public static void clearOldFiles(File dir, long secondsAgo) {
+ long olderThan = System.currentTimeMillis() - (secondsAgo * 1000L);
+ for (File f : dir.listFiles()) {
+ if (f.isDirectory()) {
+ clearOldFiles(f, olderThan);
+ f.delete();
+ }
+ if (FileUtils.isFileOlder(f, olderThan)) {
+ f.delete();
+ }
+ }
+ }
+
public static String calcFingerprint(String keyHexString) {
if (TextUtils.isEmpty(keyHexString)
|| keyHexString.matches(".*[^a-fA-F0-9].*")) {
@@ -650,40 +671,6 @@ public final class Utils {
}
}
- /**
- * Remove all files from the {@param directory} either beginning with {@param startsWith}
- * or ending with {@param endsWith}. Note that if the SD card is not ready, then the
- * cache directory will probably not be available. In this situation no files will be
- * deleted (and thus they may still exist after the SD card becomes available).
- */
- public static void deleteFiles(@Nullable File directory, @Nullable String startsWith, @Nullable String endsWith) {
-
- if (directory == null) {
- return;
- }
-
- final File[] files = directory.listFiles();
- if (files == null) {
- return;
- }
-
- if (startsWith != null) {
- debugLog(TAG, "Cleaning up files in " + directory + " that start with \"" + startsWith + "\"");
- }
-
- if (endsWith != null) {
- debugLog(TAG, "Cleaning up files in " + directory + " that end with \"" + endsWith + "\"");
- }
-
- for (File f : files) {
- if (((startsWith != null && f.getName().startsWith(startsWith))
- || (endsWith != null && f.getName().endsWith(endsWith)))
- && !f.delete()) {
- Log.w(TAG, "Couldn't delete cache file " + f);
- }
- }
- }
-
public static void debugLog(String tag, String msg) {
if (BuildConfig.DEBUG) {
Log.d(tag, msg);