Skip to content

Commit 71c6301

Browse files
committed
🤘 cache source's info in db to increase performance (#41, #45)
1 parent 372542c commit 71c6301

19 files changed

+481
-110
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.danikula.videocache.file.DiskUsage;
44
import com.danikula.videocache.file.FileNameGenerator;
5+
import com.danikula.videocache.sourcestorage.SourceInfoStorage;
56

67
import java.io.File;
78

@@ -15,11 +16,13 @@ class Config {
1516
public final File cacheRoot;
1617
public final FileNameGenerator fileNameGenerator;
1718
public final DiskUsage diskUsage;
19+
public final SourceInfoStorage sourceInfoStorage;
1820

19-
Config(File cacheRoot, FileNameGenerator fileNameGenerator, DiskUsage diskUsage) {
21+
Config(File cacheRoot, FileNameGenerator fileNameGenerator, DiskUsage diskUsage, SourceInfoStorage sourceInfoStorage) {
2022
this.cacheRoot = cacheRoot;
2123
this.fileNameGenerator = fileNameGenerator;
2224
this.diskUsage = diskUsage;
25+
this.sourceInfoStorage = sourceInfoStorage;
2326
}
2427

2528
File generateCacheFile(String url) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private void responseWithoutCache(OutputStream out, long offset) throws ProxyCac
101101
@Override
102102
protected void onCachePercentsAvailableChanged(int percents) {
103103
if (listener != null) {
104-
listener.onCacheAvailable(cache.file, source.url, percents);
104+
listener.onCacheAvailable(cache.file, source.getUrl(), percents);
105105
}
106106
}
107107
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import com.danikula.videocache.file.Md5FileNameGenerator;
1010
import com.danikula.videocache.file.TotalCountLruDiskUsage;
1111
import com.danikula.videocache.file.TotalSizeLruDiskUsage;
12+
import com.danikula.videocache.sourcestorage.SourceInfoStorage;
13+
import com.danikula.videocache.sourcestorage.SourceInfoStorageFactory;
1214

1315
import java.io.File;
1416
import java.io.IOException;
@@ -192,6 +194,8 @@ public void shutdown() {
192194

193195
shutdownClients();
194196

197+
config.sourceInfoStorage.release();
198+
195199
waitConnectionThread.interrupt();
196200
try {
197201
if (!serverSocket.isClosed()) {
@@ -364,8 +368,10 @@ public static final class Builder {
364368
private File cacheRoot;
365369
private FileNameGenerator fileNameGenerator;
366370
private DiskUsage diskUsage;
371+
private SourceInfoStorage sourceInfoStorage;
367372

368373
public Builder(Context context) {
374+
this.sourceInfoStorage = SourceInfoStorageFactory.newSourceInfoStorage(context);
369375
this.cacheRoot = StorageUtils.getIndividualCacheDirectory(context);
370376
this.diskUsage = new TotalSizeLruDiskUsage(DEFAULT_MAX_SIZE);
371377
this.fileNameGenerator = new Md5FileNameGenerator();
@@ -439,7 +445,7 @@ public HttpProxyCacheServer build() {
439445
}
440446

441447
private Config buildConfig() {
442-
return new Config(cacheRoot, fileNameGenerator, diskUsage);
448+
return new Config(cacheRoot, fileNameGenerator, diskUsage, sourceInfoStorage);
443449
}
444450

445451
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public int getClientsCount() {
7979
}
8080

8181
private HttpProxyCache newHttpProxyCache() throws ProxyCacheException {
82-
HttpUrlSource source = new HttpUrlSource(url);
82+
HttpUrlSource source = new HttpUrlSource(url, config.sourceInfoStorage);
8383
FileCache cache = new FileCache(config.generateCacheFile(url), config.diskUsage);
8484
HttpProxyCache httpProxyCache = new HttpProxyCache(source, cache);
8585
httpProxyCache.registerCacheListener(uiCacheListener);

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

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
import android.text.TextUtils;
44
import android.util.Log;
55

6+
import com.danikula.videocache.sourcestorage.SourceInfoStorage;
7+
import com.danikula.videocache.sourcestorage.SourceInfoStorageFactory;
8+
69
import java.io.BufferedInputStream;
710
import java.io.IOException;
811
import java.io.InputStream;
912
import java.io.InterruptedIOException;
1013
import java.net.HttpURLConnection;
1114
import java.net.URL;
1215

16+
import static com.danikula.videocache.Preconditions.checkNotNull;
1317
import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;
1418
import static com.danikula.videocache.ProxyCacheUtils.LOG_TAG;
1519
import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
@@ -26,51 +30,53 @@
2630
public class HttpUrlSource implements Source {
2731

2832
private static final int MAX_REDIRECTS = 5;
29-
public final String url;
33+
private final SourceInfoStorage sourceInfoStorage;
34+
private SourceInfo sourceInfo;
3035
private HttpURLConnection connection;
3136
private InputStream inputStream;
32-
private volatile int length = Integer.MIN_VALUE;
33-
private volatile String mime;
3437

3538
public HttpUrlSource(String url) {
36-
this(url, ProxyCacheUtils.getSupposablyMime(url));
39+
this(url, SourceInfoStorageFactory.newEmptySourceInfoStorage());
3740
}
3841

39-
public HttpUrlSource(String url, String mime) {
40-
this.url = Preconditions.checkNotNull(url);
41-
this.mime = mime;
42+
public HttpUrlSource(String url, SourceInfoStorage sourceInfoStorage) {
43+
this.sourceInfoStorage = checkNotNull(sourceInfoStorage);
44+
SourceInfo sourceInfo = sourceInfoStorage.get(url);
45+
this.sourceInfo = sourceInfo != null ? sourceInfo :
46+
new SourceInfo(url, Integer.MIN_VALUE, ProxyCacheUtils.getSupposablyMime(url));
4247
}
4348

4449
public HttpUrlSource(HttpUrlSource source) {
45-
this.url = source.url;
46-
this.mime = source.mime;
47-
this.length = source.length;
50+
this.sourceInfo = source.sourceInfo;
51+
this.sourceInfoStorage = source.sourceInfoStorage;
4852
}
4953

5054
@Override
5155
public synchronized int length() throws ProxyCacheException {
52-
if (length == Integer.MIN_VALUE) {
56+
if (sourceInfo.length == Integer.MIN_VALUE) {
5357
fetchContentInfo();
5458
}
55-
return length;
59+
return sourceInfo.length;
5660
}
5761

5862
@Override
5963
public void open(int offset) throws ProxyCacheException {
6064
try {
6165
connection = openConnection(offset, -1);
62-
mime = connection.getContentType();
66+
String mime = connection.getContentType();
6367
inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE);
64-
length = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
68+
int length = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
69+
this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
70+
this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
6571
} catch (IOException e) {
66-
throw new ProxyCacheException("Error opening connection for " + url + " with offset " + offset, e);
72+
throw new ProxyCacheException("Error opening connection for " + sourceInfo.url + " with offset " + offset, e);
6773
}
6874
}
6975

7076
private int readSourceAvailableBytes(HttpURLConnection connection, int offset, int responseCode) throws IOException {
7177
int contentLength = connection.getContentLength();
7278
return responseCode == HTTP_OK ? contentLength
73-
: responseCode == HTTP_PARTIAL ? contentLength + offset : length;
79+
: responseCode == HTTP_PARTIAL ? contentLength + offset : sourceInfo.length;
7480
}
7581

7682
@Override
@@ -90,29 +96,31 @@ public void close() throws ProxyCacheException {
9096
@Override
9197
public int read(byte[] buffer) throws ProxyCacheException {
9298
if (inputStream == null) {
93-
throw new ProxyCacheException("Error reading data from " + url + ": connection is absent!");
99+
throw new ProxyCacheException("Error reading data from " + sourceInfo.url + ": connection is absent!");
94100
}
95101
try {
96102
return inputStream.read(buffer, 0, buffer.length);
97103
} catch (InterruptedIOException e) {
98-
throw new InterruptedProxyCacheException("Reading source " + url + " is interrupted", e);
104+
throw new InterruptedProxyCacheException("Reading source " + sourceInfo.url + " is interrupted", e);
99105
} catch (IOException e) {
100-
throw new ProxyCacheException("Error reading data from " + url, e);
106+
throw new ProxyCacheException("Error reading data from " + sourceInfo.url, e);
101107
}
102108
}
103109

104110
private void fetchContentInfo() throws ProxyCacheException {
105-
Log.d(LOG_TAG, "Read content info from " + url);
111+
Log.d(LOG_TAG, "Read content info from " + sourceInfo.url);
106112
HttpURLConnection urlConnection = null;
107113
InputStream inputStream = null;
108114
try {
109115
urlConnection = openConnection(0, 10000);
110-
length = urlConnection.getContentLength();
111-
mime = urlConnection.getContentType();
116+
int length = urlConnection.getContentLength();
117+
String mime = urlConnection.getContentType();
112118
inputStream = urlConnection.getInputStream();
113-
Log.i(LOG_TAG, "Content info for `" + url + "`: mime: " + mime + ", content-length: " + length);
119+
this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
120+
this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
121+
Log.i(LOG_TAG, "Source info fetched: " + sourceInfo);
114122
} catch (IOException e) {
115-
Log.e(LOG_TAG, "Error fetching info from " + url, e);
123+
Log.e(LOG_TAG, "Error fetching info from " + sourceInfo.url, e);
116124
} finally {
117125
ProxyCacheUtils.close(inputStream);
118126
if (urlConnection != null) {
@@ -125,7 +133,7 @@ private HttpURLConnection openConnection(int offset, int timeout) throws IOExcep
125133
HttpURLConnection connection;
126134
boolean redirected;
127135
int redirectCount = 0;
128-
String url = this.url;
136+
String url = this.sourceInfo.url;
129137
do {
130138
Log.d(LOG_TAG, "Open connection " + (offset > 0 ? " with offset " + offset : "") + " to " + url);
131139
connection = (HttpURLConnection) new URL(url).openConnection();
@@ -151,18 +159,18 @@ private HttpURLConnection openConnection(int offset, int timeout) throws IOExcep
151159
}
152160

153161
public synchronized String getMime() throws ProxyCacheException {
154-
if (TextUtils.isEmpty(mime)) {
162+
if (TextUtils.isEmpty(sourceInfo.mime)) {
155163
fetchContentInfo();
156164
}
157-
return mime;
165+
return sourceInfo.mime;
158166
}
159167

160168
public String getUrl() {
161-
return url;
169+
return sourceInfo.url;
162170
}
163171

164172
@Override
165173
public String toString() {
166-
return "HttpUrlSource{url='" + url + "}";
174+
return "HttpUrlSource{sourceInfo='" + sourceInfo + "}";
167175
}
168176
}

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

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

3-
final class Preconditions {
3+
public final class Preconditions {
44

5-
static <T> T checkNotNull(T reference) {
5+
public static <T> T checkNotNull(T reference) {
66
if (reference == null) {
77
throw new NullPointerException();
88
}
99
return reference;
1010
}
1111

12-
static void checkAllNotNull(Object... references) {
12+
public static void checkAllNotNull(Object... references) {
1313
for (Object reference : references) {
1414
if (reference == null) {
1515
throw new NullPointerException();
1616
}
1717
}
1818
}
1919

20-
static <T> T checkNotNull(T reference, String errorMessage) {
20+
public static <T> T checkNotNull(T reference, String errorMessage) {
2121
if (reference == null) {
2222
throw new NullPointerException(errorMessage);
2323
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.danikula.videocache;
2+
3+
/**
4+
* Stores source's info.
5+
*
6+
* @author Alexey Danilov ([email protected]).
7+
*/
8+
public class SourceInfo {
9+
10+
public final String url;
11+
public final int length;
12+
public final String mime;
13+
14+
public SourceInfo(String url, int length, String mime) {
15+
this.url = url;
16+
this.length = length;
17+
this.mime = mime;
18+
}
19+
20+
@Override
21+
public String toString() {
22+
return "SourceInfo{" +
23+
"url='" + url + '\'' +
24+
", length=" + length +
25+
", mime='" + mime + '\'' +
26+
'}';
27+
}
28+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.danikula.videocache.sourcestorage;
2+
3+
import android.content.ContentValues;
4+
import android.content.Context;
5+
import android.database.Cursor;
6+
import android.database.sqlite.SQLiteDatabase;
7+
import android.database.sqlite.SQLiteOpenHelper;
8+
9+
import com.danikula.videocache.SourceInfo;
10+
11+
import static com.danikula.videocache.Preconditions.checkAllNotNull;
12+
import static com.danikula.videocache.Preconditions.checkNotNull;
13+
14+
/**
15+
* Database based {@link SourceInfoStorage}.
16+
*
17+
* @author Alexey Danilov ([email protected]).
18+
*/
19+
class DatabaseSourceInfoStorage extends SQLiteOpenHelper implements SourceInfoStorage {
20+
21+
private static final String TABLE = "SourceInfo";
22+
private static final String COLUMN_ID = "_id";
23+
private static final String COLUMN_URL = "url";
24+
private static final String COLUMN_LENGTH = "length";
25+
private static final String COLUMN_MIME = "mime";
26+
private static final String[] ALL_COLUMNS = new String[]{COLUMN_ID, COLUMN_URL, COLUMN_LENGTH, COLUMN_MIME};
27+
private static final String CREATE_SQL =
28+
"CREATE TABLE " + TABLE + " (" +
29+
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
30+
COLUMN_URL + " TEXT NOT NULL," +
31+
COLUMN_MIME + " TEXT," +
32+
COLUMN_LENGTH + " INTEGER" +
33+
");";
34+
35+
DatabaseSourceInfoStorage(Context context) {
36+
super(context, "AndroidVideoCache.db", null, 1);
37+
checkNotNull(context);
38+
}
39+
40+
@Override
41+
public void onCreate(SQLiteDatabase db) {
42+
checkNotNull(db);
43+
db.execSQL(CREATE_SQL);
44+
}
45+
46+
@Override
47+
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
48+
throw new IllegalStateException("Should not be called. There is no any migration");
49+
}
50+
51+
@Override
52+
public SourceInfo get(String url) {
53+
checkNotNull(url);
54+
Cursor cursor = null;
55+
try {
56+
cursor = getReadableDatabase().query(TABLE, ALL_COLUMNS, COLUMN_URL + "=?", new String[]{url}, null, null, null);
57+
return cursor == null || !cursor.moveToFirst() ? null : convert(cursor);
58+
} finally {
59+
if (cursor != null) {
60+
cursor.close();
61+
}
62+
}
63+
}
64+
65+
@Override
66+
public void put(String url, SourceInfo sourceInfo) {
67+
checkAllNotNull(url, sourceInfo);
68+
SourceInfo sourceInfoFromDb = get(url);
69+
boolean exist = sourceInfoFromDb != null;
70+
ContentValues contentValues = convert(sourceInfo);
71+
if (exist) {
72+
getWritableDatabase().update(TABLE, contentValues, COLUMN_URL + "=?", new String[]{url});
73+
} else {
74+
getWritableDatabase().insert(TABLE, null, contentValues);
75+
}
76+
}
77+
78+
@Override
79+
public void release() {
80+
close();
81+
}
82+
83+
private SourceInfo convert(Cursor cursor) {
84+
return new SourceInfo(
85+
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_URL)),
86+
cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_LENGTH)),
87+
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MIME))
88+
);
89+
}
90+
91+
private ContentValues convert(SourceInfo sourceInfo) {
92+
ContentValues values = new ContentValues();
93+
values.put(COLUMN_URL, sourceInfo.url);
94+
values.put(COLUMN_LENGTH, sourceInfo.length);
95+
values.put(COLUMN_MIME, sourceInfo.mime);
96+
return values;
97+
}
98+
}

0 commit comments

Comments
 (0)