Make filecache purging async and more optimised

multisite^2
Floens 8 years ago
parent 97a948504e
commit 9ff1f2d63c
  1. 122
      Clover/app/src/main/java/org/floens/chan/core/cache/FileCache.java

@ -29,6 +29,7 @@ import java.io.FileOutputStream;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -36,6 +37,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -58,7 +60,8 @@ public class FileCache {
private final File directory; private final File directory;
private final long maxSize; private final long maxSize;
private long size; private AtomicLong size = new AtomicLong();
private AtomicBoolean trimRunning = new AtomicBoolean(false);
private List<FileCacheDownloader> downloaders = new ArrayList<>(); private List<FileCacheDownloader> downloaders = new ArrayList<>();
@ -75,16 +78,8 @@ public class FileCache {
.protocols(Collections.singletonList(Protocol.HTTP_1_1)) .protocols(Collections.singletonList(Protocol.HTTP_1_1))
.build(); .build();
makeDir(); createDirectories();
calculateSize(); recalculateSize();
}
public void logStats() {
Logger.i(TAG, "Cache size = " + size + "/" + maxSize);
Logger.i(TAG, "downloaders.size() = " + downloaders.size());
for (FileCacheDownloader downloader : downloaders) {
Logger.i(TAG, "url = " + downloader.getUrl() + " cancelled = " + downloader.cancelled);
}
} }
public void clearCache() { public void clearCache() {
@ -100,7 +95,7 @@ public class FileCache {
} }
} }
} }
calculateSize(); recalculateSize();
} }
/** /**
@ -151,76 +146,99 @@ public class FileCache {
} }
public File get(String key) { public File get(String key) {
makeDir(); createDirectories();
return new File(directory, Integer.toString(key.hashCode())); return new File(directory, Integer.toString(key.hashCode()));
} }
private void put(File file) { private void createDirectories() {
size += file.length();
trim();
}
private boolean delete(File file) {
size -= file.length();
return file.delete();
}
private void makeDir() {
if (!directory.exists()) { if (!directory.exists()) {
if (!directory.mkdirs()) { if (!directory.mkdirs()) {
Logger.e(TAG, "Unable to create file cache dir " + directory.getAbsolutePath()); Logger.e(TAG, "Unable to create file cache dir " + directory.getAbsolutePath());
} else { } else {
calculateSize(); recalculateSize();
} }
} }
} }
private void fileWasAdded(File file) {
long adjustedSize = size.addAndGet(file.length());
if (adjustedSize > maxSize && trimRunning.compareAndSet(false, true)) {
executor.submit(new Runnable() {
@Override
public void run() {
try {
trim();
} catch (Exception e) {
Logger.e(TAG, "Error trimming", e);
} finally {
trimRunning.set(false);
}
}
});
}
}
// Called on a background thread
private void trim() { private void trim() {
File[] directoryFiles = directory.listFiles();
// Don't try to trim empty directories or just one image in it.
if (directoryFiles == null || directoryFiles.length <= 1) {
return;
}
List<File> files = new ArrayList<>(Arrays.asList(directoryFiles));
int trimmed = 0;
long workingSize = size.get();
int tries = 0; int tries = 0;
while (size > maxSize && tries++ < TRIM_TRIES) { while (workingSize > maxSize && tries++ < TRIM_TRIES) {
File[] files = directory.listFiles(); // Find the oldest file
if (files == null || files.length <= 1) { long oldest = Long.MAX_VALUE;
break; File oldestFile = null;
}
long age = Long.MAX_VALUE;
long last;
File oldest = null;
for (File file : files) { for (File file : files) {
last = file.lastModified(); long modified = file.lastModified();
if (last < age && last != 0L) { if (modified != 0L && modified < oldest) {
age = last; oldest = modified;
oldest = file; oldestFile = file;
} }
} }
if (oldest == null) { if (oldestFile != null) {
Logger.e(TAG, "No files to trim"); Logger.d(TAG, "Delete for trim" + oldestFile.getAbsolutePath());
break; workingSize -= oldestFile.length();
} else { trimmed++;
Logger.d(TAG, "Deleting " + oldest.getAbsolutePath()); files.remove(oldestFile);
if (!delete(oldest)) {
Logger.e(TAG, "Cannot delete cache file while trimming"); if (!oldestFile.delete()) {
calculateSize(); Logger.e(TAG, "Failed to delete cache file for trim");
break; break;
} }
} else {
Logger.e(TAG, "No files to trim");
break;
} }
}
calculateSize(); if (trimmed > 0) {
recalculateSize();
} }
} }
private void calculateSize() { // Called on a background thread
size = 0; private void recalculateSize() {
long calculatedSize = 0;
File[] files = directory.listFiles(); File[] files = directory.listFiles();
if (files != null) { if (files != null) {
for (File file : files) { for (File file : files) {
size += file.length(); calculatedSize += file.length();
} }
} }
size.set(calculatedSize);
} }
private void removeFromDownloaders(FileCacheDownloader downloader) { private void removeFromDownloaders(FileCacheDownloader downloader) {
@ -365,7 +383,7 @@ public class FileCache {
post(new Runnable() { post(new Runnable() {
@Override @Override
public void run() { public void run() {
fileCache.put(output); fileCache.fileWasAdded(output);
removeFromDownloadersList(); removeFromDownloadersList();
for (DownloadedCallback callback : callbacks) { for (DownloadedCallback callback : callbacks) {
callback.onProgress(0, 0, true); callback.onProgress(0, 0, true);

Loading…
Cancel
Save