diff --git a/README.md b/README.md
index f7d6078..f2bd7e6 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ repositories {
jcenter()
}
dependencies {
- compile 'com.danikula:videocache:2.2.0'
+ compile 'com.danikula:videocache:2.3.0'
}
```
diff --git a/library/build.gradle b/library/build.gradle
index 3bdd043..4e58e7d 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -26,7 +26,7 @@ publish {
userOrg = 'alexeydanilov'
groupId = 'com.danikula'
artifactId = 'videocache'
- publishVersion = '2.2.0'
+ publishVersion = '2.3.0'
description = 'Cache support for android VideoView'
website = 'https://github.com/danikula/AndroidVideoCache'
}
diff --git a/library/src/main/java/com/danikula/videocache/Config.java b/library/src/main/java/com/danikula/videocache/Config.java
new file mode 100644
index 0000000..bcf4e77
--- /dev/null
+++ b/library/src/main/java/com/danikula/videocache/Config.java
@@ -0,0 +1,30 @@
+package com.danikula.videocache;
+
+import com.danikula.videocache.file.DiskUsage;
+import com.danikula.videocache.file.FileNameGenerator;
+
+import java.io.File;
+
+/**
+ * Configuration for proxy cache.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+class Config {
+
+ public final File cacheRoot;
+ public final FileNameGenerator fileNameGenerator;
+ public final DiskUsage diskUsage;
+
+ Config(File cacheRoot, FileNameGenerator fileNameGenerator, DiskUsage diskUsage) {
+ this.cacheRoot = cacheRoot;
+ this.fileNameGenerator = fileNameGenerator;
+ this.diskUsage = diskUsage;
+ }
+
+ File generateCacheFile(String url) {
+ String name = fileNameGenerator.generate(url);
+ return new File(cacheRoot, name);
+ }
+
+}
diff --git a/library/src/main/java/com/danikula/videocache/HttpProxyCache.java b/library/src/main/java/com/danikula/videocache/HttpProxyCache.java
index 61681bd..221815a 100644
--- a/library/src/main/java/com/danikula/videocache/HttpProxyCache.java
+++ b/library/src/main/java/com/danikula/videocache/HttpProxyCache.java
@@ -2,6 +2,8 @@ package com.danikula.videocache;
import android.text.TextUtils;
+import com.danikula.videocache.file.FileCache;
+
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
diff --git a/library/src/main/java/com/danikula/videocache/HttpProxyCacheServer.java b/library/src/main/java/com/danikula/videocache/HttpProxyCacheServer.java
index 53f89d1..fc11c55 100644
--- a/library/src/main/java/com/danikula/videocache/HttpProxyCacheServer.java
+++ b/library/src/main/java/com/danikula/videocache/HttpProxyCacheServer.java
@@ -1,8 +1,16 @@
package com.danikula.videocache;
+import android.content.Context;
import android.os.SystemClock;
import android.util.Log;
+import com.danikula.videocache.file.DiskUsage;
+import com.danikula.videocache.file.FileNameGenerator;
+import com.danikula.videocache.file.Md5FileNameGenerator;
+import com.danikula.videocache.file.TotalCountLruDiskUsage;
+import com.danikula.videocache.file.TotalSizeLruDiskUsage;
+
+import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
@@ -56,11 +64,15 @@ public class HttpProxyCacheServer {
private final ServerSocket serverSocket;
private final int port;
private final Thread waitConnectionThread;
- private final FileNameGenerator fileNameGenerator;
+ private final Config config;
private boolean pinged;
- public HttpProxyCacheServer(FileNameGenerator fileNameGenerator) {
- this.fileNameGenerator = checkNotNull(fileNameGenerator);
+ public HttpProxyCacheServer(Context context) {
+ this(new Builder(context).buildConfig());
+ }
+
+ private HttpProxyCacheServer(Config config) {
+ this.config = checkNotNull(config);
try {
InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
this.serverSocket = new ServerSocket(0, 8, inetAddress);
@@ -231,7 +243,7 @@ public class HttpProxyCacheServer {
synchronized (clientsLock) {
HttpProxyCacheServerClients clients = clientsMap.get(url);
if (clients == null) {
- clients = new HttpProxyCacheServerClients(url, fileNameGenerator);
+ clients = new HttpProxyCacheServerClients(url, config);
clientsMap.put(url, clients);
}
return clients;
@@ -328,4 +340,94 @@ public class HttpProxyCacheServer {
return pingServer();
}
}
+
+ /**
+ * Builder for {@link HttpProxyCacheServer}.
+ */
+ public static final class Builder {
+
+ private static final long DEFAULT_MAX_SIZE = 512 * 104 * 1024;
+
+ private File cacheRoot;
+ private FileNameGenerator fileNameGenerator;
+ private DiskUsage diskUsage;
+
+ public Builder(Context context) {
+ this.cacheRoot = StorageUtils.getIndividualCacheDirectory(context);
+ this.diskUsage = new TotalSizeLruDiskUsage(DEFAULT_MAX_SIZE);
+ this.fileNameGenerator = new Md5FileNameGenerator();
+ }
+
+ /**
+ * Overrides default cache folder to be used for caching files.
+ *
+ * By default AndroidVideoCache uses
+ * '/Android/data/[app_package_name]/cache/video-cache/' if card is mounted and app has appropriate permission
+ * or 'video-cache' subdirectory in default application's cache directory otherwise.
+ *
+ * Note directory must be used only for AndroidVideoCache files.
+ *
+ * @param file a cache directory, can't be null.
+ * @return a builder.
+ */
+ public Builder cacheDirectory(File file) {
+ this.cacheRoot = checkNotNull(file);
+ return this;
+ }
+
+ /**
+ * Overrides default cache file name generator {@link Md5FileNameGenerator} .
+ *
+ * @param fileNameGenerator a new file name generator.
+ * @return a builder.
+ */
+ public Builder fileNameGenerator(FileNameGenerator fileNameGenerator) {
+ this.fileNameGenerator = checkNotNull(fileNameGenerator);
+ return this;
+ }
+
+ /**
+ * Sets max cache size in bytes.
+ * All files that exceeds limit will be deleted using LRU strategy.
+ * Default value is 512 Mb.
+ *
+ * Note this method overrides result of calling {@link #maxCacheFilesCount(int)}
+ *
+ * @param maxSize max cache size in bytes.
+ * @return a builder.
+ */
+ public Builder maxCacheSize(long maxSize) {
+ this.diskUsage = new TotalSizeLruDiskUsage(maxSize);
+ return this;
+ }
+
+ /**
+ * Sets max cache files count.
+ * All files that exceeds limit will be deleted using LRU strategy.
+ *
+ * Note this method overrides result of calling {@link #maxCacheSize(long)}
+ *
+ * @param count max cache files count.
+ * @return a builder.
+ */
+ public Builder maxCacheFilesCount(int count) {
+ this.diskUsage = new TotalCountLruDiskUsage(count);
+ return this;
+ }
+
+ /**
+ * Builds new instance of {@link HttpProxyCacheServer}.
+ *
+ * @return proxy cache. Only single instance should be used across whole app.
+ */
+ public HttpProxyCacheServer build() {
+ Config config = buildConfig();
+ return new HttpProxyCacheServer(config);
+ }
+
+ private Config buildConfig() {
+ return new Config(cacheRoot, fileNameGenerator, diskUsage);
+ }
+
+ }
}
diff --git a/library/src/main/java/com/danikula/videocache/HttpProxyCacheServerClients.java b/library/src/main/java/com/danikula/videocache/HttpProxyCacheServerClients.java
index f46147d..8296d2a 100644
--- a/library/src/main/java/com/danikula/videocache/HttpProxyCacheServerClients.java
+++ b/library/src/main/java/com/danikula/videocache/HttpProxyCacheServerClients.java
@@ -4,6 +4,8 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import com.danikula.videocache.file.FileCache;
+
import java.io.File;
import java.io.IOException;
import java.net.Socket;
@@ -24,12 +26,12 @@ final class HttpProxyCacheServerClients {
private final String url;
private volatile HttpProxyCache proxyCache;
private final List listeners = new CopyOnWriteArrayList<>();
- private final FileNameGenerator fileNameGenerator;
private final CacheListener uiCacheListener;
+ private final Config config;
- public HttpProxyCacheServerClients(String url, FileNameGenerator fileNameGenerator) {
+ public HttpProxyCacheServerClients(String url, Config config) {
this.url = checkNotNull(url);
- this.fileNameGenerator = checkNotNull(fileNameGenerator);
+ this.config = checkNotNull(config);
this.uiCacheListener = new UiListenerHandler(url, listeners);
}
@@ -78,7 +80,7 @@ final class HttpProxyCacheServerClients {
private HttpProxyCache newHttpProxyCache() throws ProxyCacheException {
HttpUrlSource source = new HttpUrlSource(url);
- FileCache cache = new FileCache(fileNameGenerator.generate(url));
+ FileCache cache = new FileCache(config.generateCacheFile(url), config.diskUsage);
HttpProxyCache httpProxyCache = new HttpProxyCache(source, cache);
httpProxyCache.registerCacheListener(uiCacheListener);
return httpProxyCache;
diff --git a/library/src/main/java/com/danikula/videocache/ProxyCacheUtils.java b/library/src/main/java/com/danikula/videocache/ProxyCacheUtils.java
index ea567c6..226fa79 100644
--- a/library/src/main/java/com/danikula/videocache/ProxyCacheUtils.java
+++ b/library/src/main/java/com/danikula/videocache/ProxyCacheUtils.java
@@ -5,7 +5,6 @@ import android.util.Log;
import android.webkit.MimeTypeMap;
import java.io.Closeable;
-import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@@ -22,7 +21,7 @@ import static com.danikula.videocache.Preconditions.checkNotNull;
*
* @author Alexey Danilov (danikula@gmail.com).
*/
-class ProxyCacheUtils {
+public class ProxyCacheUtils {
static final String LOG_TAG = "ProxyCache";
static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
@@ -50,19 +49,6 @@ class ProxyCacheUtils {
return preview;
}
- static void createDirectory(File directory) throws IOException {
- checkNotNull(directory, "File must be not null!");
- if (directory.exists()) {
- checkArgument(directory.isDirectory(), "File is not directory!");
- } else {
- boolean isCreated = directory.mkdirs();
- if (!isCreated) {
- String error = String.format("Directory %s can't be created", directory.getAbsolutePath());
- throw new IOException(error);
- }
- }
- }
-
static String encode(String url) {
try {
return URLEncoder.encode(url, "utf-8");
@@ -89,7 +75,7 @@ class ProxyCacheUtils {
}
}
- static String computeMD5(String string) {
+ public static String computeMD5(String string) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] digestBytes = messageDigest.digest(string.getBytes());
@@ -99,12 +85,11 @@ class ProxyCacheUtils {
}
}
- static String bytesToHexString(byte[] bytes) {
+ private static String bytesToHexString(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
-
}
diff --git a/library/src/main/java/com/danikula/videocache/StorageUtils.java b/library/src/main/java/com/danikula/videocache/StorageUtils.java
new file mode 100644
index 0000000..3772f29
--- /dev/null
+++ b/library/src/main/java/com/danikula/videocache/StorageUtils.java
@@ -0,0 +1,81 @@
+package com.danikula.videocache;
+
+import android.content.Context;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+
+import static android.os.Environment.MEDIA_MOUNTED;
+import static com.danikula.videocache.ProxyCacheUtils.LOG_TAG;
+
+/**
+ * Provides application storage paths
+ *
+ * See https://github.com/nostra13/Android-Universal-Image-Loader
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.0.0
+ */
+final class StorageUtils {
+
+ private static final String INDIVIDUAL_DIR_NAME = "video-cache";
+
+ /**
+ * Returns individual application cache directory (for only video caching from Proxy). Cache directory will be
+ * created on SD card ("/Android/data/[app_package_name]/cache/video-cache") if card is mounted .
+ * Else - Android defines cache directory on device's file system.
+ *
+ * @param context Application context
+ * @return Cache {@link File directory}
+ */
+ public static File getIndividualCacheDirectory(Context context) {
+ File cacheDir = getCacheDirectory(context, true);
+ return new File(cacheDir, INDIVIDUAL_DIR_NAME);
+ }
+
+ /**
+ * Returns application cache directory. Cache directory will be created on SD card
+ * ("/Android/data/[app_package_name]/cache") (if card is mounted and app has appropriate permission) or
+ * on device's file system depending incoming parameters.
+ *
+ * @param context Application context
+ * @param preferExternal Whether prefer external location for cache
+ * @return Cache {@link File directory}.
+ * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and
+ * {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null).
+ */
+ private static File getCacheDirectory(Context context, boolean preferExternal) {
+ File appCacheDir = null;
+ String externalStorageState;
+ try {
+ externalStorageState = Environment.getExternalStorageState();
+ } catch (NullPointerException e) { // (sh)it happens
+ externalStorageState = "";
+ }
+ if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState)) {
+ appCacheDir = getExternalCacheDir(context);
+ }
+ if (appCacheDir == null) {
+ appCacheDir = context.getCacheDir();
+ }
+ if (appCacheDir == null) {
+ String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
+ Log.w(LOG_TAG, "Can't define system cache directory! '" + cacheDirPath + "%s' will be used.");
+ appCacheDir = new File(cacheDirPath);
+ }
+ return appCacheDir;
+ }
+
+ private static File getExternalCacheDir(Context context) {
+ File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
+ File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
+ if (!appCacheDir.exists()) {
+ if (!appCacheDir.mkdirs()) {
+ Log.w(LOG_TAG, "Unable to create external cache directory");
+ return null;
+ }
+ }
+ return appCacheDir;
+ }
+}
diff --git a/library/src/main/java/com/danikula/videocache/file/DiskUsage.java b/library/src/main/java/com/danikula/videocache/file/DiskUsage.java
new file mode 100644
index 0000000..1a43694
--- /dev/null
+++ b/library/src/main/java/com/danikula/videocache/file/DiskUsage.java
@@ -0,0 +1,15 @@
+package com.danikula.videocache.file;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Declares how {@link FileCache} will use disc space.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+public interface DiskUsage {
+
+ void touch(File file) throws IOException;
+
+}
diff --git a/library/src/main/java/com/danikula/videocache/FileCache.java b/library/src/main/java/com/danikula/videocache/file/FileCache.java
similarity index 86%
rename from library/src/main/java/com/danikula/videocache/FileCache.java
rename to library/src/main/java/com/danikula/videocache/file/FileCache.java
index a99034c..dc4a68e 100644
--- a/library/src/main/java/com/danikula/videocache/FileCache.java
+++ b/library/src/main/java/com/danikula/videocache/file/FileCache.java
@@ -1,11 +1,12 @@
-package com.danikula.videocache;
+package com.danikula.videocache.file;
+
+import com.danikula.videocache.Cache;
+import com.danikula.videocache.ProxyCacheException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
-import static com.danikula.videocache.Preconditions.checkNotNull;
-
/**
* {@link Cache} that uses file for storing data.
*
@@ -15,13 +16,22 @@ public class FileCache implements Cache {
private static final String TEMP_POSTFIX = ".download";
+ private final DiskUsage diskUsage;
public File file;
private RandomAccessFile dataFile;
public FileCache(File file) throws ProxyCacheException {
+ this(file, new UnlimitedDiskUsage());
+ }
+
+ public FileCache(File file, DiskUsage diskUsage) throws ProxyCacheException {
try {
- checkNotNull(file);
- ProxyCacheUtils.createDirectory(file.getParentFile());
+ if (diskUsage == null) {
+ throw new NullPointerException();
+ }
+ this.diskUsage = diskUsage;
+ File directory = file.getParentFile();
+ Files.makeDir(directory);
boolean completed = file.exists();
this.file = completed ? file : new File(file.getParentFile(), file.getName() + TEMP_POSTFIX);
this.dataFile = new RandomAccessFile(this.file, completed ? "r" : "rw");
@@ -68,6 +78,7 @@ public class FileCache implements Cache {
public synchronized void close() throws ProxyCacheException {
try {
dataFile.close();
+ diskUsage.touch(file);
} catch (IOException e) {
throw new ProxyCacheException("Error closing file " + file, e);
}
diff --git a/library/src/main/java/com/danikula/videocache/FileNameGenerator.java b/library/src/main/java/com/danikula/videocache/file/FileNameGenerator.java
similarity index 63%
rename from library/src/main/java/com/danikula/videocache/FileNameGenerator.java
rename to library/src/main/java/com/danikula/videocache/file/FileNameGenerator.java
index afc7c6d..cd8b344 100644
--- a/library/src/main/java/com/danikula/videocache/FileNameGenerator.java
+++ b/library/src/main/java/com/danikula/videocache/file/FileNameGenerator.java
@@ -1,6 +1,4 @@
-package com.danikula.videocache;
-
-import java.io.File;
+package com.danikula.videocache.file;
/**
* Generator for files to be used for caching.
@@ -9,6 +7,6 @@ import java.io.File;
*/
public interface FileNameGenerator {
- File generate(String url);
+ String generate(String url);
}
diff --git a/library/src/main/java/com/danikula/videocache/file/Files.java b/library/src/main/java/com/danikula/videocache/file/Files.java
new file mode 100644
index 0000000..cf8b665
--- /dev/null
+++ b/library/src/main/java/com/danikula/videocache/file/Files.java
@@ -0,0 +1,88 @@
+package com.danikula.videocache.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Utils for work with files.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+class Files {
+
+ static void makeDir(File directory) throws IOException {
+ if (directory.exists()) {
+ if (!directory.isDirectory()) {
+ throw new IOException("File " + directory + " is not directory!");
+ }
+ } else {
+ boolean isCreated = directory.mkdirs();
+ if (!isCreated) {
+ throw new IOException(String.format("Directory %s can't be created", directory.getAbsolutePath()));
+ }
+ }
+ }
+
+ static List getLruListFiles(File directory) {
+ List result = new LinkedList<>();
+ File[] files = directory.listFiles();
+ if (files != null) {
+ result = Arrays.asList(files);
+ Collections.sort(result, new LastModifiedComparator());
+ }
+ return result;
+ }
+
+ static void setLastModifiedNow(File file) throws IOException {
+ if (file.exists()) {
+ long now = System.currentTimeMillis();
+ boolean modified = file.setLastModified(now); // on some devices (e.g. Nexus 5) doesn't work
+ if (!modified) {
+ modify(file);
+ if (file.lastModified() < now) {
+ throw new IOException("Error set last modified date to " + file);
+ }
+ }
+ }
+ }
+
+ static void modify(File file) throws IOException {
+ long size = file.length();
+ if (size == 0) {
+ recreateZeroSizeFile(file);
+ return;
+ }
+
+ RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
+ accessFile.seek(size - 1);
+ byte lastByte = accessFile.readByte();
+ accessFile.seek(size - 1);
+ accessFile.write(lastByte);
+ accessFile.close();
+ }
+
+ private static void recreateZeroSizeFile(File file) throws IOException {
+ if (!file.delete() || !file.createNewFile()) {
+ throw new IOException("Error recreate zero-size file " + file);
+ }
+ }
+
+ private static final class LastModifiedComparator implements Comparator {
+
+ @Override
+ public int compare(File lhs, File rhs) {
+ return compareLong(lhs.lastModified(), rhs.lastModified());
+ }
+
+ private int compareLong(long first, long second) {
+ return (first < second) ? -1 : ((first == second) ? 0 : 1);
+ }
+ }
+
+}
diff --git a/library/src/main/java/com/danikula/videocache/file/LruDiskUsage.java b/library/src/main/java/com/danikula/videocache/file/LruDiskUsage.java
new file mode 100644
index 0000000..f8925f2
--- /dev/null
+++ b/library/src/main/java/com/danikula/videocache/file/LruDiskUsage.java
@@ -0,0 +1,76 @@
+package com.danikula.videocache.file;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * {@link DiskUsage} that uses LRU (Least Recently Used) strategy to trim cache.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+abstract class LruDiskUsage implements DiskUsage {
+
+ private static final String LOG_TAG = "ProxyCache";
+ private final ExecutorService workerThread = Executors.newSingleThreadExecutor();
+
+ @Override
+ public void touch(File file) throws IOException {
+ workerThread.submit(new TouchCallable(file));
+ }
+
+ private void touchInBackground(File file) throws IOException {
+ Files.setLastModifiedNow(file);
+ List files = Files.getLruListFiles(file.getParentFile());
+ trim(files);
+ }
+
+ protected abstract boolean accept(File file, long totalSize, int totalCount);
+
+ private void trim(List files) {
+ long totalSize = countTotalSize(files);
+ int totalCount = files.size();
+ for (File file : files) {
+ boolean accepted = accept(file, totalSize, totalCount);
+ if (!accepted) {
+ long fileSize = file.length();
+ boolean deleted = file.delete();
+ if (deleted) {
+ totalCount--;
+ totalSize -= fileSize;
+ Log.i(LOG_TAG, "Cache file " + file + " is deleted because it exceeds cache limit");
+ } else {
+ Log.e(LOG_TAG, "Error deleting file " + file + " for trimming cache");
+ }
+ }
+ }
+ }
+
+ private long countTotalSize(List files) {
+ long totalSize = 0;
+ for (File file : files) {
+ totalSize += file.length();
+ }
+ return totalSize;
+ }
+
+ private class TouchCallable implements Callable {
+
+ private final File file;
+
+ public TouchCallable(File file) {
+ this.file = file;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ touchInBackground(file);
+ return null;
+ }
+ }
+}
diff --git a/library/src/main/java/com/danikula/videocache/Md5FileNameGenerator.java b/library/src/main/java/com/danikula/videocache/file/Md5FileNameGenerator.java
similarity index 62%
rename from library/src/main/java/com/danikula/videocache/Md5FileNameGenerator.java
rename to library/src/main/java/com/danikula/videocache/file/Md5FileNameGenerator.java
index 6d460b8..27d72ee 100644
--- a/library/src/main/java/com/danikula/videocache/Md5FileNameGenerator.java
+++ b/library/src/main/java/com/danikula/videocache/file/Md5FileNameGenerator.java
@@ -1,10 +1,8 @@
-package com.danikula.videocache;
+package com.danikula.videocache.file;
import android.text.TextUtils;
-import java.io.File;
-
-import static com.danikula.videocache.Preconditions.checkNotNull;
+import com.danikula.videocache.ProxyCacheUtils;
/**
* Implementation of {@link FileNameGenerator} that uses MD5 of url as file name
@@ -14,19 +12,12 @@ import static com.danikula.videocache.Preconditions.checkNotNull;
public class Md5FileNameGenerator implements FileNameGenerator {
private static final int MAX_EXTENSION_LENGTH = 4;
- private final File cacheDirectory;
-
- public Md5FileNameGenerator(File cacheDirectory) {
- this.cacheDirectory = checkNotNull(cacheDirectory);
- }
@Override
- public File generate(String url) {
- checkNotNull(url);
+ public String generate(String url) {
String extension = getExtension(url);
String name = ProxyCacheUtils.computeMD5(url);
- name = TextUtils.isEmpty(extension) ? name : name + "." + extension;
- return new File(cacheDirectory, name);
+ return TextUtils.isEmpty(extension) ? name : name + "." + extension;
}
private String getExtension(String url) {
diff --git a/library/src/main/java/com/danikula/videocache/file/TotalCountLruDiskUsage.java b/library/src/main/java/com/danikula/videocache/file/TotalCountLruDiskUsage.java
new file mode 100644
index 0000000..784b658
--- /dev/null
+++ b/library/src/main/java/com/danikula/videocache/file/TotalCountLruDiskUsage.java
@@ -0,0 +1,25 @@
+package com.danikula.videocache.file;
+
+import java.io.File;
+
+/**
+ * {@link DiskUsage} that uses LRU (Least Recently Used) strategy and trims cache size to max files count if needed.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+public class TotalCountLruDiskUsage extends LruDiskUsage {
+
+ private final int maxCount;
+
+ public TotalCountLruDiskUsage(int maxCount) {
+ if (maxCount <= 0) {
+ throw new IllegalArgumentException("Max count must be positive number!");
+ }
+ this.maxCount = maxCount;
+ }
+
+ @Override
+ protected boolean accept(File file, long totalSize, int totalCount) {
+ return totalCount <= maxCount;
+ }
+}
diff --git a/library/src/main/java/com/danikula/videocache/file/TotalSizeLruDiskUsage.java b/library/src/main/java/com/danikula/videocache/file/TotalSizeLruDiskUsage.java
new file mode 100644
index 0000000..7dd8e27
--- /dev/null
+++ b/library/src/main/java/com/danikula/videocache/file/TotalSizeLruDiskUsage.java
@@ -0,0 +1,25 @@
+package com.danikula.videocache.file;
+
+import java.io.File;
+
+/**
+ * {@link DiskUsage} that uses LRU (Least Recently Used) strategy and trims cache size to max size if needed.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+public class TotalSizeLruDiskUsage extends LruDiskUsage {
+
+ private final long maxSize;
+
+ public TotalSizeLruDiskUsage(long maxSize) {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("Max size must be positive number!");
+ }
+ this.maxSize = maxSize;
+ }
+
+ @Override
+ protected boolean accept(File file, long totalSize, int totalCount) {
+ return totalSize <= maxSize;
+ }
+}
diff --git a/library/src/main/java/com/danikula/videocache/file/UnlimitedDiskUsage.java b/library/src/main/java/com/danikula/videocache/file/UnlimitedDiskUsage.java
new file mode 100644
index 0000000..85ae66c
--- /dev/null
+++ b/library/src/main/java/com/danikula/videocache/file/UnlimitedDiskUsage.java
@@ -0,0 +1,17 @@
+package com.danikula.videocache.file;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Unlimited version of {@link DiskUsage}.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+public class UnlimitedDiskUsage implements DiskUsage {
+
+ @Override
+ public void touch(File file) throws IOException {
+ // do nothing
+ }
+}
diff --git a/sample/build.gradle b/sample/build.gradle
index c05b9c1..b525bec 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -35,10 +35,10 @@ apt {
}
dependencies {
-// compile project(':library')
+ compile project(':library')
compile 'com.android.support:support-v4:23.1.0'
compile 'org.androidannotations:androidannotations-api:3.3.2'
- compile 'com.danikula:videocache:2.2.0'
+// compile 'com.danikula:videocache:2.2.0'
compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar'
apt 'org.androidannotations:androidannotations:3.3.2'
}
diff --git a/sample/src/main/java/com/danikula/videocache/sample/App.java b/sample/src/main/java/com/danikula/videocache/sample/App.java
index 9372f1c..6a4118c 100644
--- a/sample/src/main/java/com/danikula/videocache/sample/App.java
+++ b/sample/src/main/java/com/danikula/videocache/sample/App.java
@@ -3,9 +3,7 @@ package com.danikula.videocache.sample;
import android.app.Application;
import android.content.Context;
-import com.danikula.videocache.FileNameGenerator;
import com.danikula.videocache.HttpProxyCacheServer;
-import com.danikula.videocache.Md5FileNameGenerator;
/**
* @author Alexey Danilov (danikula@gmail.com).
@@ -20,7 +18,6 @@ public class App extends Application {
}
private HttpProxyCacheServer newProxy() {
- FileNameGenerator nameGenerator = new Md5FileNameGenerator(getExternalCacheDir());
- return new HttpProxyCacheServer(nameGenerator);
+ return new HttpProxyCacheServer(this);
}
}
diff --git a/test/src/test/java/com/danikula/videocache/FileNameGeneratorTest.java b/test/src/test/java/com/danikula/videocache/FileNameGeneratorTest.java
index 0fdd88d..ea8a4a0 100644
--- a/test/src/test/java/com/danikula/videocache/FileNameGeneratorTest.java
+++ b/test/src/test/java/com/danikula/videocache/FileNameGeneratorTest.java
@@ -1,5 +1,7 @@
package com.danikula.videocache;
+import com.danikula.videocache.file.FileNameGenerator;
+import com.danikula.videocache.file.Md5FileNameGenerator;
import com.danikula.videocache.test.BuildConfig;
import org.junit.Test;
@@ -62,21 +64,16 @@ public class FileNameGeneratorTest {
assertThat(path).isEqualTo(expected);
}
- @Test(expected = NullPointerException.class)
- public void testAssertNullRoot() throws Exception {
- new Md5FileNameGenerator(null);
- fail("Root folder should be not null");
- }
-
@Test(expected = NullPointerException.class)
public void testAssertNullUrl() throws Exception {
- FileNameGenerator nameGenerator = new Md5FileNameGenerator(new File("/"));
+ FileNameGenerator nameGenerator = new Md5FileNameGenerator();
nameGenerator.generate(null);
fail("Url should be not null");
}
private String generateMd5Name(String rootFolder, String url) {
- FileNameGenerator nameGenerator = new Md5FileNameGenerator(new File(rootFolder));
- return nameGenerator.generate(url).getAbsolutePath();
+ FileNameGenerator nameGenerator = new Md5FileNameGenerator();
+ String name = nameGenerator.generate(url);
+ return new File(rootFolder, name).getAbsolutePath();
}
}
diff --git a/test/src/test/java/com/danikula/videocache/HttpProxyCacheServerTest.java b/test/src/test/java/com/danikula/videocache/HttpProxyCacheServerTest.java
index 8aaa9da..f6da326 100644
--- a/test/src/test/java/com/danikula/videocache/HttpProxyCacheServerTest.java
+++ b/test/src/test/java/com/danikula/videocache/HttpProxyCacheServerTest.java
@@ -3,10 +3,13 @@ package com.danikula.videocache;
import android.util.Pair;
import com.danikula.android.garden.io.IoUtils;
+import com.danikula.videocache.file.FileNameGenerator;
+import com.danikula.videocache.file.Md5FileNameGenerator;
import com.danikula.videocache.support.ProxyCacheTestUtils;
import com.danikula.videocache.support.Response;
import com.danikula.videocache.test.BuildConfig;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
@@ -17,12 +20,18 @@ import java.io.File;
import java.io.IOException;
import java.util.Arrays;
+import static com.danikula.android.garden.io.Files.cleanDirectory;
+import static com.danikula.android.garden.io.Files.createDirectory;
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_BIG_NAME;
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_NAME;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_SIZE;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL_ONE_REDIRECT;
+import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_SIZE;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL;
+import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_3_REDIRECTS;
+import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_6_REDIRECTS;
+import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_ONE_REDIRECT;
import static com.danikula.videocache.support.ProxyCacheTestUtils.getFileContent;
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
import static com.danikula.videocache.support.ProxyCacheTestUtils.readProxyResponse;
@@ -35,6 +44,15 @@ import static org.fest.assertions.api.Assertions.assertThat;
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION)
public class HttpProxyCacheServerTest {
+ private File cacheFolder;
+
+ @Before
+ public void setup() throws Exception {
+ cacheFolder = ProxyCacheTestUtils.newCacheFile();
+ createDirectory(cacheFolder);
+ cleanDirectory(cacheFolder);
+ }
+
@Test
public void testHttpProxyCache() throws Exception {
Pair response = readProxyData(HTTP_DATA_URL);
@@ -46,14 +64,14 @@ public class HttpProxyCacheServerTest {
@Test
public void testProxyContentWithPartialCache() throws Exception {
- FileNameGenerator fileNameGenerator = new Md5FileNameGenerator(RuntimeEnvironment.application.getExternalCacheDir());
- File file = fileNameGenerator.generate(HTTP_DATA_URL);
+ File cacheDir = RuntimeEnvironment.application.getExternalCacheDir();
+ File file = new File(cacheDir, new Md5FileNameGenerator().generate(HTTP_DATA_URL));
int partialCacheSize = 1000;
byte[] partialData = ProxyCacheTestUtils.generate(partialCacheSize);
File partialCacheFile = ProxyCacheTestUtils.getTempFile(file);
IoUtils.saveToFile(partialData, partialCacheFile);
- HttpProxyCacheServer proxy = new HttpProxyCacheServer(fileNameGenerator);
+ HttpProxyCacheServer proxy = newProxy(cacheDir);
Response response = readProxyResponse(proxy, HTTP_DATA_URL);
proxy.shutdown();
@@ -132,11 +150,70 @@ public class HttpProxyCacheServerTest {
assertThat(getFileContent(response.first)).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME));
}
+ @Test
+ public void testMaxSizeCacheLimit() throws Exception {
+ HttpProxyCacheServer proxy = new HttpProxyCacheServer.Builder(RuntimeEnvironment.application)
+ .cacheDirectory(cacheFolder)
+ .maxCacheSize(HTTP_DATA_SIZE * 3 - 1) // for 2 files
+ .build();
+
+ // use different url (doesn't matter than same content)
+ readProxyResponse(proxy, HTTP_DATA_URL, 0);
+ Thread.sleep(1050); // wait for new last modified date (file rounds time to second)
+
+ readProxyResponse(proxy, HTTP_DATA_URL_ONE_REDIRECT, 0);
+ Thread.sleep(1050);
+
+ readProxyResponse(proxy, HTTP_DATA_URL_3_REDIRECTS, 0);
+ Thread.sleep(1050);
+
+ assertThat(file(cacheFolder, HTTP_DATA_URL)).doesNotExist();
+ assertThat(file(cacheFolder, HTTP_DATA_URL_ONE_REDIRECT)).exists();
+ assertThat(file(cacheFolder, HTTP_DATA_URL_3_REDIRECTS)).exists();
+
+ readProxyResponse(proxy, HTTP_DATA_URL_ONE_REDIRECT, 0); // touch file
+ readProxyResponse(proxy, HTTP_DATA_URL_6_REDIRECTS, 0);
+ proxy.shutdown();
+
+ assertThat(file(cacheFolder, HTTP_DATA_URL_3_REDIRECTS)).doesNotExist();
+ assertThat(file(cacheFolder, HTTP_DATA_URL_ONE_REDIRECT)).exists();
+ assertThat(file(cacheFolder, HTTP_DATA_URL_6_REDIRECTS)).exists();
+ }
+
+ @Test
+ public void testMaxFileCacheLimit() throws Exception {
+ HttpProxyCacheServer proxy = new HttpProxyCacheServer.Builder(RuntimeEnvironment.application)
+ .cacheDirectory(cacheFolder)
+ .maxCacheFilesCount(2)
+ .build();
+
+ // use different url (doesn't matter than same content)
+ readProxyResponse(proxy, HTTP_DATA_URL, 0);
+ Thread.sleep(1050); // wait for new last modified date (file rounds time to second)
+
+ readProxyResponse(proxy, HTTP_DATA_URL_ONE_REDIRECT, 0);
+ Thread.sleep(1050);
+
+ readProxyResponse(proxy, HTTP_DATA_URL_3_REDIRECTS, 0);
+ Thread.sleep(1050);
+
+ assertThat(file(cacheFolder, HTTP_DATA_URL)).doesNotExist();
+ assertThat(file(cacheFolder, HTTP_DATA_URL_ONE_REDIRECT)).exists();
+ assertThat(file(cacheFolder, HTTP_DATA_URL_3_REDIRECTS)).exists();
+
+ readProxyResponse(proxy, HTTP_DATA_URL_ONE_REDIRECT, 0); // touch file
+ readProxyResponse(proxy, HTTP_DATA_URL_6_REDIRECTS, 0);
+ proxy.shutdown();
+
+ assertThat(file(cacheFolder, HTTP_DATA_URL_3_REDIRECTS)).doesNotExist();
+ assertThat(file(cacheFolder, HTTP_DATA_URL_ONE_REDIRECT)).exists();
+ assertThat(file(cacheFolder, HTTP_DATA_URL_6_REDIRECTS)).exists();
+ }
+
private Pair readProxyData(String url, int offset) throws IOException {
File externalCacheDir = RuntimeEnvironment.application.getExternalCacheDir();
- FileNameGenerator fileNameGenerator = new Md5FileNameGenerator(externalCacheDir);
- File file = fileNameGenerator.generate(url);
- HttpProxyCacheServer proxy = new HttpProxyCacheServer(fileNameGenerator);
+ File file = file(externalCacheDir, url);
+ HttpProxyCacheServer proxy = newProxy(externalCacheDir);
Response response = readProxyResponse(proxy, url, offset);
proxy.shutdown();
@@ -144,7 +221,19 @@ public class HttpProxyCacheServerTest {
return new Pair<>(file, response);
}
+ private File file(File parent, String url) {
+ FileNameGenerator fileNameGenerator = new Md5FileNameGenerator();
+ String name = fileNameGenerator.generate(url);
+ return new File(parent, name);
+ }
+
private Pair readProxyData(String url) throws IOException {
return readProxyData(url, -1);
}
+
+ private HttpProxyCacheServer newProxy(File cacheDir) {
+ return new HttpProxyCacheServer.Builder(RuntimeEnvironment.application)
+ .cacheDirectory(cacheDir)
+ .build();
+ }
}
diff --git a/test/src/test/java/com/danikula/videocache/HttpProxyCacheTest.java b/test/src/test/java/com/danikula/videocache/HttpProxyCacheTest.java
index b1b2d7b..c558e17 100644
--- a/test/src/test/java/com/danikula/videocache/HttpProxyCacheTest.java
+++ b/test/src/test/java/com/danikula/videocache/HttpProxyCacheTest.java
@@ -1,5 +1,6 @@
package com.danikula.videocache;
+import com.danikula.videocache.file.FileCache;
import com.danikula.videocache.support.ProxyCacheTestUtils;
import com.danikula.videocache.support.Response;
import com.danikula.videocache.test.BuildConfig;
diff --git a/test/src/test/java/com/danikula/videocache/ProxyCacheTest.java b/test/src/test/java/com/danikula/videocache/ProxyCacheTest.java
index 5654f33..5fc64b3 100644
--- a/test/src/test/java/com/danikula/videocache/ProxyCacheTest.java
+++ b/test/src/test/java/com/danikula/videocache/ProxyCacheTest.java
@@ -1,6 +1,7 @@
package com.danikula.videocache;
import com.danikula.android.garden.io.IoUtils;
+import com.danikula.videocache.file.FileCache;
import com.danikula.videocache.support.AngryHttpUrlSource;
import com.danikula.videocache.support.PhlegmaticByteArraySource;
import com.danikula.videocache.test.BuildConfig;
diff --git a/test/src/test/java/com/danikula/videocache/file/DiskUsageTest.java b/test/src/test/java/com/danikula/videocache/file/DiskUsageTest.java
new file mode 100644
index 0000000..152afe3
--- /dev/null
+++ b/test/src/test/java/com/danikula/videocache/file/DiskUsageTest.java
@@ -0,0 +1,122 @@
+package com.danikula.videocache.file;
+
+import com.danikula.videocache.support.ProxyCacheTestUtils;
+import com.danikula.videocache.test.BuildConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.io.File;
+import java.io.IOException;
+
+import static com.danikula.android.garden.io.Files.cleanDirectory;
+import static com.danikula.android.garden.io.Files.createDirectory;
+import static org.fest.assertions.api.Assertions.assertThat;
+
+/**
+ * Tests for implementations of {@link DiskUsage}.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION)
+public class DiskUsageTest {
+
+ private File cacheFolder;
+
+ @Before
+ public void setup() throws Exception {
+ cacheFolder = ProxyCacheTestUtils.newCacheFile();
+ createDirectory(cacheFolder);
+ cleanDirectory(cacheFolder);
+ }
+
+ @Test
+ public void testMaxSizeCacheLimit() throws Exception {
+ DiskUsage diskUsage = new TotalSizeLruDiskUsage(300);
+ long now = System.currentTimeMillis();
+ createFile(file("b"), 101, now - 10000);
+ createFile(file("c"), 102, now - 8000);
+ createFile(file("a"), 104, now - 4000); // exceeds
+
+ diskUsage.touch(file("c"));
+ waitForAsyncTrimming();
+
+ assertThat(file("b")).doesNotExist();
+ assertThat(file("c")).exists();
+ assertThat(file("a")).exists();
+
+ createFile(file("d"), 500, now); // exceeds all
+ diskUsage.touch(file("d"));
+ waitForAsyncTrimming();
+
+ assertThat(file("a")).doesNotExist();
+ assertThat(file("c")).doesNotExist();
+ assertThat(file("d")).doesNotExist();
+ }
+
+ @Test
+ public void testMaxFilesCount() throws Exception {
+ DiskUsage diskUsage = new TotalCountLruDiskUsage(2);
+ long now = System.currentTimeMillis();
+ createFile(file("b"), 101, now - 10000);
+ createFile(file("c"), 102, now - 8000);
+ createFile(file("a"), 104, now - 4000);
+
+ diskUsage.touch(file("c"));
+ waitForAsyncTrimming();
+
+ assertThat(file("b")).doesNotExist();
+ assertThat(file("a")).exists();
+ assertThat(file("c")).exists();
+
+ createFile(file("d"), 500, now);
+ diskUsage.touch(file("d"));
+ waitForAsyncTrimming();
+
+ assertThat(file("a")).doesNotExist();
+ assertThat(file("c")).exists();
+ assertThat(file("d")).exists();
+ }
+
+ @Test
+ public void testTouch() throws Exception {
+ DiskUsage diskUsage = new TotalCountLruDiskUsage(2);
+ long now = System.currentTimeMillis();
+ createFile(file("b"), 101, now - 10000);
+ createFile(file("c"), 102, now - 8000);
+ createFile(file("a"), 104, now - 4000);
+
+ diskUsage.touch(file("b"));
+ waitForAsyncTrimming();
+
+ assertThat(file("b")).exists();
+ assertThat(file("a")).exists();
+ assertThat(file("c")).doesNotExist();
+
+ Thread.sleep(1000); // last modified is rounded to seconds, so wait for sec
+ new TotalCountLruDiskUsage(1).touch(file("a"));
+ waitForAsyncTrimming();
+
+ assertThat(file("a")).exists();
+ assertThat(file("b")).doesNotExist();
+ }
+
+ private void waitForAsyncTrimming() throws InterruptedException {
+ Thread.sleep(200);
+ }
+
+ private File file(String name) {
+ return new File(cacheFolder, name);
+ }
+
+ private void createFile(File file, int capacity, long lastModified) throws IOException {
+ byte[] data = ProxyCacheTestUtils.generate(capacity);
+ com.google.common.io.Files.write(data, file);
+ boolean modified = file.setLastModified(lastModified);
+ assertThat(modified).isTrue();
+ }
+}
diff --git a/test/src/test/java/com/danikula/videocache/FileCacheTest.java b/test/src/test/java/com/danikula/videocache/file/FileCacheTest.java
similarity index 97%
rename from test/src/test/java/com/danikula/videocache/FileCacheTest.java
rename to test/src/test/java/com/danikula/videocache/file/FileCacheTest.java
index 2054bda..36d6fae 100644
--- a/test/src/test/java/com/danikula/videocache/FileCacheTest.java
+++ b/test/src/test/java/com/danikula/videocache/file/FileCacheTest.java
@@ -1,7 +1,9 @@
-package com.danikula.videocache;
+package com.danikula.videocache.file;
import com.danikula.android.garden.io.Files;
import com.danikula.android.garden.io.IoUtils;
+import com.danikula.videocache.Cache;
+import com.danikula.videocache.ProxyCacheException;
import com.danikula.videocache.test.BuildConfig;
import org.junit.Assert;
diff --git a/test/src/test/java/com/danikula/videocache/file/FilesTest.java b/test/src/test/java/com/danikula/videocache/file/FilesTest.java
new file mode 100644
index 0000000..6686743
--- /dev/null
+++ b/test/src/test/java/com/danikula/videocache/file/FilesTest.java
@@ -0,0 +1,62 @@
+package com.danikula.videocache.file;
+
+import com.danikula.videocache.support.ProxyCacheTestUtils;
+import com.danikula.videocache.test.BuildConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.io.File;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link Files}.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION)
+public class FilesTest {
+
+ @Test
+ public void testModify() throws Exception {
+ byte[] data = ProxyCacheTestUtils.generate(999);
+ File file = ProxyCacheTestUtils.newCacheFile();
+ com.google.common.io.Files.write(data, file);
+ long lastModified = file.lastModified();
+
+ Thread.sleep(1100); // file can store modification date in seconds. so wait for ~ 1 sec
+ Files.modify(file);
+
+ assertThat(file).hasBinaryContent(data);
+ assertThat(file.lastModified()).isGreaterThan(lastModified);
+ }
+
+ @Test
+ public void testSetModifiedNow() throws Exception {
+ File file = ProxyCacheTestUtils.newCacheFile();
+ com.google.common.io.Files.write(ProxyCacheTestUtils.generate(22), file);
+
+ Thread.sleep(1100); // file can store modification date in seconds. so wait for ~ 1 sec
+ long nowRoundedToSecond = System.currentTimeMillis() / 1000 * 1000;
+ Files.setLastModifiedNow(file);
+
+ assertThat(file.lastModified()).isGreaterThanOrEqualTo(nowRoundedToSecond);
+ }
+
+ @Test
+ public void testModifyZeroSizeFile() throws Exception {
+ File file = ProxyCacheTestUtils.newCacheFile();
+ boolean created = file.createNewFile();
+ assertThat(created).isTrue();
+
+ Thread.sleep(1100); // file can store modification date in seconds. so wait for ~ 2 sec
+ long nowRoundedToSecond = System.currentTimeMillis() / 1000 * 1000;
+ Files.modify(file);
+
+ assertThat(file.lastModified()).isGreaterThanOrEqualTo(nowRoundedToSecond);
+ }
+}
diff --git a/test/src/test/java/com/danikula/videocache/support/AngryHttpUrlSource.java b/test/src/test/java/com/danikula/videocache/support/AngryHttpUrlSource.java
index 951d725..220837f 100644
--- a/test/src/test/java/com/danikula/videocache/support/AngryHttpUrlSource.java
+++ b/test/src/test/java/com/danikula/videocache/support/AngryHttpUrlSource.java
@@ -8,6 +8,7 @@ import com.danikula.videocache.Source;
*
* @author Alexey Danilov (danikula@gmail.com).
*/
+@Deprecated // use Mockito to throw error
public class AngryHttpUrlSource implements Source {
@Override
diff --git a/test/src/test/java/com/danikula/videocache/support/PhlegmaticByteArraySource.java b/test/src/test/java/com/danikula/videocache/support/PhlegmaticByteArraySource.java
index dee5ac1..387c7c7 100644
--- a/test/src/test/java/com/danikula/videocache/support/PhlegmaticByteArraySource.java
+++ b/test/src/test/java/com/danikula/videocache/support/PhlegmaticByteArraySource.java
@@ -8,6 +8,7 @@ import java.util.Random;
/**
* @author Alexey Danilov (danikula@gmail.com).
*/
+@Deprecated // TODO: use Mockito to mock delay
public class PhlegmaticByteArraySource extends ByteArraySource {
private final Random delayGenerator;