mirror of
https://github.com/zhigang1992/AndroidVideoCache.git
synced 2026-06-15 18:07:51 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b597ec1ad | ||
|
|
7e48955383 | ||
|
|
355e83b4ba | ||
|
|
c300b2e479 | ||
|
|
8726693015 | ||
|
|
02bc21f882 | ||
|
|
ba6ba4ebe9 | ||
|
|
f5dd92efff | ||
|
|
351cf2b986 | ||
|
|
2253b16797 | ||
|
|
6cd18555d3 | ||
|
|
01bba67289 | ||
|
|
e0ee4a7dde | ||
|
|
699aa05a2d | ||
|
|
4cf2a6bb68 | ||
|
|
3cc6f591c5 | ||
|
|
94692447db | ||
|
|
3023e4a1ee | ||
|
|
090f5ec7f9 |
@@ -1,8 +1,9 @@
|
||||
language: android
|
||||
jdk: oraclejdk8
|
||||
android:
|
||||
components:
|
||||
- platform-tools
|
||||
- tools
|
||||
- build-tools-22.0.1
|
||||
- build-tools-25.0.2
|
||||
- android-23
|
||||
- extra
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2014-2016 Alexey Danilov
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
37
README.md
37
README.md
@@ -8,6 +8,7 @@
|
||||
- [Recipes](#recipes)
|
||||
- [Disk cache limit](#disk-cache-limit)
|
||||
- [Listen caching progress](#listen-caching-progress)
|
||||
- [Providing names for cached files](#providing-names-for-cached-files)
|
||||
- [Sample](#sample)
|
||||
- [Known problems](#known-problems)
|
||||
- [Whats new](#whats-new)
|
||||
@@ -33,7 +34,7 @@ Note `AndroidVideoCache` works only with **direct urls** to media file, it [**d
|
||||
Just add dependency (`AndroidVideoCache` is available in jcenter):
|
||||
```
|
||||
dependencies {
|
||||
compile 'com.danikula:videocache:2.6.4'
|
||||
compile 'com.danikula:videocache:2.7.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -98,6 +99,16 @@ private HttpProxyCacheServer newProxy() {
|
||||
}
|
||||
```
|
||||
|
||||
or even implement your own `DiskUsage` strategy:
|
||||
```java
|
||||
private HttpProxyCacheServer newProxy() {
|
||||
return new HttpProxyCacheServer.Builder(this)
|
||||
.diskUsage(new MyCoolDiskUsageStrategy())
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Listen caching progress
|
||||
Use `HttpProxyCacheServer.registerCacheListener(CacheListener listener)` method to set listener with callback `onCacheAvailable(File cacheFile, String url, int percentsAvailable)` to be aware of caching progress. Do not forget to to unsubscribe listener with help of `HttpProxyCacheServer.unregisterCacheListener(CacheListener listener)` method to avoid memory leaks.
|
||||
|
||||
@@ -105,11 +116,31 @@ Use `HttpProxyCacheServer.isCached(String url)` method to check was url's conten
|
||||
|
||||
See `sample` app for more details.
|
||||
|
||||
### Providing names for cached files
|
||||
By default `AndroidVideoCache` uses MD5 of video url as file name. But in some cases url is not stable and it can contain some generated parts (e.g. session token). In this case caching mechanism will be broken. To fix it you have to provide own `FileNameGenerator`:
|
||||
``` java
|
||||
public class MyFileNameGenerator implements FileNameGenerator {
|
||||
|
||||
// Urls contain mutable parts (parameter 'sessionToken') and stable video's id (parameter 'videoId').
|
||||
// e. g. http://example.com?videoId=abcqaz&sessionToken=xyz987
|
||||
public String generate(String url) {
|
||||
Uri uri = Uri.parse(url);
|
||||
String videoId = uri.getQueryParameter("videoId");
|
||||
return videoId + ".mp4";
|
||||
}
|
||||
}
|
||||
|
||||
...
|
||||
HttpProxyCacheServer proxy = HttpProxyCacheServer.Builder(context)
|
||||
.fileNameGenerator(new MyFileNameGenerator())
|
||||
.build()
|
||||
```
|
||||
|
||||
### Sample
|
||||
See `sample` app.
|
||||
|
||||
## Known problems
|
||||
`AndroidVideoCache` [doesn't work](https://github.com/danikula/AndroidVideoCache/issues/28) if wifi or mobile internet connection uses proxy.
|
||||
- In some cases clients [can't connect](https://github.com/danikula/AndroidVideoCache/issues/134) to local proxy server ('Error pinging server' error). May be it is result of previous error. Note in this case video will be played, but without caching.
|
||||
|
||||
## Whats new
|
||||
See Release Notes [here](https://github.com/danikula/AndroidVideoCache/releases)
|
||||
@@ -133,7 +164,7 @@ If it's a feature that you think would need to be discussed please open an issue
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2014-2016 Alexey Danilov
|
||||
Copyright 2014-2017 Alexey Danilov
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.1.3'
|
||||
classpath 'com.android.tools.build:gradle:2.3.1'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Mon Sep 05 18:06:17 MSK 2016
|
||||
#Tue Apr 18 11:58:38 MSK 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
||||
|
||||
@@ -3,7 +3,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.novoda:bintray-release:0.3.4'
|
||||
classpath 'com.novoda:bintray-release:0.4.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ apply plugin: 'bintray-release'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '22.0.1'
|
||||
buildToolsVersion '25.0.2'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 23
|
||||
versionCode 20
|
||||
versionName '2.6.4'
|
||||
versionCode 21
|
||||
versionName '2.7.0'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ByteArrayCache implements Cache {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws ProxyCacheException {
|
||||
public long available() throws ProxyCacheException {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,12 @@ public class ByteArraySource implements Source {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() throws ProxyCacheException {
|
||||
public long length() throws ProxyCacheException {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(int offset) throws ProxyCacheException {
|
||||
public void open(long offset) throws ProxyCacheException {
|
||||
arrayInputStream = new ByteArrayInputStream(data);
|
||||
arrayInputStream.skip(offset);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ package com.danikula.videocache;
|
||||
*/
|
||||
public interface Cache {
|
||||
|
||||
int available() throws ProxyCacheException;
|
||||
long available() throws ProxyCacheException;
|
||||
|
||||
int read(byte[] buffer, long offset, int length) throws ProxyCacheException;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;
|
||||
|
||||
@@ -48,9 +49,9 @@ class HttpProxyCache extends ProxyCache {
|
||||
}
|
||||
|
||||
private boolean isUseCache(GetRequest request) throws ProxyCacheException {
|
||||
int sourceLength = source.length();
|
||||
long sourceLength = source.length();
|
||||
boolean sourceLengthKnown = sourceLength > 0;
|
||||
int cacheAvailable = cache.available();
|
||||
long cacheAvailable = cache.available();
|
||||
// do not use cache for partial requests which too far from available cache. It seems user seek video.
|
||||
return !sourceLengthKnown || !request.partial || request.rangeOffset <= cacheAvailable + sourceLength * NO_CACHE_BARRIER;
|
||||
}
|
||||
@@ -58,16 +59,16 @@ class HttpProxyCache extends ProxyCache {
|
||||
private String newResponseHeaders(GetRequest request) throws IOException, ProxyCacheException {
|
||||
String mime = source.getMime();
|
||||
boolean mimeKnown = !TextUtils.isEmpty(mime);
|
||||
int length = cache.isCompleted() ? cache.available() : source.length();
|
||||
long length = cache.isCompleted() ? cache.available() : source.length();
|
||||
boolean lengthKnown = length >= 0;
|
||||
long contentLength = request.partial ? length - request.rangeOffset : length;
|
||||
boolean addRange = lengthKnown && request.partial;
|
||||
return new StringBuilder()
|
||||
.append(request.partial ? "HTTP/1.1 206 PARTIAL CONTENT\n" : "HTTP/1.1 200 OK\n")
|
||||
.append("Accept-Ranges: bytes\n")
|
||||
.append(lengthKnown ? String.format("Content-Length: %d\n", contentLength) : "")
|
||||
.append(addRange ? String.format("Content-Range: bytes %d-%d/%d\n", request.rangeOffset, length - 1, length) : "")
|
||||
.append(mimeKnown ? String.format("Content-Type: %s\n", mime) : "")
|
||||
.append(lengthKnown ? format("Content-Length: %d\n", contentLength) : "")
|
||||
.append(addRange ? format("Content-Range: bytes %d-%d/%d\n", request.rangeOffset, length - 1, length) : "")
|
||||
.append(mimeKnown ? format("Content-Type: %s\n", mime) : "")
|
||||
.append("\n") // headers end
|
||||
.toString();
|
||||
}
|
||||
@@ -98,6 +99,10 @@ class HttpProxyCache extends ProxyCache {
|
||||
}
|
||||
}
|
||||
|
||||
private String format(String pattern, Object... args) {
|
||||
return String.format(Locale.US, pattern, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCachePercentsAvailableChanged(int percents) {
|
||||
if (listener != null) {
|
||||
|
||||
@@ -73,6 +73,7 @@ public class HttpProxyCacheServer {
|
||||
InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
|
||||
this.serverSocket = new ServerSocket(0, 8, inetAddress);
|
||||
this.port = serverSocket.getLocalPort();
|
||||
IgnoreHostProxySelector.install(PROXY_HOST, port);
|
||||
CountDownLatch startSignal = new CountDownLatch(1);
|
||||
this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal));
|
||||
this.waitConnectionThread.start();
|
||||
@@ -414,6 +415,17 @@ public class HttpProxyCacheServer {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom DiskUsage logic for handling when to keep or clean cache.
|
||||
*
|
||||
* @param diskUsage a disk usage strategy, cant be {@code null}.
|
||||
* @return a builder.
|
||||
*/
|
||||
public Builder diskUsage(DiskUsage diskUsage) {
|
||||
this.diskUsage = checkNotNull(diskUsage);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds new instance of {@link HttpProxyCacheServer}.
|
||||
*
|
||||
|
||||
@@ -55,7 +55,7 @@ public class HttpUrlSource implements Source {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int length() throws ProxyCacheException {
|
||||
public synchronized long length() throws ProxyCacheException {
|
||||
if (sourceInfo.length == Integer.MIN_VALUE) {
|
||||
fetchContentInfo();
|
||||
}
|
||||
@@ -63,12 +63,12 @@ public class HttpUrlSource implements Source {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(int offset) throws ProxyCacheException {
|
||||
public void open(long offset) throws ProxyCacheException {
|
||||
try {
|
||||
connection = openConnection(offset, -1);
|
||||
String mime = connection.getContentType();
|
||||
inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE);
|
||||
int length = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
|
||||
long length = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
|
||||
this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
|
||||
this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
|
||||
} catch (IOException e) {
|
||||
@@ -76,12 +76,17 @@ public class HttpUrlSource implements Source {
|
||||
}
|
||||
}
|
||||
|
||||
private int readSourceAvailableBytes(HttpURLConnection connection, int offset, int responseCode) throws IOException {
|
||||
int contentLength = connection.getContentLength();
|
||||
private long readSourceAvailableBytes(HttpURLConnection connection, long offset, int responseCode) throws IOException {
|
||||
long contentLength = getContentLength(connection);
|
||||
return responseCode == HTTP_OK ? contentLength
|
||||
: responseCode == HTTP_PARTIAL ? contentLength + offset : sourceInfo.length;
|
||||
}
|
||||
|
||||
private long getContentLength(HttpURLConnection connection) {
|
||||
String contentLengthValue = connection.getHeaderField("Content-Length");
|
||||
return contentLengthValue == null ? -1 : Long.parseLong(contentLengthValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ProxyCacheException {
|
||||
if (connection != null) {
|
||||
@@ -90,8 +95,13 @@ public class HttpUrlSource implements Source {
|
||||
} catch (NullPointerException | IllegalArgumentException e) {
|
||||
String message = "Wait... but why? WTF!? " +
|
||||
"Really shouldn't happen any more after fixing https://github.com/danikula/AndroidVideoCache/issues/43. " +
|
||||
"If you read it on your device log, please, notify me danikula@gmail.com or create issue here https://github.com/danikula/AndroidVideoCache/issues.";
|
||||
"If you read it on your device log, please, notify me danikula@gmail.com or create issue here " +
|
||||
"https://github.com/danikula/AndroidVideoCache/issues.";
|
||||
throw new RuntimeException(message, e);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
LOG.error("Error closing connection correctly. Should happen only on Android L. " +
|
||||
"If anybody know how to fix it, please visit https://github.com/danikula/AndroidVideoCache/issues/88. " +
|
||||
"Until good solution is not know, just ignore this issue :(", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +126,7 @@ public class HttpUrlSource implements Source {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
urlConnection = openConnection(0, 10000);
|
||||
int length = urlConnection.getContentLength();
|
||||
long length = getContentLength(urlConnection);
|
||||
String mime = urlConnection.getContentType();
|
||||
inputStream = urlConnection.getInputStream();
|
||||
this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
|
||||
@@ -132,7 +142,7 @@ public class HttpUrlSource implements Source {
|
||||
}
|
||||
}
|
||||
|
||||
private HttpURLConnection openConnection(int offset, int timeout) throws IOException, ProxyCacheException {
|
||||
private HttpURLConnection openConnection(long offset, int timeout) throws IOException, ProxyCacheException {
|
||||
HttpURLConnection connection;
|
||||
boolean redirected;
|
||||
int redirectCount = 0;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.danikula.videocache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.danikula.videocache.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* {@link ProxySelector} that ignore system default proxies for concrete host.
|
||||
* <p>
|
||||
* It is important to <a href="https://github.com/danikula/AndroidVideoCache/issues/28">ignore system proxy</a> for localhost connection.
|
||||
*
|
||||
* @author Alexey Danilov (danikula@gmail.com).
|
||||
*/
|
||||
class IgnoreHostProxySelector extends ProxySelector {
|
||||
|
||||
private static final List<Proxy> NO_PROXY_LIST = Arrays.asList(Proxy.NO_PROXY);
|
||||
|
||||
private final ProxySelector defaultProxySelector;
|
||||
private final String hostToIgnore;
|
||||
private final int portToIgnore;
|
||||
|
||||
IgnoreHostProxySelector(ProxySelector defaultProxySelector, String hostToIgnore, int portToIgnore) {
|
||||
this.defaultProxySelector = checkNotNull(defaultProxySelector);
|
||||
this.hostToIgnore = checkNotNull(hostToIgnore);
|
||||
this.portToIgnore = portToIgnore;
|
||||
}
|
||||
|
||||
static void install(String hostToIgnore, int portToIgnore) {
|
||||
ProxySelector defaultProxySelector = ProxySelector.getDefault();
|
||||
ProxySelector ignoreHostProxySelector = new IgnoreHostProxySelector(defaultProxySelector, hostToIgnore, portToIgnore);
|
||||
ProxySelector.setDefault(ignoreHostProxySelector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Proxy> select(URI uri) {
|
||||
boolean ignored = hostToIgnore.equals(uri.getHost()) && portToIgnore == uri.getPort();
|
||||
return ignored ? NO_PROXY_LIST : defaultProxySelector.select(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(URI uri, SocketAddress address, IOException failure) {
|
||||
defaultProxySelector.connectFailed(uri, address, failure);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,13 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -61,13 +66,23 @@ class Pinger {
|
||||
attempts++;
|
||||
timeout *= 2;
|
||||
}
|
||||
String error = String.format("Error pinging server (attempts: %d, max timeout: %d). " +
|
||||
"If you see this message, please, email me danikula@gmail.com " +
|
||||
"or create issue here https://github.com/danikula/AndroidVideoCache/issues", attempts, timeout / 2);
|
||||
String error = String.format(Locale.US, "Error pinging server (attempts: %d, max timeout: %d). " +
|
||||
"If you see this message, please, report at https://github.com/danikula/AndroidVideoCache/issues/134. " +
|
||||
"Default proxies are: %s"
|
||||
, attempts, timeout / 2, getDefaultProxies());
|
||||
LOG.error(error, new ProxyCacheException(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<Proxy> getDefaultProxies() {
|
||||
try {
|
||||
ProxySelector defaultProxySelector = ProxySelector.getDefault();
|
||||
return defaultProxySelector.select(new URI(getPingUrl()));
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isPingRequest(String request) {
|
||||
return PING_REQUEST.equals(request);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ class ProxyCache {
|
||||
|
||||
protected void onCacheAvailable(long cacheAvailable, long sourceLength) {
|
||||
boolean zeroLengthSource = sourceLength == 0;
|
||||
int percents = zeroLengthSource ? 100 : (int) (cacheAvailable * 100 / sourceLength);
|
||||
int percents = zeroLengthSource ? 100 : (int) ((float) cacheAvailable / sourceLength * 100);
|
||||
boolean percentsChanged = percents != percentsAvailable;
|
||||
boolean sourceLengthKnown = sourceLength >= 0;
|
||||
if (sourceLengthKnown && percentsChanged) {
|
||||
@@ -116,8 +116,8 @@ class ProxyCache {
|
||||
}
|
||||
|
||||
private void readSource() {
|
||||
int sourceAvailable = -1;
|
||||
int offset = 0;
|
||||
long sourceAvailable = -1;
|
||||
long offset = 0;
|
||||
try {
|
||||
offset = cache.available();
|
||||
source.open(offset);
|
||||
|
||||
@@ -7,15 +7,17 @@ package com.danikula.videocache;
|
||||
*/
|
||||
public class ProxyCacheException extends Exception {
|
||||
|
||||
private static final String LIBRARY_VERSION = ". Version: " + BuildConfig.VERSION_NAME;
|
||||
|
||||
public ProxyCacheException(String message) {
|
||||
super(message);
|
||||
super(message + LIBRARY_VERSION);
|
||||
}
|
||||
|
||||
public ProxyCacheException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
super(message + LIBRARY_VERSION, cause);
|
||||
}
|
||||
|
||||
public ProxyCacheException(Throwable cause) {
|
||||
super(cause);
|
||||
super("No explanation error" + LIBRARY_VERSION, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public interface Source {
|
||||
* @param offset offset in bytes for source.
|
||||
* @throws ProxyCacheException if error occur while opening source.
|
||||
*/
|
||||
void open(int offset) throws ProxyCacheException;
|
||||
void open(long offset) throws ProxyCacheException;
|
||||
|
||||
/**
|
||||
* Returns length bytes or <b>negative value</b> if length is unknown.
|
||||
@@ -21,12 +21,13 @@ public interface Source {
|
||||
* @return bytes length
|
||||
* @throws ProxyCacheException if error occur while fetching source data.
|
||||
*/
|
||||
int length() throws ProxyCacheException;
|
||||
long length() throws ProxyCacheException;
|
||||
|
||||
/**
|
||||
* Read data to byte buffer from source with current offset.
|
||||
*
|
||||
* @param buffer a buffer to be used for reading data.
|
||||
* @return a count of read bytes
|
||||
* @throws ProxyCacheException if error occur while reading source.
|
||||
*/
|
||||
int read(byte[] buffer) throws ProxyCacheException;
|
||||
|
||||
@@ -8,10 +8,10 @@ package com.danikula.videocache;
|
||||
public class SourceInfo {
|
||||
|
||||
public final String url;
|
||||
public final int length;
|
||||
public final long length;
|
||||
public final String mime;
|
||||
|
||||
public SourceInfo(String url, int length, String mime) {
|
||||
public SourceInfo(String url, long length, String mime) {
|
||||
this.url = url;
|
||||
this.length = length;
|
||||
this.mime = mime;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class FileCache implements Cache {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int available() throws ProxyCacheException {
|
||||
public synchronized long available() throws ProxyCacheException {
|
||||
try {
|
||||
return (int) dataFile.length();
|
||||
} catch (IOException e) {
|
||||
@@ -100,6 +100,7 @@ public class FileCache implements Cache {
|
||||
file = completedFile;
|
||||
try {
|
||||
dataFile = new RandomAccessFile(file, "r");
|
||||
diskUsage.touch(file);
|
||||
} catch (IOException e) {
|
||||
throw new ProxyCacheException("Error opening " + file + " as disc cache", e);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.danikula.videocache.file;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -16,6 +20,8 @@ import java.util.List;
|
||||
*/
|
||||
class Files {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger("Files");
|
||||
|
||||
static void makeDir(File directory) throws IOException {
|
||||
if (directory.exists()) {
|
||||
if (!directory.isDirectory()) {
|
||||
@@ -46,7 +52,8 @@ class Files {
|
||||
if (!modified) {
|
||||
modify(file);
|
||||
if (file.lastModified() < now) {
|
||||
throw new IOException("Error set last modified date to " + file);
|
||||
// NOTE: apparently this is a known issue (see: http://stackoverflow.com/questions/6633748/file-lastmodified-is-never-what-was-set-with-file-setlastmodified)
|
||||
LOG.warn("Last modified date {} is not set for file {}", new Date(file.lastModified()), file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.concurrent.Executors;
|
||||
*
|
||||
* @author Alexey Danilov (danikula@gmail.com).
|
||||
*/
|
||||
abstract class LruDiskUsage implements DiskUsage {
|
||||
public abstract class LruDiskUsage implements DiskUsage {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger("LruDiskUsage");
|
||||
private final ExecutorService workerThread = Executors.newSingleThreadExecutor();
|
||||
|
||||
@@ -83,7 +83,7 @@ class DatabaseSourceInfoStorage extends SQLiteOpenHelper implements SourceInfoSt
|
||||
private SourceInfo convert(Cursor cursor) {
|
||||
return new SourceInfo(
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_URL)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_LENGTH)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_LENGTH)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MIME))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ apply plugin: 'com.neenbedankt.android-apt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '22.0.1'
|
||||
buildToolsVersion '25.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.danikula.videocache.sample'
|
||||
@@ -38,7 +38,7 @@ dependencies {
|
||||
// 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.6.4'
|
||||
compile 'com.danikula:videocache:2.7.0'
|
||||
compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar'
|
||||
apt 'org.androidannotations:androidannotations:3.3.2'
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ public class App extends Application {
|
||||
}
|
||||
|
||||
private HttpProxyCacheServer newProxy() {
|
||||
return new HttpProxyCacheServer(this);
|
||||
return new HttpProxyCacheServer.Builder(this)
|
||||
.cacheDirectory(Utils.getVideoCacheDir(this))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.danikula.videocache.sample;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.Fragment;
|
||||
@@ -22,10 +21,7 @@ import java.io.File;
|
||||
@EFragment(R.layout.fragment_video)
|
||||
public class GalleryVideoFragment extends Fragment implements CacheListener {
|
||||
|
||||
private static final String LOG_TAG = "VideoFragment";
|
||||
|
||||
@FragmentArg String url;
|
||||
@FragmentArg String cachePath;
|
||||
|
||||
@InstanceState int position;
|
||||
@InstanceState boolean playerStarted;
|
||||
@@ -37,14 +33,9 @@ public class GalleryVideoFragment extends Fragment implements CacheListener {
|
||||
|
||||
private final VideoProgressUpdater updater = new VideoProgressUpdater();
|
||||
|
||||
public static Fragment build(Context context, Video video) {
|
||||
return build(video.url, video.getCacheFile(context).getAbsolutePath());
|
||||
}
|
||||
|
||||
public static Fragment build(String url, String cachePath) {
|
||||
public static Fragment build(String url) {
|
||||
return GalleryVideoFragment_.builder()
|
||||
.url(url)
|
||||
.cachePath(cachePath)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ public class MenuActivity extends FragmentActivity {
|
||||
@Click(R.id.cleanCacheButton)
|
||||
void onClearCacheButtonClick() {
|
||||
try {
|
||||
Utils.cleanDirectory(getExternalCacheDir());
|
||||
|
||||
Utils.cleanVideoCacheDir(this);
|
||||
} catch (IOException e) {
|
||||
Log.e(null, "Error cleaning cache", e);
|
||||
Toast.makeText(this, "Error cleaning cache", Toast.LENGTH_LONG).show();
|
||||
|
||||
@@ -23,7 +23,7 @@ public class MultipleVideosActivity extends FragmentActivity {
|
||||
private void addVideoFragment(Video video, int containerViewId) {
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(containerViewId, VideoFragment.build(this, video))
|
||||
.add(containerViewId, VideoFragment.build(video.url))
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class SharedCacheActivity extends FragmentActivity {
|
||||
private void addVideoFragment(Video video, int containerViewId) {
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(containerViewId, VideoFragment.build(this, video))
|
||||
.add(containerViewId, VideoFragment.build(video.url))
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class SingleVideoActivity extends FragmentActivity {
|
||||
if (state == null) {
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.containerView, VideoFragment.build(this, Video.ORANGE_1))
|
||||
.add(R.id.containerView, VideoFragment.build(Video.ORANGE_1.url))
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.danikula.videocache.sample;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -10,7 +12,16 @@ import java.io.IOException;
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
public static void cleanDirectory(File file) throws IOException {
|
||||
public static File getVideoCacheDir(Context context) {
|
||||
return new File(context.getExternalCacheDir(), "video-cache");
|
||||
}
|
||||
|
||||
public static void cleanVideoCacheDir(Context context) throws IOException {
|
||||
File videoCacheDir = getVideoCacheDir(context);
|
||||
cleanDirectory(videoCacheDir);
|
||||
}
|
||||
|
||||
private static void cleanDirectory(File file) throws IOException {
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package com.danikula.videocache.sample;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public enum Video {
|
||||
|
||||
ORANGE_1(Config.ROOT + "orange1.mp4"),
|
||||
@@ -18,10 +14,6 @@ public enum Video {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public File getCacheFile(Context context) {
|
||||
return new File(context.getExternalCacheDir(), name());
|
||||
}
|
||||
|
||||
private class Config {
|
||||
private static final String ROOT = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.danikula.videocache.sample;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.Fragment;
|
||||
@@ -26,7 +25,6 @@ public class VideoFragment extends Fragment implements CacheListener {
|
||||
private static final String LOG_TAG = "VideoFragment";
|
||||
|
||||
@FragmentArg String url;
|
||||
@FragmentArg String cachePath;
|
||||
|
||||
@ViewById ImageView cacheStatusImageView;
|
||||
@ViewById VideoView videoView;
|
||||
@@ -34,14 +32,9 @@ public class VideoFragment extends Fragment implements CacheListener {
|
||||
|
||||
private final VideoProgressUpdater updater = new VideoProgressUpdater();
|
||||
|
||||
public static Fragment build(Context context, Video video) {
|
||||
return build(video.url, video.getCacheFile(context).getAbsolutePath());
|
||||
}
|
||||
|
||||
public static Fragment build(String url, String cachePath) {
|
||||
public static Fragment build(String url) {
|
||||
return VideoFragment_.builder()
|
||||
.url(url)
|
||||
.cachePath(cachePath)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.danikula.videocache.sample;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
@@ -27,17 +26,14 @@ public class VideoGalleryActivity extends FragmentActivity {
|
||||
|
||||
private static final class ViewsPagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public ViewsPagerAdapter(FragmentActivity activity) {
|
||||
super(activity.getSupportFragmentManager());
|
||||
this.context = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
Video video = Video.values()[position];
|
||||
return GalleryVideoFragment.build(context, video);
|
||||
return GalleryVideoFragment.build(video.url);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,7 +6,7 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '22.0.1'
|
||||
buildToolsVersion '25.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.danikula.proxycache.test'
|
||||
@@ -21,11 +21,47 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// temporary workaround for Robolectric issue https://github.com/robolectric/robolectric/issues/2647
|
||||
android.applicationVariants.all { variant ->
|
||||
def productFlavor = variant.productFlavors[0] != null ? "${variant.productFlavors[0].name.capitalize()}" : ""
|
||||
def buildType = "${variant.buildType.name.capitalize()}"
|
||||
tasks["compile${productFlavor}${buildType}UnitTestSources"].dependsOn(tasks["merge${productFlavor}${buildType}Assets"])
|
||||
}
|
||||
|
||||
// display test progress http://stackoverflow.com/a/36130467/999458
|
||||
tasks.withType(Test) {
|
||||
testLogging {
|
||||
// set options for log level LIFECYCLE
|
||||
events "passed", "skipped", "failed", "standardOut"
|
||||
showExceptions true
|
||||
exceptionFormat "full"
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
|
||||
// set options for log level DEBUG and INFO
|
||||
debug {
|
||||
events "started", "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
exceptionFormat "full"
|
||||
}
|
||||
info.events = debug.events
|
||||
info.exceptionFormat = debug.exceptionFormat
|
||||
|
||||
afterSuite { desc, result ->
|
||||
if (!desc.parent) { // will match the outermost suite
|
||||
def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)"
|
||||
def startItem = '| ', endItem = ' |'
|
||||
def repeatLength = startItem.length() + output.length() + endItem.length()
|
||||
println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':library')
|
||||
testCompile 'org.slf4j:slf4j-simple:1.7.21'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.robolectric:robolectric:3.1'
|
||||
testCompile 'org.robolectric:robolectric:3.3.2'
|
||||
testCompile 'com.squareup:fest-android:1.0.0'
|
||||
testCompile 'com.google.guava:guava-jdk5:17.0'
|
||||
testCompile('com.danikula:android-garden:2.1.4') {
|
||||
|
||||
@@ -3,10 +3,10 @@ package com.danikula.videocache;
|
||||
import com.danikula.videocache.test.BuildConfig;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricGradleTestRunner;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricGradleTestRunner.class)
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class)
|
||||
public abstract class BaseTest {
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_
|
||||
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.getPort;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.installExternalSystemProxy;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.readProxyResponse;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.resetSystemProxy;
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
@@ -47,6 +49,7 @@ public class HttpProxyCacheServerTest extends BaseTest {
|
||||
cacheFolder = ProxyCacheTestUtils.newCacheFile();
|
||||
createDirectory(cacheFolder);
|
||||
cleanDirectory(cacheFolder);
|
||||
resetSystemProxy();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -296,6 +299,67 @@ public class HttpProxyCacheServerTest extends BaseTest {
|
||||
proxy.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrimFileCacheForTotalCountLru() throws Exception {
|
||||
FileNameGenerator fileNameGenerator = new Md5FileNameGenerator();
|
||||
HttpProxyCacheServer proxy = new HttpProxyCacheServer.Builder(RuntimeEnvironment.application)
|
||||
.cacheDirectory(cacheFolder)
|
||||
.fileNameGenerator(fileNameGenerator)
|
||||
.maxCacheFilesCount(2)
|
||||
.build();
|
||||
readProxyResponse(proxy, proxy.getProxyUrl(HTTP_DATA_URL), 0);
|
||||
assertThat(new File(cacheFolder, fileNameGenerator.generate(HTTP_DATA_URL))).exists();
|
||||
|
||||
readProxyResponse(proxy, proxy.getProxyUrl(HTTP_DATA_URL_ONE_REDIRECT), 0);
|
||||
assertThat(new File(cacheFolder, fileNameGenerator.generate(HTTP_DATA_URL_ONE_REDIRECT))).exists();
|
||||
|
||||
readProxyResponse(proxy, proxy.getProxyUrl(HTTP_DATA_URL_3_REDIRECTS), 0);
|
||||
assertThat(new File(cacheFolder, fileNameGenerator.generate(HTTP_DATA_URL_3_REDIRECTS))).exists();
|
||||
|
||||
waitForAsyncTrimming();
|
||||
assertThat(new File(cacheFolder, fileNameGenerator.generate(HTTP_DATA_URL))).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrimFileCacheForTotalSizeLru() throws Exception {
|
||||
FileNameGenerator fileNameGenerator = new Md5FileNameGenerator();
|
||||
HttpProxyCacheServer proxy = new HttpProxyCacheServer.Builder(RuntimeEnvironment.application)
|
||||
.cacheDirectory(cacheFolder)
|
||||
.fileNameGenerator(fileNameGenerator)
|
||||
.maxCacheSize(HTTP_DATA_SIZE * 3 - 1)
|
||||
.build();
|
||||
readProxyResponse(proxy, proxy.getProxyUrl(HTTP_DATA_URL), 0);
|
||||
assertThat(new File(cacheFolder, fileNameGenerator.generate(HTTP_DATA_URL))).exists();
|
||||
|
||||
readProxyResponse(proxy, proxy.getProxyUrl(HTTP_DATA_URL_ONE_REDIRECT), 0);
|
||||
assertThat(new File(cacheFolder, fileNameGenerator.generate(HTTP_DATA_URL_ONE_REDIRECT))).exists();
|
||||
|
||||
readProxyResponse(proxy, proxy.getProxyUrl(HTTP_DATA_URL_3_REDIRECTS), 0);
|
||||
assertThat(new File(cacheFolder, fileNameGenerator.generate(HTTP_DATA_URL_3_REDIRECTS))).exists();
|
||||
|
||||
waitForAsyncTrimming();
|
||||
assertThat(new File(cacheFolder, fileNameGenerator.generate(HTTP_DATA_URL))).doesNotExist();
|
||||
}
|
||||
|
||||
@Test // https://github.com/danikula/AndroidVideoCache/issues/28
|
||||
public void testWorkWithExternalProxy() throws Exception {
|
||||
installExternalSystemProxy();
|
||||
|
||||
Pair<File, Response> response = readProxyData(HTTP_DATA_URL, 0);
|
||||
assertThat(response.second.data).isEqualTo(loadAssetFile(ASSETS_DATA_NAME));
|
||||
}
|
||||
|
||||
@Test // https://github.com/danikula/AndroidVideoCache/issues/28
|
||||
public void testDoesNotWorkWithoutCustomProxySelector() throws Exception {
|
||||
HttpProxyCacheServer httpProxyCacheServer = new HttpProxyCacheServer(RuntimeEnvironment.application);
|
||||
// IgnoreHostProxySelector is set in HttpProxyCacheServer constructor. So let reset it by custom.
|
||||
installExternalSystemProxy();
|
||||
|
||||
String proxiedUrl = httpProxyCacheServer.getProxyUrl(HTTP_DATA_URL);
|
||||
// server can't proxy this url due to it is not alive (can't ping itself), so it returns original url
|
||||
assertThat(proxiedUrl).isEqualTo(HTTP_DATA_URL);
|
||||
}
|
||||
|
||||
private Pair<File, Response> readProxyData(String url, int offset) throws IOException {
|
||||
File file = file(cacheFolder, url);
|
||||
HttpProxyCacheServer proxy = newProxy(cacheFolder);
|
||||
@@ -321,4 +385,8 @@ public class HttpProxyCacheServerTest extends BaseTest {
|
||||
.cacheDirectory(cacheDir)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void waitForAsyncTrimming() throws InterruptedException {
|
||||
Thread.sleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class HttpUrlSourceTest extends BaseTest {
|
||||
public void testFetchInfoWithRedirect() throws Exception {
|
||||
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_ONE_REDIRECT);
|
||||
source.open(0);
|
||||
int available = source.length();
|
||||
long available = source.length();
|
||||
String mime = source.getMime();
|
||||
source.close();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.danikula.videocache;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@@ -7,6 +8,9 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.getPort;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.getPortWithoutPing;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.installExternalSystemProxy;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.resetSystemProxy;
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -18,6 +22,11 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
public class PingerTest extends BaseTest {
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
resetSystemProxy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPingSuccess() throws Exception {
|
||||
HttpProxyCacheServer server = new HttpProxyCacheServer(RuntimeEnvironment.application);
|
||||
@@ -52,5 +61,22 @@ public class PingerTest extends BaseTest {
|
||||
assertThat(out.toString()).isEqualTo("HTTP/1.1 200 OK\n\nping ok");
|
||||
}
|
||||
|
||||
@Test // https://github.com/danikula/AndroidVideoCache/issues/28
|
||||
public void testPingedWithExternalProxy() throws Exception {
|
||||
installExternalSystemProxy();
|
||||
|
||||
HttpProxyCacheServer server = new HttpProxyCacheServer(RuntimeEnvironment.application);
|
||||
Pinger pinger = new Pinger("127.0.0.1", getPortWithoutPing(server));
|
||||
assertThat(pinger.ping(1, 100)).isTrue();
|
||||
}
|
||||
|
||||
@Test // https://github.com/danikula/AndroidVideoCache/issues/28
|
||||
public void testIsNotPingedWithoutCustomProxySelector() throws Exception {
|
||||
HttpProxyCacheServer server = new HttpProxyCacheServer(RuntimeEnvironment.application);
|
||||
// IgnoreHostProxySelector is set in HttpProxyCacheServer constructor. So let reset it by custom.
|
||||
installExternalSystemProxy();
|
||||
|
||||
Pinger pinger = new Pinger("127.0.0.1", getPortWithoutPing(server));
|
||||
assertThat(pinger.ping(1, 100)).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.danikula.videocache;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.resetSystemProxy;
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests {@link IgnoreHostProxySelector}.
|
||||
*
|
||||
* @author Alexey Danilov (danikula@gmail.com).
|
||||
*/
|
||||
public class ProxySelectorTest extends BaseTest {
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
resetSystemProxy();
|
||||
}
|
||||
|
||||
@Test // https://github.com/danikula/AndroidVideoCache/issues/28
|
||||
public void testIgnoring() throws Exception {
|
||||
InetSocketAddress proxyAddress = new InetSocketAddress("proxy.com", 80);
|
||||
Proxy systemProxy = new Proxy(Proxy.Type.HTTP, proxyAddress);
|
||||
ProxySelector mockedProxySelector = Mockito.mock(ProxySelector.class);
|
||||
when(mockedProxySelector.select(Mockito.<URI>any())).thenReturn(Lists.newArrayList(systemProxy));
|
||||
ProxySelector.setDefault(mockedProxySelector);
|
||||
|
||||
IgnoreHostProxySelector.install("localhost", 42);
|
||||
|
||||
ProxySelector proxySelector = ProxySelector.getDefault();
|
||||
List<Proxy> githubProxies = proxySelector.select(new URI("http://github.com"));
|
||||
assertThat(githubProxies).hasSize(1);
|
||||
assertThat(githubProxies.get(0).address()).isEqualTo(proxyAddress);
|
||||
|
||||
List<Proxy> localhostProxies = proxySelector.select(new URI("http://localhost:42"));
|
||||
assertThat(localhostProxies).hasSize(1);
|
||||
assertThat(localhostProxies.get(0)).isEqualTo(Proxy.NO_PROXY);
|
||||
|
||||
List<Proxy> localhostPort69Proxies = proxySelector.select(new URI("http://localhost:69"));
|
||||
assertThat(localhostPort69Proxies).hasSize(1);
|
||||
assertThat(localhostPort69Proxies.get(0).address()).isEqualTo(proxyAddress);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.danikula.videocache.file;
|
||||
|
||||
import com.danikula.android.garden.io.Files;
|
||||
import com.danikula.android.garden.io.IoUtils;
|
||||
import com.danikula.videocache.BaseTest;
|
||||
import com.danikula.videocache.Cache;
|
||||
import com.danikula.videocache.ProxyCacheException;
|
||||
@@ -11,6 +10,7 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_NAME;
|
||||
@@ -19,6 +19,7 @@ import static com.danikula.videocache.support.ProxyCacheTestUtils.getFileContent
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.getTempFile;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.newCacheFile;
|
||||
import static com.google.common.io.Files.write;
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
@@ -85,7 +86,7 @@ public class FileCacheTest extends BaseTest {
|
||||
fileCache.read(readData, firstPortionLength, secondPortionLength);
|
||||
assertThat(readData).isEqualTo(wroteSecondPortion);
|
||||
|
||||
readData = new byte[fileCache.available()];
|
||||
readData = new byte[(int)fileCache.available()];
|
||||
fileCache.read(readData, 0, readData.length);
|
||||
byte[] fileContent = getFileContent(getTempFile(file));
|
||||
assertThat(readData).isEqualTo(fileContent);
|
||||
@@ -95,7 +96,8 @@ public class FileCacheTest extends BaseTest {
|
||||
public void testIsFileCacheCompleted() throws Exception {
|
||||
File file = newCacheFile();
|
||||
File partialFile = new File(file.getParentFile(), file.getName() + ".download");
|
||||
IoUtils.saveToFile(loadAssetFile(ASSETS_DATA_NAME), partialFile);
|
||||
write(loadAssetFile(ASSETS_DATA_NAME), partialFile);
|
||||
write(loadAssetFile(ASSETS_DATA_NAME), partialFile);
|
||||
Cache fileCache = new FileCache(partialFile);
|
||||
|
||||
assertThat(file.exists()).isFalse();
|
||||
@@ -114,7 +116,7 @@ public class FileCacheTest extends BaseTest {
|
||||
@Test(expected = ProxyCacheException.class)
|
||||
public void testErrorWritingCompletedCache() throws Exception {
|
||||
File file = newCacheFile();
|
||||
IoUtils.saveToFile(loadAssetFile(ASSETS_DATA_NAME), file);
|
||||
write(loadAssetFile(ASSETS_DATA_NAME), file);
|
||||
FileCache fileCache = new FileCache(file);
|
||||
fileCache.append(generate(100), 20);
|
||||
Assert.fail();
|
||||
@@ -124,7 +126,7 @@ public class FileCacheTest extends BaseTest {
|
||||
public void testErrorWritingAfterCompletion() throws Exception {
|
||||
File file = newCacheFile();
|
||||
File partialFile = new File(file.getParentFile(), file.getName() + ".download");
|
||||
IoUtils.saveToFile(loadAssetFile(ASSETS_DATA_NAME), partialFile);
|
||||
write(loadAssetFile(ASSETS_DATA_NAME), partialFile);
|
||||
FileCache fileCache = new FileCache(partialFile);
|
||||
fileCache.complete();
|
||||
fileCache.append(generate(100), 20);
|
||||
@@ -140,4 +142,46 @@ public class FileCacheTest extends BaseTest {
|
||||
fileCache.available();
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrimAfterCompletionForTotalCountLru() throws Exception {
|
||||
File cacheDir = newCacheFile();
|
||||
DiskUsage diskUsage = new TotalCountLruDiskUsage(2);
|
||||
byte[] data = loadAssetFile(ASSETS_DATA_NAME);
|
||||
saveAndCompleteCache(diskUsage, data,
|
||||
new File(cacheDir, "0.dat"),
|
||||
new File(cacheDir, "1.dat"),
|
||||
new File(cacheDir, "2.dat")
|
||||
);
|
||||
waitForAsyncTrimming();
|
||||
assertThat(new File(cacheDir, "0.dat")).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrimAfterCompletionForTotalSizeLru() throws Exception {
|
||||
File cacheDir = newCacheFile();
|
||||
byte[] data = loadAssetFile(ASSETS_DATA_NAME);
|
||||
DiskUsage diskUsage = new TotalSizeLruDiskUsage(data.length * 3 - 1);
|
||||
saveAndCompleteCache(diskUsage, data,
|
||||
new File(cacheDir, "0.dat"),
|
||||
new File(cacheDir, "1.dat"),
|
||||
new File(cacheDir, "2.dat")
|
||||
);
|
||||
waitForAsyncTrimming();
|
||||
File deletedFile = new File(cacheDir, "0.dat");
|
||||
assertThat(deletedFile).doesNotExist();
|
||||
}
|
||||
|
||||
private void saveAndCompleteCache(DiskUsage diskUsage, byte[] data, File... files) throws ProxyCacheException, IOException {
|
||||
for (File file : files) {
|
||||
FileCache fileCache = new FileCache(file, diskUsage);
|
||||
fileCache.append(data, data.length);
|
||||
fileCache.complete();
|
||||
assertThat(file).exists();
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForAsyncTrimming() throws InterruptedException {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ import com.danikula.videocache.HttpUrlSource;
|
||||
import com.danikula.videocache.ProxyCacheException;
|
||||
import com.danikula.videocache.Source;
|
||||
import com.danikula.videocache.sourcestorage.SourceInfoStorage;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import org.apache.tools.ant.util.ReflectUtil;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
@@ -18,6 +21,10 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
@@ -31,6 +38,7 @@ import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Alexey Danilov (danikula@gmail.com).
|
||||
@@ -147,4 +155,22 @@ public class ProxyCacheTestUtils {
|
||||
String portAsString = matcher.group(1);
|
||||
return Integer.parseInt(portAsString);
|
||||
}
|
||||
|
||||
public static int getPortWithoutPing(HttpProxyCacheServer server) {
|
||||
return (Integer) ReflectUtil.getField(server, "port");
|
||||
}
|
||||
|
||||
public static void installExternalSystemProxy() {
|
||||
// see proxies list at http://proxylist.hidemyass.com/
|
||||
Proxy systemProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("162.8.230.7", 11180));
|
||||
ProxySelector mockedProxySelector = Mockito.mock(ProxySelector.class);
|
||||
when(mockedProxySelector.select(Mockito.<URI>any())).thenReturn(Lists.newArrayList(systemProxy));
|
||||
ProxySelector.setDefault(mockedProxySelector);
|
||||
}
|
||||
|
||||
public static void resetSystemProxy() {
|
||||
ProxySelector mockedProxySelector = Mockito.mock(ProxySelector.class);
|
||||
when(mockedProxySelector.select(Mockito.<URI>any())).thenReturn(Lists.newArrayList(Proxy.NO_PROXY));
|
||||
ProxySelector.setDefault(mockedProxySelector);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user