From ecbbbfcc4d799cd41c18d4fd11f8bd57885df843 Mon Sep 17 00:00:00 2001 From: artdeell Date: Fri, 18 Aug 2023 19:39:44 +0300 Subject: [PATCH] Feat[icon]: icon cache cleaner --- .../net/kdt/pojavlaunch/LauncherActivity.java | 2 + .../imagecache/DownloadImageTask.java | 1 + .../modpacks/imagecache/IconCacheJanitor.java | 86 +++++++++++++++++++ .../modpacks/imagecache/ModIconCache.java | 5 +- .../modpacks/imagecache/ReadFromDiskTask.java | 1 + 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/IconCacheJanitor.java diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java index c88f2120a..43cf19e1c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java @@ -32,6 +32,7 @@ import net.kdt.pojavlaunch.extra.ExtraListener; import net.kdt.pojavlaunch.fragments.SelectAuthFragment; import net.kdt.pojavlaunch.modloaders.modpacks.ModloaderInstallTracker; +import net.kdt.pojavlaunch.modloaders.modpacks.imagecache.IconCacheJanitor; import net.kdt.pojavlaunch.multirt.MultiRTConfigDialog; import net.kdt.pojavlaunch.prefs.LauncherPreferences; import net.kdt.pojavlaunch.prefs.screens.LauncherPreferenceFragment; @@ -154,6 +155,7 @@ public class LauncherActivity extends BaseActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pojav_launcher); + IconCacheJanitor.runJanitor(); getWindow().setBackgroundDrawable(null); bindViews(); ProgressKeeper.addTaskCountListener((mProgressServiceKeeper = new ProgressServiceKeeper(this))); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/DownloadImageTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/DownloadImageTask.java index 52f6d47ea..3b3baa41f 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/DownloadImageTask.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/DownloadImageTask.java @@ -26,6 +26,7 @@ class DownloadImageTask implements Runnable { public boolean runCatching() { try { + IconCacheJanitor.waitForJanitorToFinish(); DownloadUtils.downloadFile(mParentTask.imageUrl, mParentTask.cacheFile); return true; }catch (IOException e) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/IconCacheJanitor.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/IconCacheJanitor.java new file mode 100644 index 000000000..f35fadeaa --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/IconCacheJanitor.java @@ -0,0 +1,86 @@ +package net.kdt.pojavlaunch.modloaders.modpacks.imagecache; + +import android.util.Log; + +import net.kdt.pojavlaunch.PojavApplication; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +/** + * This image is intended to keep the mod icon cache tidy (aka under 100 megabytes) + */ +public class IconCacheJanitor implements Runnable{ + public static final long CACHE_SIZE_LIMIT = 104857600; // The cache size limit, 100 megabytes + public static final long CACHE_BRINGDOWN = 52428800; // The size to which the cache should be brought + // in case of an overflow, 50 mb + private static Future sJanitorFuture; + private static boolean sJanitorRan = false; + private IconCacheJanitor() { + // don't allow others to create this + } + @Override + public void run() { + File modIconCachePath = ModIconCache.getImageCachePath(); + if(!modIconCachePath.isDirectory() || !modIconCachePath.canRead()) return; + File[] modIconFiles = modIconCachePath.listFiles(); + if(modIconFiles == null) return; + ArrayList writableModIconFiles = new ArrayList<>(modIconFiles.length); + long directoryFileSize = 0; + for(File modIconFile : modIconFiles) { + if(!modIconFile.isFile() || !modIconFile.canRead()) continue; + directoryFileSize += modIconFile.length(); + if(!modIconFile.canWrite()) continue; + writableModIconFiles.add(modIconFile); + } + if(directoryFileSize < CACHE_SIZE_LIMIT) { + Log.i("IconCacheJanitor", "Skipping cleanup because there's not enough to clean up"); + return; + } + Arrays.sort(modIconFiles, + (x,y)-> Long.compare(y.lastModified(), x.lastModified()) + ); + int filesCleanedUp = 0; + for(File modFile : writableModIconFiles) { + if(directoryFileSize < CACHE_BRINGDOWN) break; + long modFileSize = modFile.length(); + if(modFile.delete()) { + directoryFileSize -= modFileSize; + filesCleanedUp++; + } + } + Log.i("IconCacheJanitor", "Cleaned up "+filesCleanedUp+ " files"); + synchronized (IconCacheJanitor.class) { + sJanitorFuture = null; + sJanitorRan = true; + } + } + + /** + * Runs the janitor task, unless there was one running already or one has ran already + */ + public static void runJanitor() { + synchronized (IconCacheJanitor.class) { + if (sJanitorFuture != null || sJanitorRan) return; + sJanitorFuture = PojavApplication.sExecutorService.submit(new IconCacheJanitor()); + } + } + + /** + * Waits for the janitor task to finish, if there is one running already + * Note that the thread waiting must not be interrupted. + */ + public static void waitForJanitorToFinish() { + synchronized (IconCacheJanitor.class) { + if (sJanitorFuture == null) return; + try { + sJanitorFuture.get(); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException("Should not happen!", e); + } + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/ModIconCache.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/ModIconCache.java index 9efc94fff..af1775633 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/ModIconCache.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/ModIconCache.java @@ -27,13 +27,16 @@ public class ModIconCache { File cachePath; private final List> mCancelledReceivers = new ArrayList<>(); public ModIconCache() { - cachePath = new File(Tools.DIR_CACHE, "mod_icons"); + cachePath = getImageCachePath(); if(!cachePath.exists() && !cachePath.isFile() && Tools.DIR_CACHE.canWrite()) { if(!cachePath.mkdirs()) throw new RuntimeException("Failed to create icon cache directory"); } } + static File getImageCachePath() { + return new File(Tools.DIR_CACHE, "mod_icons"); + } /** * Get an image for a mod with the associated tag and URL to download it in case if its not cached diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/ReadFromDiskTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/ReadFromDiskTask.java index ac7f887fb..89f3ed41c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/ReadFromDiskTask.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/imagecache/ReadFromDiskTask.java @@ -30,6 +30,7 @@ public class ReadFromDiskTask implements Runnable { return; } if(cacheFile.canRead()) { + IconCacheJanitor.waitForJanitorToFinish(); Bitmap bitmap = BitmapFactory.decodeFile(cacheFile.getAbsolutePath()); if(bitmap != null) { Tools.runOnUiThread(()->{