5 Commits

Author SHA1 Message Date
Alexey Danilov
99b8330679 📦 release 2.6.0 2016-08-26 13:14:47 +03:00
Alexey Danilov
0f522f200d notify CacheListener after cache is completed with original file 2016-08-26 13:10:19 +03:00
Alexey Danilov
71c6301eb4 🤘 cache source's info in db to increase performance (#41, #45) 2016-08-05 14:36:25 +03:00
Alexey Danilov
372542c2dc update robolectric 3.0.rc-2 -> 3.1 2016-08-04 22:07:27 +03:00
Alexey Danilov
22cfddb2c9 add table of content, known problems to readme 2016-08-04 18:33:58 +03:00
29 changed files with 532 additions and 134 deletions

View File

@@ -1,6 +1,21 @@
# Video cache support for Android ## Video cache support for Android
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-AndroidVideoCache-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/1751) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-AndroidVideoCache-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/1751)
## Table of Content
- [Why AndroidVideoCache?](#why-androidvideocache)
- [Features](#features)
- [Get started](#get-started)
- [Recipes](#recipes)
- [Disk cache limit](#disk-cache-limit)
- [Listen caching progress](#listen-caching-progress)
- [Sample](#sample)
- [Known problems](#known-problems)
- [Whats new](#whats-new)
- [Code contributions](#code-contributions)
- [Where published?](#where-published)
- [Questions?](#questions)
- [License](#license)
## Why AndroidVideoCache? ## Why AndroidVideoCache?
Because there is no sense to download video a lot of times while streaming! Because there is no sense to download video a lot of times while streaming!
`AndroidVideoCache` allows to add caching support to your `VideoView/MediaPlayer`, [ExoPlayer](https://github.com/danikula/ExoPlayer/commit/6110be8559f003f98020ada8c5e09691b67aaff4) or any another player with help of single line! `AndroidVideoCache` allows to add caching support to your `VideoView/MediaPlayer`, [ExoPlayer](https://github.com/danikula/ExoPlayer/commit/6110be8559f003f98020ada8c5e09691b67aaff4) or any another player with help of single line!
@@ -21,7 +36,7 @@ repositories {
jcenter() jcenter()
} }
dependencies { dependencies {
compile 'com.danikula:videocache:2.5.0' compile 'com.danikula:videocache:2.6.0'
} }
``` ```
@@ -96,6 +111,9 @@ See `sample` app for more details.
### Sample ### Sample
See `sample` app. See `sample` app.
## Known problems
`AndroidVideoCache` [doesn't work](https://github.com/danikula/AndroidVideoCache/issues/28) if wifi or mobile internet connection uses proxy.
## Whats new ## Whats new
See Release Notes [here](https://github.com/danikula/AndroidVideoCache/releases) See Release Notes [here](https://github.com/danikula/AndroidVideoCache/releases)

View File

@@ -29,7 +29,7 @@ publish {
userOrg = 'alexeydanilov' userOrg = 'alexeydanilov'
groupId = 'com.danikula' groupId = 'com.danikula'
artifactId = 'videocache' artifactId = 'videocache'
publishVersion = '2.5.0' publishVersion = '2.6.0'
description = 'Cache support for android VideoView' description = 'Cache support for android VideoView'
website = 'https://github.com/danikula/AndroidVideoCache' website = 'https://github.com/danikula/AndroidVideoCache'
} }

View File

@@ -2,6 +2,7 @@ package com.danikula.videocache;
import com.danikula.videocache.file.DiskUsage; import com.danikula.videocache.file.DiskUsage;
import com.danikula.videocache.file.FileNameGenerator; import com.danikula.videocache.file.FileNameGenerator;
import com.danikula.videocache.sourcestorage.SourceInfoStorage;
import java.io.File; import java.io.File;
@@ -15,11 +16,13 @@ class Config {
public final File cacheRoot; public final File cacheRoot;
public final FileNameGenerator fileNameGenerator; public final FileNameGenerator fileNameGenerator;
public final DiskUsage diskUsage; public final DiskUsage diskUsage;
public final SourceInfoStorage sourceInfoStorage;
Config(File cacheRoot, FileNameGenerator fileNameGenerator, DiskUsage diskUsage) { Config(File cacheRoot, FileNameGenerator fileNameGenerator, DiskUsage diskUsage, SourceInfoStorage sourceInfoStorage) {
this.cacheRoot = cacheRoot; this.cacheRoot = cacheRoot;
this.fileNameGenerator = fileNameGenerator; this.fileNameGenerator = fileNameGenerator;
this.diskUsage = diskUsage; this.diskUsage = diskUsage;
this.sourceInfoStorage = sourceInfoStorage;
} }
File generateCacheFile(String url) { File generateCacheFile(String url) {

View File

@@ -101,7 +101,7 @@ class HttpProxyCache extends ProxyCache {
@Override @Override
protected void onCachePercentsAvailableChanged(int percents) { protected void onCachePercentsAvailableChanged(int percents) {
if (listener != null) { if (listener != null) {
listener.onCacheAvailable(cache.file, source.url, percents); listener.onCacheAvailable(cache.file, source.getUrl(), percents);
} }
} }
} }

View File

@@ -9,6 +9,8 @@ import com.danikula.videocache.file.FileNameGenerator;
import com.danikula.videocache.file.Md5FileNameGenerator; import com.danikula.videocache.file.Md5FileNameGenerator;
import com.danikula.videocache.file.TotalCountLruDiskUsage; import com.danikula.videocache.file.TotalCountLruDiskUsage;
import com.danikula.videocache.file.TotalSizeLruDiskUsage; import com.danikula.videocache.file.TotalSizeLruDiskUsage;
import com.danikula.videocache.sourcestorage.SourceInfoStorage;
import com.danikula.videocache.sourcestorage.SourceInfoStorageFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -192,6 +194,8 @@ public class HttpProxyCacheServer {
shutdownClients(); shutdownClients();
config.sourceInfoStorage.release();
waitConnectionThread.interrupt(); waitConnectionThread.interrupt();
try { try {
if (!serverSocket.isClosed()) { if (!serverSocket.isClosed()) {
@@ -364,8 +368,10 @@ public class HttpProxyCacheServer {
private File cacheRoot; private File cacheRoot;
private FileNameGenerator fileNameGenerator; private FileNameGenerator fileNameGenerator;
private DiskUsage diskUsage; private DiskUsage diskUsage;
private SourceInfoStorage sourceInfoStorage;
public Builder(Context context) { public Builder(Context context) {
this.sourceInfoStorage = SourceInfoStorageFactory.newSourceInfoStorage(context);
this.cacheRoot = StorageUtils.getIndividualCacheDirectory(context); this.cacheRoot = StorageUtils.getIndividualCacheDirectory(context);
this.diskUsage = new TotalSizeLruDiskUsage(DEFAULT_MAX_SIZE); this.diskUsage = new TotalSizeLruDiskUsage(DEFAULT_MAX_SIZE);
this.fileNameGenerator = new Md5FileNameGenerator(); this.fileNameGenerator = new Md5FileNameGenerator();
@@ -439,7 +445,7 @@ public class HttpProxyCacheServer {
} }
private Config buildConfig() { private Config buildConfig() {
return new Config(cacheRoot, fileNameGenerator, diskUsage); return new Config(cacheRoot, fileNameGenerator, diskUsage, sourceInfoStorage);
} }
} }

View File

@@ -79,7 +79,7 @@ final class HttpProxyCacheServerClients {
} }
private HttpProxyCache newHttpProxyCache() throws ProxyCacheException { private HttpProxyCache newHttpProxyCache() throws ProxyCacheException {
HttpUrlSource source = new HttpUrlSource(url); HttpUrlSource source = new HttpUrlSource(url, config.sourceInfoStorage);
FileCache cache = new FileCache(config.generateCacheFile(url), config.diskUsage); FileCache cache = new FileCache(config.generateCacheFile(url), config.diskUsage);
HttpProxyCache httpProxyCache = new HttpProxyCache(source, cache); HttpProxyCache httpProxyCache = new HttpProxyCache(source, cache);
httpProxyCache.registerCacheListener(uiCacheListener); httpProxyCache.registerCacheListener(uiCacheListener);

View File

@@ -3,6 +3,9 @@ package com.danikula.videocache;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.danikula.videocache.sourcestorage.SourceInfoStorage;
import com.danikula.videocache.sourcestorage.SourceInfoStorageFactory;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -10,6 +13,7 @@ import java.io.InterruptedIOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import static com.danikula.videocache.Preconditions.checkNotNull;
import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE; import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;
import static com.danikula.videocache.ProxyCacheUtils.LOG_TAG; import static com.danikula.videocache.ProxyCacheUtils.LOG_TAG;
import static java.net.HttpURLConnection.HTTP_MOVED_PERM; import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
@@ -26,51 +30,53 @@ import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
public class HttpUrlSource implements Source { public class HttpUrlSource implements Source {
private static final int MAX_REDIRECTS = 5; private static final int MAX_REDIRECTS = 5;
public final String url; private final SourceInfoStorage sourceInfoStorage;
private SourceInfo sourceInfo;
private HttpURLConnection connection; private HttpURLConnection connection;
private InputStream inputStream; private InputStream inputStream;
private volatile int length = Integer.MIN_VALUE;
private volatile String mime;
public HttpUrlSource(String url) { public HttpUrlSource(String url) {
this(url, ProxyCacheUtils.getSupposablyMime(url)); this(url, SourceInfoStorageFactory.newEmptySourceInfoStorage());
} }
public HttpUrlSource(String url, String mime) { public HttpUrlSource(String url, SourceInfoStorage sourceInfoStorage) {
this.url = Preconditions.checkNotNull(url); this.sourceInfoStorage = checkNotNull(sourceInfoStorage);
this.mime = mime; SourceInfo sourceInfo = sourceInfoStorage.get(url);
this.sourceInfo = sourceInfo != null ? sourceInfo :
new SourceInfo(url, Integer.MIN_VALUE, ProxyCacheUtils.getSupposablyMime(url));
} }
public HttpUrlSource(HttpUrlSource source) { public HttpUrlSource(HttpUrlSource source) {
this.url = source.url; this.sourceInfo = source.sourceInfo;
this.mime = source.mime; this.sourceInfoStorage = source.sourceInfoStorage;
this.length = source.length;
} }
@Override @Override
public synchronized int length() throws ProxyCacheException { public synchronized int length() throws ProxyCacheException {
if (length == Integer.MIN_VALUE) { if (sourceInfo.length == Integer.MIN_VALUE) {
fetchContentInfo(); fetchContentInfo();
} }
return length; return sourceInfo.length;
} }
@Override @Override
public void open(int offset) throws ProxyCacheException { public void open(int offset) throws ProxyCacheException {
try { try {
connection = openConnection(offset, -1); connection = openConnection(offset, -1);
mime = connection.getContentType(); String mime = connection.getContentType();
inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE); inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE);
length = readSourceAvailableBytes(connection, offset, connection.getResponseCode()); int length = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
} catch (IOException e) { } catch (IOException e) {
throw new ProxyCacheException("Error opening connection for " + url + " with offset " + offset, e); throw new ProxyCacheException("Error opening connection for " + sourceInfo.url + " with offset " + offset, e);
} }
} }
private int readSourceAvailableBytes(HttpURLConnection connection, int offset, int responseCode) throws IOException { private int readSourceAvailableBytes(HttpURLConnection connection, int offset, int responseCode) throws IOException {
int contentLength = connection.getContentLength(); int contentLength = connection.getContentLength();
return responseCode == HTTP_OK ? contentLength return responseCode == HTTP_OK ? contentLength
: responseCode == HTTP_PARTIAL ? contentLength + offset : length; : responseCode == HTTP_PARTIAL ? contentLength + offset : sourceInfo.length;
} }
@Override @Override
@@ -90,29 +96,31 @@ public class HttpUrlSource implements Source {
@Override @Override
public int read(byte[] buffer) throws ProxyCacheException { public int read(byte[] buffer) throws ProxyCacheException {
if (inputStream == null) { if (inputStream == null) {
throw new ProxyCacheException("Error reading data from " + url + ": connection is absent!"); throw new ProxyCacheException("Error reading data from " + sourceInfo.url + ": connection is absent!");
} }
try { try {
return inputStream.read(buffer, 0, buffer.length); return inputStream.read(buffer, 0, buffer.length);
} catch (InterruptedIOException e) { } catch (InterruptedIOException e) {
throw new InterruptedProxyCacheException("Reading source " + url + " is interrupted", e); throw new InterruptedProxyCacheException("Reading source " + sourceInfo.url + " is interrupted", e);
} catch (IOException e) { } catch (IOException e) {
throw new ProxyCacheException("Error reading data from " + url, e); throw new ProxyCacheException("Error reading data from " + sourceInfo.url, e);
} }
} }
private void fetchContentInfo() throws ProxyCacheException { private void fetchContentInfo() throws ProxyCacheException {
Log.d(LOG_TAG, "Read content info from " + url); Log.d(LOG_TAG, "Read content info from " + sourceInfo.url);
HttpURLConnection urlConnection = null; HttpURLConnection urlConnection = null;
InputStream inputStream = null; InputStream inputStream = null;
try { try {
urlConnection = openConnection(0, 10000); urlConnection = openConnection(0, 10000);
length = urlConnection.getContentLength(); int length = urlConnection.getContentLength();
mime = urlConnection.getContentType(); String mime = urlConnection.getContentType();
inputStream = urlConnection.getInputStream(); inputStream = urlConnection.getInputStream();
Log.i(LOG_TAG, "Content info for `" + url + "`: mime: " + mime + ", content-length: " + length); this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
Log.i(LOG_TAG, "Source info fetched: " + sourceInfo);
} catch (IOException e) { } catch (IOException e) {
Log.e(LOG_TAG, "Error fetching info from " + url, e); Log.e(LOG_TAG, "Error fetching info from " + sourceInfo.url, e);
} finally { } finally {
ProxyCacheUtils.close(inputStream); ProxyCacheUtils.close(inputStream);
if (urlConnection != null) { if (urlConnection != null) {
@@ -125,7 +133,7 @@ public class HttpUrlSource implements Source {
HttpURLConnection connection; HttpURLConnection connection;
boolean redirected; boolean redirected;
int redirectCount = 0; int redirectCount = 0;
String url = this.url; String url = this.sourceInfo.url;
do { do {
Log.d(LOG_TAG, "Open connection " + (offset > 0 ? " with offset " + offset : "") + " to " + url); Log.d(LOG_TAG, "Open connection " + (offset > 0 ? " with offset " + offset : "") + " to " + url);
connection = (HttpURLConnection) new URL(url).openConnection(); connection = (HttpURLConnection) new URL(url).openConnection();
@@ -151,18 +159,18 @@ public class HttpUrlSource implements Source {
} }
public synchronized String getMime() throws ProxyCacheException { public synchronized String getMime() throws ProxyCacheException {
if (TextUtils.isEmpty(mime)) { if (TextUtils.isEmpty(sourceInfo.mime)) {
fetchContentInfo(); fetchContentInfo();
} }
return mime; return sourceInfo.mime;
} }
public String getUrl() { public String getUrl() {
return url; return sourceInfo.url;
} }
@Override @Override
public String toString() { public String toString() {
return "HttpUrlSource{url='" + url + "}"; return "HttpUrlSource{sourceInfo='" + sourceInfo + "}";
} }
} }

View File

@@ -1,15 +1,15 @@
package com.danikula.videocache; package com.danikula.videocache;
final class Preconditions { public final class Preconditions {
static <T> T checkNotNull(T reference) { public static <T> T checkNotNull(T reference) {
if (reference == null) { if (reference == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
return reference; return reference;
} }
static void checkAllNotNull(Object... references) { public static void checkAllNotNull(Object... references) {
for (Object reference : references) { for (Object reference : references) {
if (reference == null) { if (reference == null) {
throw new NullPointerException(); throw new NullPointerException();
@@ -17,7 +17,7 @@ final class Preconditions {
} }
} }
static <T> T checkNotNull(T reference, String errorMessage) { public static <T> T checkNotNull(T reference, String errorMessage) {
if (reference == null) { if (reference == null) {
throw new NullPointerException(errorMessage); throw new NullPointerException(errorMessage);
} }

View File

@@ -134,6 +134,7 @@ class ProxyCache {
notifyNewCacheDataAvailable(offset, sourceAvailable); notifyNewCacheDataAvailable(offset, sourceAvailable);
} }
tryComplete(); tryComplete();
onSourceRead();
} catch (Throwable e) { } catch (Throwable e) {
readSourceErrorsCount.incrementAndGet(); readSourceErrorsCount.incrementAndGet();
onError(e); onError(e);
@@ -143,6 +144,12 @@ class ProxyCache {
} }
} }
private void onSourceRead() {
// guaranteed notify listeners after source read and cache completed
percentsAvailable = 100;
onCachePercentsAvailableChanged(percentsAvailable);
}
private void tryComplete() throws ProxyCacheException { private void tryComplete() throws ProxyCacheException {
synchronized (stopLock) { synchronized (stopLock) {
if (!isStopped() && cache.available() == source.length()) { if (!isStopped() && cache.available() == source.length()) {

View File

@@ -0,0 +1,28 @@
package com.danikula.videocache;
/**
* Stores source's info.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public class SourceInfo {
public final String url;
public final int length;
public final String mime;
public SourceInfo(String url, int length, String mime) {
this.url = url;
this.length = length;
this.mime = mime;
}
@Override
public String toString() {
return "SourceInfo{" +
"url='" + url + '\'' +
", length=" + length +
", mime='" + mime + '\'' +
'}';
}
}

View File

@@ -0,0 +1,98 @@
package com.danikula.videocache.sourcestorage;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.danikula.videocache.SourceInfo;
import static com.danikula.videocache.Preconditions.checkAllNotNull;
import static com.danikula.videocache.Preconditions.checkNotNull;
/**
* Database based {@link SourceInfoStorage}.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
class DatabaseSourceInfoStorage extends SQLiteOpenHelper implements SourceInfoStorage {
private static final String TABLE = "SourceInfo";
private static final String COLUMN_ID = "_id";
private static final String COLUMN_URL = "url";
private static final String COLUMN_LENGTH = "length";
private static final String COLUMN_MIME = "mime";
private static final String[] ALL_COLUMNS = new String[]{COLUMN_ID, COLUMN_URL, COLUMN_LENGTH, COLUMN_MIME};
private static final String CREATE_SQL =
"CREATE TABLE " + TABLE + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
COLUMN_URL + " TEXT NOT NULL," +
COLUMN_MIME + " TEXT," +
COLUMN_LENGTH + " INTEGER" +
");";
DatabaseSourceInfoStorage(Context context) {
super(context, "AndroidVideoCache.db", null, 1);
checkNotNull(context);
}
@Override
public void onCreate(SQLiteDatabase db) {
checkNotNull(db);
db.execSQL(CREATE_SQL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
throw new IllegalStateException("Should not be called. There is no any migration");
}
@Override
public SourceInfo get(String url) {
checkNotNull(url);
Cursor cursor = null;
try {
cursor = getReadableDatabase().query(TABLE, ALL_COLUMNS, COLUMN_URL + "=?", new String[]{url}, null, null, null);
return cursor == null || !cursor.moveToFirst() ? null : convert(cursor);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
@Override
public void put(String url, SourceInfo sourceInfo) {
checkAllNotNull(url, sourceInfo);
SourceInfo sourceInfoFromDb = get(url);
boolean exist = sourceInfoFromDb != null;
ContentValues contentValues = convert(sourceInfo);
if (exist) {
getWritableDatabase().update(TABLE, contentValues, COLUMN_URL + "=?", new String[]{url});
} else {
getWritableDatabase().insert(TABLE, null, contentValues);
}
}
@Override
public void release() {
close();
}
private SourceInfo convert(Cursor cursor) {
return new SourceInfo(
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_URL)),
cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_LENGTH)),
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MIME))
);
}
private ContentValues convert(SourceInfo sourceInfo) {
ContentValues values = new ContentValues();
values.put(COLUMN_URL, sourceInfo.url);
values.put(COLUMN_LENGTH, sourceInfo.length);
values.put(COLUMN_MIME, sourceInfo.mime);
return values;
}
}

View File

@@ -0,0 +1,24 @@
package com.danikula.videocache.sourcestorage;
import com.danikula.videocache.SourceInfo;
/**
* {@link SourceInfoStorage} that does nothing.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public class NoSourceInfoStorage implements SourceInfoStorage {
@Override
public SourceInfo get(String url) {
return null;
}
@Override
public void put(String url, SourceInfo sourceInfo) {
}
@Override
public void release() {
}
}

View File

@@ -0,0 +1,17 @@
package com.danikula.videocache.sourcestorage;
import com.danikula.videocache.SourceInfo;
/**
* Storage for {@link SourceInfo}.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public interface SourceInfoStorage {
SourceInfo get(String url);
void put(String url, SourceInfo sourceInfo);
void release();
}

View File

@@ -0,0 +1,19 @@
package com.danikula.videocache.sourcestorage;
import android.content.Context;
/**
* Simple factory for {@link SourceInfoStorage}.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public class SourceInfoStorageFactory {
public static SourceInfoStorage newSourceInfoStorage(Context context) {
return new DatabaseSourceInfoStorage(context);
}
public static SourceInfoStorage newEmptySourceInfoStorage() {
return new NoSourceInfoStorage();
}
}

View File

@@ -38,7 +38,7 @@ dependencies {
// compile project(':library') // compile project(':library')
compile 'com.android.support:support-v4:23.1.0' compile 'com.android.support:support-v4:23.1.0'
compile 'org.androidannotations:androidannotations-api:3.3.2' compile 'org.androidannotations:androidannotations-api:3.3.2'
compile 'com.danikula:videocache:2.5.0' compile 'com.danikula:videocache:2.6.0'
compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar' compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar'
apt 'org.androidannotations:androidannotations:3.3.2' apt 'org.androidannotations:androidannotations:3.3.2'
} }

View File

@@ -15,14 +15,6 @@ android {
versionCode 1 versionCode 1
versionName '0.1' versionName '0.1'
} }
buildTypes {
debug {
buildConfigField "int", "MIN_SDK_VERSION", Integer.toString(android.defaultConfig.minSdkVersion.apiLevel)
}
release {
buildConfigField "int", "MIN_SDK_VERSION", Integer.toString(android.defaultConfig.minSdkVersion.apiLevel)
}
}
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7 sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7
@@ -30,13 +22,10 @@ android {
} }
dependencies { dependencies {
compile project (':library') compile project(':library')
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile('org.robolectric:robolectric:3.0-rc2') { testCompile 'org.robolectric:robolectric:3.1'
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
}
testCompile 'com.squareup:fest-android:1.0.0' testCompile 'com.squareup:fest-android:1.0.0'
testCompile 'com.google.guava:guava-jdk5:17.0' testCompile 'com.google.guava:guava-jdk5:17.0'
testCompile('com.danikula:android-garden:2.1.4') { testCompile('com.danikula:android-garden:2.1.4') {

View File

@@ -20,7 +20,7 @@ import static org.fest.assertions.api.Assertions.fail;
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION) @Config(constants = BuildConfig.class)
public class FileNameGeneratorTest { public class FileNameGeneratorTest {
@Test @Test

View File

@@ -17,7 +17,7 @@ import static org.fest.assertions.api.Assertions.fail;
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION) @Config(constants = BuildConfig.class)
public class GetRequestTest { public class GetRequestTest {
@Test @Test

View File

@@ -41,7 +41,7 @@ import static org.fest.assertions.api.Assertions.assertThat;
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION) @Config(constants = BuildConfig.class)
public class HttpProxyCacheServerTest { public class HttpProxyCacheServerTest {
private File cacheFolder; private File cacheFolder;

View File

@@ -1,6 +1,9 @@
package com.danikula.videocache; package com.danikula.videocache;
import com.danikula.android.garden.io.IoUtils;
import com.danikula.videocache.file.FileCache; import com.danikula.videocache.file.FileCache;
import com.danikula.videocache.sourcestorage.SourceInfoStorage;
import com.danikula.videocache.sourcestorage.SourceInfoStorageFactory;
import com.danikula.videocache.support.ProxyCacheTestUtils; import com.danikula.videocache.support.ProxyCacheTestUtils;
import com.danikula.videocache.support.Response; import com.danikula.videocache.support.Response;
import com.danikula.videocache.test.BuildConfig; import com.danikula.videocache.test.BuildConfig;
@@ -9,6 +12,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -23,12 +27,15 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_BIG_NAME; 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_URL; import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_SIZE; 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;
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile; import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadTestData; import static com.danikula.videocache.support.ProxyCacheTestUtils.loadTestData;
import static com.danikula.videocache.support.ProxyCacheTestUtils.newCacheFile;
import static org.fest.assertions.api.Assertions.assertThat; import static org.fest.assertions.api.Assertions.assertThat;
import static org.fest.assertions.api.Assertions.fail;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyLong;
@@ -43,7 +50,7 @@ import static org.mockito.Mockito.when;
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION) @Config(constants = BuildConfig.class)
public class HttpProxyCacheTest { public class HttpProxyCacheTest {
@Test @Test
@@ -159,6 +166,73 @@ public class HttpProxyCacheTest {
assertThat(response.data).isEmpty(); assertThat(response.data).isEmpty();
} }
@Test
public void testCacheListenerCalledAtTheEnd() throws Exception {
File file = ProxyCacheTestUtils.newCacheFile();
File tempFile = ProxyCacheTestUtils.getTempFile(file);
HttpProxyCache proxyCache = new HttpProxyCache(new HttpUrlSource(HTTP_DATA_URL), new FileCache(file));
CacheListener listener = Mockito.mock(CacheListener.class);
proxyCache.registerCacheListener(listener);
processRequest(proxyCache, "GET /" + HTTP_DATA_URL + " HTTP/1.1");
Mockito.verify(listener).onCacheAvailable(tempFile, HTTP_DATA_URL, 100); // must be called for temp file ...
Mockito.verify(listener).onCacheAvailable(file, HTTP_DATA_URL, 100); // .. and for original file too
}
@Test(expected = ProxyCacheException.class)
public void testTouchSourceForAbsentSourceInfoAndCache() throws Exception {
SourceInfoStorage sourceInfoStorage = SourceInfoStorageFactory.newEmptySourceInfoStorage();
HttpUrlSource source = ProxyCacheTestUtils.newNotOpenableHttpUrlSource(HTTP_DATA_URL, sourceInfoStorage);
HttpProxyCache proxyCache = new HttpProxyCache(source, new FileCache(newCacheFile()));
processRequest(proxyCache, "GET /" + HTTP_DATA_URL + " HTTP/1.1");
proxyCache.shutdown();
fail("Angry source should throw error! There is no file and caches source info");
}
@Test(expected = ProxyCacheException.class)
public void testTouchSourceForExistedSourceInfoAndAbsentCache() throws Exception {
SourceInfoStorage sourceInfoStorage = SourceInfoStorageFactory.newSourceInfoStorage(RuntimeEnvironment.application);
sourceInfoStorage.put(HTTP_DATA_URL, new SourceInfo(HTTP_DATA_URL, HTTP_DATA_SIZE, "image/jpg"));
HttpUrlSource source = ProxyCacheTestUtils.newNotOpenableHttpUrlSource(HTTP_DATA_URL, sourceInfoStorage);
HttpProxyCache proxyCache = new HttpProxyCache(source, new FileCache(newCacheFile()));
processRequest(proxyCache, "GET /" + HTTP_DATA_URL + " HTTP/1.1");
proxyCache.shutdown();
fail("Angry source should throw error! There is no cache file");
}
@Test
public void testTouchSourceForExistedSourceInfoAndCache() throws Exception {
SourceInfoStorage sourceInfoStorage = SourceInfoStorageFactory.newSourceInfoStorage(RuntimeEnvironment.application);
sourceInfoStorage.put(HTTP_DATA_URL, new SourceInfo(HTTP_DATA_URL, HTTP_DATA_SIZE, "cached/mime"));
HttpUrlSource source = ProxyCacheTestUtils.newNotOpenableHttpUrlSource(HTTP_DATA_URL, sourceInfoStorage);
File file = newCacheFile();
IoUtils.saveToFile(loadAssetFile(ASSETS_DATA_NAME), file);
HttpProxyCache proxyCache = new HttpProxyCache(source, new FileCache(file));
Response response = processRequest(proxyCache, "GET /" + HTTP_DATA_URL + " HTTP/1.1");
proxyCache.shutdown();
assertThat(response.data).isEqualTo(loadAssetFile(ASSETS_DATA_NAME));
assertThat(response.contentLength).isEqualTo(HTTP_DATA_SIZE);
assertThat(response.contentType).isEqualTo("cached/mime");
}
@Test
public void testReuseSourceInfo() throws Exception {
SourceInfoStorage sourceInfoStorage = SourceInfoStorageFactory.newSourceInfoStorage(RuntimeEnvironment.application);
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL, sourceInfoStorage);
File cacheFile = newCacheFile();
HttpProxyCache proxyCache = new HttpProxyCache(source, new FileCache(cacheFile));
processRequest(proxyCache, "GET /" + HTTP_DATA_URL + " HTTP/1.1");
HttpUrlSource notOpenableSource = ProxyCacheTestUtils.newNotOpenableHttpUrlSource(HTTP_DATA_URL, sourceInfoStorage);
HttpProxyCache proxyCache2 = new HttpProxyCache(notOpenableSource, new FileCache(cacheFile));
Response response = processRequest(proxyCache2, "GET /" + HTTP_DATA_URL + " HTTP/1.1");
proxyCache.shutdown();
assertThat(response.data).isEqualTo(loadAssetFile(ASSETS_DATA_NAME));
assertThat(response.contentLength).isEqualTo(HTTP_DATA_SIZE);
assertThat(response.contentType).isEqualTo("image/jpeg");
}
private Response processRequest(String sourceUrl, String httpRequest) throws ProxyCacheException, IOException { private Response processRequest(String sourceUrl, String httpRequest) throws ProxyCacheException, IOException {
FileCache fileCache = new FileCache(ProxyCacheTestUtils.newCacheFile()); FileCache fileCache = new FileCache(ProxyCacheTestUtils.newCacheFile());
return processRequest(sourceUrl, httpRequest, fileCache); return processRequest(sourceUrl, httpRequest, fileCache);

View File

@@ -1,10 +1,14 @@
package com.danikula.videocache; package com.danikula.videocache;
import com.danikula.videocache.sourcestorage.SourceInfoStorage;
import com.danikula.videocache.sourcestorage.SourceInfoStorageFactory;
import com.danikula.videocache.support.ProxyCacheTestUtils;
import com.danikula.videocache.test.BuildConfig; import com.danikula.videocache.test.BuildConfig;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
@@ -24,12 +28,13 @@ import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile; import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
import static org.fest.assertions.api.Assertions.assertThat; import static org.fest.assertions.api.Assertions.assertThat;
import static org.fest.assertions.api.Assertions.fail; import static org.fest.assertions.api.Assertions.fail;
import static org.mockito.Matchers.any;
/** /**
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION) @Config(constants = BuildConfig.class)
public class HttpUrlSourceTest { public class HttpUrlSourceTest {
@Test @Test
@@ -132,6 +137,31 @@ public class HttpUrlSourceTest {
assertThat(new HttpUrlSource(HTTP_DATA_URL).getMime()).isEqualTo("image/jpeg"); assertThat(new HttpUrlSource(HTTP_DATA_URL).getMime()).isEqualTo("image/jpeg");
} }
@Test(expected = RuntimeException.class)
public void testAngryHttpUrlSourceLength() throws Exception {
ProxyCacheTestUtils.newAngryHttpUrlSource().length();
fail("source.length() should throw exception");
}
@Test(expected = RuntimeException.class)
public void testAngryHttpUrlSourceOpen() throws Exception {
ProxyCacheTestUtils.newAngryHttpUrlSource().open(Mockito.anyInt());
fail("source.open() should throw exception");
}
@Test(expected = RuntimeException.class)
public void testAngryHttpUrlSourceRead() throws Exception {
ProxyCacheTestUtils.newAngryHttpUrlSource().read(any(byte[].class));
fail("source.read() should throw exception");
}
@Test(expected = RuntimeException.class)
public void testNotOpenableHttpUrlSourceOpen() throws Exception {
SourceInfoStorage sourceInfoStorage = SourceInfoStorageFactory.newEmptySourceInfoStorage();
ProxyCacheTestUtils.newNotOpenableHttpUrlSource("", sourceInfoStorage).open(Mockito.anyInt());
fail("source.open() should throw exception");
}
private void readSource(Source source, byte[] target) throws ProxyCacheException { private void readSource(Source source, byte[] target) throws ProxyCacheException {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int totalRead = 0; int totalRead = 0;

View File

@@ -2,8 +2,7 @@ package com.danikula.videocache;
import com.danikula.android.garden.io.IoUtils; import com.danikula.android.garden.io.IoUtils;
import com.danikula.videocache.file.FileCache; import com.danikula.videocache.file.FileCache;
import com.danikula.videocache.support.AngryHttpUrlSource; import com.danikula.videocache.support.ProxyCacheTestUtils;
import com.danikula.videocache.support.PhlegmaticByteArraySource;
import com.danikula.videocache.test.BuildConfig; import com.danikula.videocache.test.BuildConfig;
import org.junit.Test; import org.junit.Test;
@@ -23,13 +22,14 @@ import static com.danikula.videocache.support.ProxyCacheTestUtils.generate;
import static com.danikula.videocache.support.ProxyCacheTestUtils.getFileContent; import static com.danikula.videocache.support.ProxyCacheTestUtils.getFileContent;
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile; import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
import static com.danikula.videocache.support.ProxyCacheTestUtils.newCacheFile; import static com.danikula.videocache.support.ProxyCacheTestUtils.newCacheFile;
import static com.danikula.videocache.support.ProxyCacheTestUtils.newPhlegmaticSource;
import static org.fest.assertions.api.Assertions.assertThat; import static org.fest.assertions.api.Assertions.assertThat;
/** /**
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION) @Config(constants = BuildConfig.class)
public class ProxyCacheTest { public class ProxyCacheTest {
@Test @Test
@@ -95,7 +95,7 @@ public class ProxyCacheTest {
public void testProxyWithPhlegmaticSource() throws Exception { public void testProxyWithPhlegmaticSource() throws Exception {
int dataSize = 100000; int dataSize = 100000;
byte[] sourceData = generate(dataSize); byte[] sourceData = generate(dataSize);
Source source = new PhlegmaticByteArraySource(sourceData, 200); Source source = newPhlegmaticSource(sourceData, 200);
ProxyCache proxyCache = new ProxyCache(source, new FileCache(newCacheFile())); ProxyCache proxyCache = new ProxyCache(source, new FileCache(newCacheFile()));
byte[] readData = new byte[dataSize]; byte[] readData = new byte[dataSize];
proxyCache.read(readData, 0, dataSize); proxyCache.read(readData, 0, dataSize);
@@ -106,7 +106,7 @@ public class ProxyCacheTest {
@Test @Test
public void testReadEnd() throws Exception { public void testReadEnd() throws Exception {
int capacity = 5323; int capacity = 5323;
Source source = new PhlegmaticByteArraySource(generate(capacity), 200); Source source = newPhlegmaticSource(generate(capacity), 200);
Cache cache = new FileCache(newCacheFile()); Cache cache = new FileCache(newCacheFile());
ProxyCache proxyCache = new ProxyCache(source, cache); ProxyCache proxyCache = new ProxyCache(source, cache);
proxyCache.read(new byte[1], capacity - 1, 1); proxyCache.read(new byte[1], capacity - 1, 1);
@@ -118,7 +118,7 @@ public class ProxyCacheTest {
public void testReadRandomParts() throws Exception { public void testReadRandomParts() throws Exception {
int dataSize = 123456; int dataSize = 123456;
byte[] sourceData = generate(dataSize); byte[] sourceData = generate(dataSize);
Source source = new PhlegmaticByteArraySource(sourceData, 300); Source source = newPhlegmaticSource(sourceData, 300);
File file = newCacheFile(); File file = newCacheFile();
Cache cache = new FileCache(file); Cache cache = new FileCache(file);
ProxyCache proxyCache = new ProxyCache(source, cache); ProxyCache proxyCache = new ProxyCache(source, cache);
@@ -176,8 +176,9 @@ public class ProxyCacheTest {
byte[] data = generate(dataSize); byte[] data = generate(dataSize);
File file = newCacheFile(); File file = newCacheFile();
IoUtils.saveToFile(data, file); IoUtils.saveToFile(data, file);
ProxyCache proxyCache = new ProxyCache(new AngryHttpUrlSource(), new FileCache(file));
Source source = ProxyCacheTestUtils.newAngryHttpUrlSource();
ProxyCache proxyCache = new ProxyCache(source, new FileCache(file));
byte[] readData = new byte[dataSize]; byte[] readData = new byte[dataSize];
proxyCache.read(readData, 0, dataSize); proxyCache.read(readData, 0, dataSize);

View File

@@ -22,7 +22,7 @@ import static org.fest.assertions.api.Assertions.assertThat;
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION) @Config(constants = BuildConfig.class)
public class DiskUsageTest { public class DiskUsageTest {
private File cacheFolder; private File cacheFolder;

View File

@@ -28,7 +28,7 @@ import static org.fest.assertions.api.Assertions.assertThat;
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION) @Config(constants = BuildConfig.class)
public class FileCacheTest { public class FileCacheTest {
@Test @Test

View File

@@ -18,7 +18,7 @@ import static org.fest.assertions.api.Assertions.assertThat;
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = BuildConfig.MIN_SDK_VERSION) @Config(constants = BuildConfig.class)
public class FilesTest { public class FilesTest {
@Test @Test

View File

@@ -0,0 +1,89 @@
package com.danikula.videocache.sourcestorage;
import com.danikula.videocache.SourceInfo;
import com.danikula.videocache.test.BuildConfig;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.fest.assertions.api.Assertions.fail;
/**
* Tests for {@link SourceInfoStorage}.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)
public class SourceInfoStorageTest {
private SourceInfoStorage storage;
@Before
public void setUp() throws Exception {
storage = SourceInfoStorageFactory.newSourceInfoStorage(RuntimeEnvironment.application);
}
@After
public void tearDown() throws Exception {
storage.release();
}
@Test
public void testGetAbsent() throws Exception {
SourceInfo sourceInfo = storage.get(":-)");
assertThat(sourceInfo).isNull();
}
@Test
public void testSaving() throws Exception {
storage.put(":-)", new SourceInfo(":-)", 42, "text/plain"));
storage.put(":-(", new SourceInfo(":-(", 43, "video/mp4"));
SourceInfo sourceInfo = storage.get(":-)");
assertThat(sourceInfo.url).isEqualTo(":-)");
assertThat(sourceInfo.length).isEqualTo(42);
assertThat(sourceInfo.mime).isEqualTo("text/plain");
SourceInfo sourceInfo2 = storage.get(":-(");
assertThat(sourceInfo2.url).isEqualTo(":-(");
assertThat(sourceInfo2.length).isEqualTo(43);
assertThat(sourceInfo2.mime).isEqualTo("video/mp4");
}
@Test
public void testUpdating() throws Exception {
String url = ":-)";
storage.put(url, new SourceInfo(url, 42, "text/plain"));
storage.put(url, new SourceInfo(url, 43, "video/mp4"));
SourceInfo sourceInfo = storage.get(url);
assertThat(sourceInfo.url).isEqualTo(url);
assertThat(sourceInfo.length).isEqualTo(43);
assertThat(sourceInfo.mime).isEqualTo("video/mp4");
}
@Test(expected = NullPointerException.class)
public void testNpeForGetting() throws Exception {
storage.get(null);
fail("null is not acceptable");
}
@Test(expected = NullPointerException.class)
public void testNpeForPuttingUrl() throws Exception {
storage.put(null, new SourceInfo("", 0, ""));
fail("null is not acceptable");
}
@Test(expected = NullPointerException.class)
public void testNpeForPuttingSource() throws Exception {
storage.put("url", null);
fail("null is not acceptable");
}
}

View File

@@ -1,33 +0,0 @@
package com.danikula.videocache.support;
import com.danikula.videocache.ProxyCacheException;
import com.danikula.videocache.Source;
/**
* {@link Source} that throws exception in all methods.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
@Deprecated // use Mockito to throw error
public class AngryHttpUrlSource implements Source {
@Override
public int length() throws ProxyCacheException {
throw new IllegalStateException();
}
@Override
public void open(int offset) throws ProxyCacheException {
throw new IllegalStateException();
}
@Override
public void close() throws ProxyCacheException {
throw new IllegalStateException();
}
@Override
public int read(byte[] buffer) throws ProxyCacheException {
throw new IllegalStateException();
}
}

View File

@@ -1,32 +0,0 @@
package com.danikula.videocache.support;
import com.danikula.videocache.ByteArraySource;
import com.danikula.videocache.ProxyCacheException;
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;
private final int maxDelayMs;
public PhlegmaticByteArraySource(byte[] data, int maxDelayMs) {
super(data);
this.maxDelayMs = maxDelayMs;
this.delayGenerator = new Random(System.currentTimeMillis());
}
@Override
public int read(byte[] buffer) throws ProxyCacheException {
try {
Thread.sleep(delayGenerator.nextInt(maxDelayMs));
} catch (InterruptedException e) {
throw new ProxyCacheException("Error sleeping", e);
}
return super.read(buffer);
}
}

View File

@@ -1,9 +1,16 @@
package com.danikula.videocache.support; package com.danikula.videocache.support;
import com.danikula.android.garden.io.IoUtils; import com.danikula.android.garden.io.IoUtils;
import com.danikula.videocache.ByteArraySource;
import com.danikula.videocache.HttpProxyCacheServer; import com.danikula.videocache.HttpProxyCacheServer;
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.io.Files; import com.google.common.io.Files;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -15,6 +22,13 @@ import java.net.URL;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
/** /**
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
*/ */
@@ -83,4 +97,42 @@ public class ProxyCacheTestUtils {
random.nextBytes(result); random.nextBytes(result);
return result; return result;
} }
public static HttpUrlSource newAngryHttpUrlSource() throws ProxyCacheException {
HttpUrlSource source = mock(HttpUrlSource.class);
doThrow(new RuntimeException()).when(source).getMime();
doThrow(new RuntimeException()).when(source).read(any(byte[].class));
doThrow(new RuntimeException()).when(source).open(anyInt());
doThrow(new RuntimeException()).when(source).length();
doThrow(new RuntimeException()).when(source).getUrl();
doThrow(new RuntimeException()).when(source).close();
return source;
}
public static HttpUrlSource newNotOpenableHttpUrlSource(String url, SourceInfoStorage sourceInfoStorage) throws ProxyCacheException {
HttpUrlSource httpUrlSource = new HttpUrlSource(url, sourceInfoStorage);
HttpUrlSource source = spy(httpUrlSource);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
System.out.print("Can't open!!!");
throw new RuntimeException();
}
}).when(source).open(anyInt());
return source;
}
public static Source newPhlegmaticSource(byte[] data, final int maxDelayMs) throws ProxyCacheException {
Source spySource = spy(new ByteArraySource(data));
final Random delayGenerator = new Random(System.currentTimeMillis());
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(delayGenerator.nextInt(maxDelayMs));
return null;
}
}).doCallRealMethod().when(spySource).read(any(byte[].class));
return spySource;
}
} }