add disk usage limits (total cache size, total files, unlimited) #5 · majorxia/AndroidVideoCache@d32e88f · GitHub
Skip to content

Commit d32e88f

Browse files
committed
add disk usage limits (total cache size, total files, unlimited) danikula#5
1 parent 55988e2 commit d32e88f

28 files changed

Lines changed: 793 additions & 72 deletions

README.md

Lines changed: 1 addition & 1 deletion

library/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ publish {
2626
userOrg = 'alexeydanilov'
2727
groupId = 'com.danikula'
2828
artifactId = 'videocache'
29-
publishVersion = '2.2.0'
29+
publishVersion = '2.3.0'
3030
description = 'Cache support for android VideoView'
3131
website = 'https://github.com/danikula/AndroidVideoCache'
3232
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.danikula.videocache;
2+
3+
import com.danikula.videocache.file.DiskUsage;
4+
import com.danikula.videocache.file.FileNameGenerator;
5+
6+
import java.io.File;
7+
8+
/**
9+
* Configuration for proxy cache.
10+
*
11+
* @author Alexey Danilov (danikula@gmail.com).
12+
*/
13+
class Config {
14+
15+
public final File cacheRoot;
16+
public final FileNameGenerator fileNameGenerator;
17+
public final DiskUsage diskUsage;
18+
19+
Config(File cacheRoot, FileNameGenerator fileNameGenerator, DiskUsage diskUsage) {
20+
this.cacheRoot = cacheRoot;
21+
this.fileNameGenerator = fileNameGenerator;
22+
this.diskUsage = diskUsage;
23+
}
24+
25+
File generateCacheFile(String url) {
26+
String name = fileNameGenerator.generate(url);
27+
return new File(cacheRoot, name);
28+
}
29+
30+
}

library/src/main/java/com/danikula/videocache/HttpProxyCache.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import android.text.TextUtils;
44

5+
import com.danikula.videocache.file.FileCache;
6+
57
import java.io.BufferedOutputStream;
68
import java.io.IOException;
79
import java.io.OutputStream;

library/src/main/java/com/danikula/videocache/HttpProxyCacheServer.java

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
package com.danikula.videocache;
22

3+
import android.content.Context;
34
import android.os.SystemClock;
45
import android.util.Log;
56

7+
import com.danikula.videocache.file.DiskUsage;
8+
import com.danikula.videocache.file.FileNameGenerator;
9+
import com.danikula.videocache.file.Md5FileNameGenerator;
10+
import com.danikula.videocache.file.TotalCountLruDiskUsage;
11+
import com.danikula.videocache.file.TotalSizeLruDiskUsage;
12+
13+
import java.io.File;
614
import java.io.IOException;
715
import java.io.OutputStream;
816
import java.net.InetAddress;
@@ -56,11 +64,15 @@ public class HttpProxyCacheServer {
5664
private final ServerSocket serverSocket;
5765
private final int port;
5866
private final Thread waitConnectionThread;
59-
private final FileNameGenerator fileNameGenerator;
67+
private final Config config;
6068
private boolean pinged;
6169

62-
public HttpProxyCacheServer(FileNameGenerator fileNameGenerator) {
63-
this.fileNameGenerator = checkNotNull(fileNameGenerator);
70+
public HttpProxyCacheServer(Context context) {
71+
this(new Builder(context).buildConfig());
72+
}
73+
74+
private HttpProxyCacheServer(Config config) {
75+
this.config = checkNotNull(config);
6476
try {
6577
InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
6678
this.serverSocket = new ServerSocket(0, 8, inetAddress);
@@ -231,7 +243,7 @@ private HttpProxyCacheServerClients getClients(String url) throws ProxyCacheExce
231243
synchronized (clientsLock) {
232244
HttpProxyCacheServerClients clients = clientsMap.get(url);
233245
if (clients == null) {
234-
clients = new HttpProxyCacheServerClients(url, fileNameGenerator);
246+
clients = new HttpProxyCacheServerClients(url, config);
235247
clientsMap.put(url, clients);
236248
}
237249
return clients;
@@ -328,4 +340,94 @@ public Boolean call() throws Exception {
328340
return pingServer();
329341
}
330342
}
343+
344+
/**
345+
* Builder for {@link HttpProxyCacheServer}.
346+
*/
347+
public static final class Builder {
348+
349+
private static final long DEFAULT_MAX_SIZE = 512 * 104 * 1024;
350+
351+
private File cacheRoot;
352+
private FileNameGenerator fileNameGenerator;
353+
private DiskUsage diskUsage;
354+
355+
public Builder(Context context) {
356+
this.cacheRoot = StorageUtils.getIndividualCacheDirectory(context);
357+
this.diskUsage = new TotalSizeLruDiskUsage(DEFAULT_MAX_SIZE);
358+
this.fileNameGenerator = new Md5FileNameGenerator();
359+
}
360+
361+
/**
362+
* Overrides default cache folder to be used for caching files.
363+
* <p/>
364+
* By default AndroidVideoCache uses
365+
* '/Android/data/[app_package_name]/cache/video-cache/' if card is mounted and app has appropriate permission
366+
* or 'video-cache' subdirectory in default application's cache directory otherwise.
367+
* <p/>
368+
* <b>Note</b> directory must be used <b>only</b> for AndroidVideoCache files.
369+
*
370+
* @param file a cache directory, can't be null.
371+
* @return a builder.
372+
*/
373+
public Builder cacheDirectory(File file) {
374+
this.cacheRoot = checkNotNull(file);
375+
return this;
376+
}
377+
378+
/**
379+
* Overrides default cache file name generator {@link Md5FileNameGenerator} .
380+
*
381+
* @param fileNameGenerator a new file name generator.
382+
* @return a builder.
383+
*/
384+
public Builder fileNameGenerator(FileNameGenerator fileNameGenerator) {
385+
this.fileNameGenerator = checkNotNull(fileNameGenerator);
386+
return this;
387+
}
388+
389+
/**
390+
* Sets max cache size in bytes.
391+
* All files that exceeds limit will be deleted using LRU strategy.
392+
* Default value is 512 Mb.
393+
* <p/>
394+
* Note this method overrides result of calling {@link #maxCacheFilesCount(int)}
395+
*
396+
* @param maxSize max cache size in bytes.
397+
* @return a builder.
398+
*/
399+
public Builder maxCacheSize(long maxSize) {
400+
this.diskUsage = new TotalSizeLruDiskUsage(maxSize);
401+
return this;
402+
}
403+
404+
/**
405+
* Sets max cache files count.
406+
* All files that exceeds limit will be deleted using LRU strategy.
407+
* <p/>
408+
* Note this method overrides result of calling {@link #maxCacheSize(long)}
409+
*
410+
* @param count max cache files count.
411+
* @return a builder.
412+
*/
413+
public Builder maxCacheFilesCount(int count) {
414+
this.diskUsage = new TotalCountLruDiskUsage(count);
415+
return this;
416+
}
417+
418+
/**
419+
* Builds new instance of {@link HttpProxyCacheServer}.
420+
*
421+
* @return proxy cache. Only single instance should be used across whole app.
422+
*/
423+
public HttpProxyCacheServer build() {
424+
Config config = buildConfig();
425+
return new HttpProxyCacheServer(config);
426+
}
427+
428+
private Config buildConfig() {
429+
return new Config(cacheRoot, fileNameGenerator, diskUsage);
430+
}
431+
432+
}
331433
}

library/src/main/java/com/danikula/videocache/HttpProxyCacheServerClients.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import android.os.Looper;
55
import android.os.Message;
66

7+
import com.danikula.videocache.file.FileCache;
8+
79
import java.io.File;
810
import java.io.IOException;
911
import java.net.Socket;
@@ -24,12 +26,12 @@ final class HttpProxyCacheServerClients {
2426
private final String url;
2527
private volatile HttpProxyCache proxyCache;
2628
private final List<CacheListener> listeners = new CopyOnWriteArrayList<>();
27-
private final FileNameGenerator fileNameGenerator;
2829
private final CacheListener uiCacheListener;
30+
private final Config config;
2931

30-
public HttpProxyCacheServerClients(String url, FileNameGenerator fileNameGenerator) {
32+
public HttpProxyCacheServerClients(String url, Config config) {
3133
this.url = checkNotNull(url);
32-
this.fileNameGenerator = checkNotNull(fileNameGenerator);
34+
this.config = checkNotNull(config);
3335
this.uiCacheListener = new UiListenerHandler(url, listeners);
3436
}
3537

@@ -78,7 +80,7 @@ public int getClientsCount() {
7880

7981
private HttpProxyCache newHttpProxyCache() throws ProxyCacheException {
8082
HttpUrlSource source = new HttpUrlSource(url);
81-
FileCache cache = new FileCache(fileNameGenerator.generate(url));
83+
FileCache cache = new FileCache(config.generateCacheFile(url), config.diskUsage);
8284
HttpProxyCache httpProxyCache = new HttpProxyCache(source, cache);
8385
httpProxyCache.registerCacheListener(uiCacheListener);
8486
return httpProxyCache;

library/src/main/java/com/danikula/videocache/ProxyCacheUtils.java

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import android.webkit.MimeTypeMap;
66

77
import java.io.Closeable;
8-
import java.io.File;
98
import java.io.IOException;
109
import java.io.UnsupportedEncodingException;
1110
import java.net.URLDecoder;
@@ -22,7 +21,7 @@
2221
*
2322
* @author Alexey Danilov (danikula@gmail.com).
2423
*/
25-
class ProxyCacheUtils {
24+
public class ProxyCacheUtils {
2625

2726
static final String LOG_TAG = "ProxyCache";
2827
static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
@@ -50,19 +49,6 @@ static String preview(byte[] data, int length) {
5049
return preview;
5150
}
5251

53-
static void createDirectory(File directory) throws IOException {
54-
checkNotNull(directory, "File must be not null!");
55-
if (directory.exists()) {
56-
checkArgument(directory.isDirectory(), "File is not directory!");
57-
} else {
58-
boolean isCreated = directory.mkdirs();
59-
if (!isCreated) {
60-
String error = String.format("Directory %s can't be created", directory.getAbsolutePath());
61-
throw new IOException(error);
62-
}
63-
}
64-
}
65-
6652
static String encode(String url) {
6753
try {
6854
return URLEncoder.encode(url, "utf-8");
@@ -89,7 +75,7 @@ static void close(Closeable closeable) {
8975
}
9076
}
9177

92-
static String computeMD5(String string) {
78+
public static String computeMD5(String string) {
9379
try {
9480
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
9581
byte[] digestBytes = messageDigest.digest(string.getBytes());
@@ -99,12 +85,11 @@ static String computeMD5(String string) {
9985
}
10086
}
10187

102-
static String bytesToHexString(byte[] bytes) {
88+
private static String bytesToHexString(byte[] bytes) {
10389
StringBuffer sb = new StringBuffer();
10490
for (byte b : bytes) {
10591
sb.append(String.format("%02x", b));
10692
}
10793
return sb.toString();
10894
}
109-
11095
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.danikula.videocache;
2+
3+
import android.content.Context;
4+
import android.os.Environment;
5+
import android.util.Log;
6+
7+
import java.io.File;
8+
9+
import static android.os.Environment.MEDIA_MOUNTED;
10+
import static com.danikula.videocache.ProxyCacheUtils.LOG_TAG;
11+
12+
/**
13+
* Provides application storage paths
14+
* <p/>
15+
* See https://github.com/nostra13/Android-Universal-Image-Loader
16+
*
17+
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
18+
* @since 1.0.0
19+
*/
20+
final class StorageUtils {
21+
22+
private static final String INDIVIDUAL_DIR_NAME = "video-cache";
23+
24+
/**
25+
* Returns individual application cache directory (for only video caching from Proxy). Cache directory will be
26+
* created on SD card <i>("/Android/data/[app_package_name]/cache/video-cache")</i> if card is mounted .
27+
* Else - Android defines cache directory on device's file system.
28+
*
29+
* @param context Application context
30+
* @return Cache {@link File directory}
31+
*/
32+
public static File getIndividualCacheDirectory(Context context) {
33+
File cacheDir = getCacheDirectory(context, true);
34+
return new File(cacheDir, INDIVIDUAL_DIR_NAME);
35+
}
36+
37+
/**
38+
* Returns application cache directory. Cache directory will be created on SD card
39+
* <i>("/Android/data/[app_package_name]/cache")</i> (if card is mounted and app has appropriate permission) or
40+
* on device's file system depending incoming parameters.
41+
*
42+
* @param context Application context
43+
* @param preferExternal Whether prefer external location for cache
44+
* @return Cache {@link File directory}.<br />
45+
* <b>NOTE:</b> Can be null in some unpredictable cases (if SD card is unmounted and
46+
* {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null).
47+
*/
48+
private static File getCacheDirectory(Context context, boolean preferExternal) {
49+
File appCacheDir = null;
50+
String externalStorageState;
51+
try {
52+
externalStorageState = Environment.getExternalStorageState();
53+
} catch (NullPointerException e) { // (sh)it happens
54+
externalStorageState = "";
55+
}
56+
if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState)) {
57+
appCacheDir = getExternalCacheDir(context);
58+
}
59+
if (appCacheDir == null) {
60+
appCacheDir = context.getCacheDir();
61+
}
62+
if (appCacheDir == null) {
63+
String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
64+
Log.w(LOG_TAG, "Can't define system cache directory! '" + cacheDirPath + "%s' will be used.");
65+
appCacheDir = new File(cacheDirPath);
66+
}
67+
return appCacheDir;
68+
}
69+
70+
private static File getExternalCacheDir(Context context) {
71+
File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
72+
File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
73+
if (!appCacheDir.exists()) {
74+
if (!appCacheDir.mkdirs()) {
75+
Log.w(LOG_TAG, "Unable to create external cache directory");
76+
return null;
77+
}
78+
}
79+
return appCacheDir;
80+
}
81+
}
Lines changed: 15 additions & 0 deletions

0 commit comments

Comments
 (0)