485 lines
14 KiB
Java
485 lines
14 KiB
Java
/*
|
|
* Copyright 2015-2016 Hippo Seven
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.hippo.conaco;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.os.AsyncTask;
|
|
import android.util.Log;
|
|
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.UiThread;
|
|
|
|
import com.hippo.beerbelly.SimpleDiskCache;
|
|
import com.hippo.okhttp.ChromeRequestBuilder;
|
|
import com.hippo.streampipe.OutputStreamPipe;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.concurrent.Executor;
|
|
|
|
import okhttp3.Call;
|
|
import okhttp3.OkHttpClient;
|
|
import okhttp3.Request;
|
|
import okhttp3.Response;
|
|
import okhttp3.ResponseBody;
|
|
|
|
@SuppressWarnings({"rawtypes", "deprecation"})
|
|
public final class ConacoTask<V> {
|
|
|
|
private static final String TAG = ConacoTask.class.getSimpleName();
|
|
|
|
private final int mId;
|
|
private final WeakReference<Unikery<V>> mUnikeryWeakReference;
|
|
private final String mKey;
|
|
private final String mUrl;
|
|
private final boolean mUseMemoryCache;
|
|
private final boolean mUseDiskCache;
|
|
private final boolean mUseNetwork;
|
|
private final ValueHelper<V> mHelper;
|
|
private final ValueCache<V> mCache;
|
|
private final OkHttpClient mOkHttpClient;
|
|
private final Executor mDiskExecutor;
|
|
private final Executor mNetworkExecutor;
|
|
private final Conaco<V> mConaco;
|
|
|
|
private DiskLoadTask mDiskLoadTask;
|
|
private NetworkLoadTask mNetworkLoadTask;
|
|
private Call mCall;
|
|
private boolean mStart;
|
|
private volatile boolean mStop;
|
|
|
|
private ConacoTask(Builder<V> builder) {
|
|
mId = builder.mId;
|
|
mUnikeryWeakReference = new WeakReference<>(builder.mUnikery);
|
|
mKey = builder.mKey;
|
|
mUrl = builder.mUrl;
|
|
mUseMemoryCache = builder.mUseMemoryCache;
|
|
mUseDiskCache = builder.mUseDiskCache;
|
|
mUseNetwork = builder.mUseNetwork;
|
|
mHelper = builder.mHelper;
|
|
mCache = builder.mCache;
|
|
mOkHttpClient = builder.mOkHttpClient;
|
|
mDiskExecutor = builder.mDiskExecutor;
|
|
mNetworkExecutor = builder.mNetworkExecutor;
|
|
mConaco = builder.mConaco;
|
|
}
|
|
|
|
int getId() {
|
|
return mId;
|
|
}
|
|
|
|
String getKey() {
|
|
return mKey;
|
|
}
|
|
|
|
boolean useMemoryCache() {
|
|
return mUseMemoryCache;
|
|
}
|
|
|
|
@Nullable
|
|
Unikery<V> getUnikery() {
|
|
return mUnikeryWeakReference.get();
|
|
}
|
|
|
|
void clearUnikery() {
|
|
mUnikeryWeakReference.clear();
|
|
}
|
|
|
|
private void onFinish() {
|
|
if (!mStop) {
|
|
mConaco.finishConacoTask(this);
|
|
}/* else {
|
|
// It is done by Conaco
|
|
}*/
|
|
}
|
|
|
|
@UiThread
|
|
void start() {
|
|
if (mStop || mStart) {
|
|
return;
|
|
}
|
|
|
|
mStart = true;
|
|
|
|
Unikery unikery = mUnikeryWeakReference.get();
|
|
if (unikery != null && unikery.getTaskId() == mId) {
|
|
if (mUseDiskCache) {
|
|
mDiskLoadTask = new DiskLoadTask();
|
|
mDiskLoadTask.executeOnExecutor(mDiskExecutor);
|
|
return;
|
|
} else if (mUseNetwork) {
|
|
unikery.onMiss(Conaco.SOURCE_DISK);
|
|
unikery.onRequest();
|
|
mNetworkLoadTask = new NetworkLoadTask();
|
|
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
|
return;
|
|
} else {
|
|
unikery.onMiss(Conaco.SOURCE_DISK);
|
|
unikery.onMiss(Conaco.SOURCE_NETWORK);
|
|
unikery.onFailure();
|
|
}
|
|
}
|
|
|
|
onFinish();
|
|
}
|
|
|
|
@UiThread
|
|
void stop() {
|
|
if (mStop) {
|
|
return;
|
|
}
|
|
|
|
mStop = true;
|
|
|
|
// Stop jobs
|
|
if (mDiskLoadTask != null) { // Getting from disk
|
|
mDiskLoadTask.cancel(false);
|
|
} else if (mNetworkLoadTask != null) { // Getting from network
|
|
mNetworkLoadTask.cancel(false);
|
|
if (mCall != null) {
|
|
mCall = null;
|
|
}
|
|
}
|
|
|
|
Unikery unikery = mUnikeryWeakReference.get();
|
|
if (unikery != null) {
|
|
unikery.onCancel();
|
|
}
|
|
|
|
// Conaco handle the clean up
|
|
}
|
|
|
|
private boolean isNotNecessary(AsyncTask asyncTask) {
|
|
Unikery unikery = mUnikeryWeakReference.get();
|
|
return mStop || asyncTask.isCancelled() || unikery == null || unikery.getTaskId() != mId;
|
|
}
|
|
|
|
@SuppressWarnings({"unused", "UnusedReturnValue", "RedundantSuppression"})
|
|
public static class Builder<T> {
|
|
|
|
private int mId;
|
|
private Unikery<T> mUnikery;
|
|
private String mKey;
|
|
private String mUrl;
|
|
private boolean mUseMemoryCache = true;
|
|
private boolean mUseDiskCache = true;
|
|
private boolean mUseNetwork = true;
|
|
private ValueHelper<T> mHelper;
|
|
private ValueCache<T> mCache;
|
|
private OkHttpClient mOkHttpClient;
|
|
private Executor mDiskExecutor;
|
|
private Executor mNetworkExecutor;
|
|
private Conaco<T> mConaco;
|
|
|
|
public Builder<T> setId(int id) {
|
|
mId = id;
|
|
return this;
|
|
}
|
|
|
|
public Unikery<T> getUnikery() {
|
|
return mUnikery;
|
|
}
|
|
|
|
public Builder<T> setUnikery(Unikery<T> unikery) {
|
|
mUnikery = unikery;
|
|
return this;
|
|
}
|
|
|
|
public String getKey() {
|
|
return mKey;
|
|
}
|
|
|
|
public Builder<T> setKey(String key) {
|
|
mKey = key;
|
|
return this;
|
|
}
|
|
|
|
public String getUrl() {
|
|
return mUrl;
|
|
}
|
|
|
|
public Builder<T> setUrl(String url) {
|
|
mUrl = url;
|
|
return this;
|
|
}
|
|
|
|
boolean isUseMemoryCache() {
|
|
return mUseMemoryCache;
|
|
}
|
|
|
|
public Builder<T> setUseMemoryCache(boolean useMemoryCache) {
|
|
mUseMemoryCache = useMemoryCache;
|
|
return this;
|
|
}
|
|
|
|
boolean isUseDiskCache() {
|
|
return mUseDiskCache;
|
|
}
|
|
|
|
public Builder<T> setUseDiskCache(boolean useDiskCache) {
|
|
mUseDiskCache = useDiskCache;
|
|
return this;
|
|
}
|
|
|
|
boolean isUseNetwork() {
|
|
return mUseNetwork;
|
|
}
|
|
|
|
public Builder<T> setUseNetwork(boolean useNetwork) {
|
|
mUseNetwork = useNetwork;
|
|
return this;
|
|
}
|
|
|
|
ValueHelper<T> getHelper() {
|
|
return mHelper;
|
|
}
|
|
|
|
Builder<T> setHelper(ValueHelper<T> helper) {
|
|
mHelper = helper;
|
|
return this;
|
|
}
|
|
|
|
Builder<T> setCache(ValueCache<T> cache) {
|
|
mCache = cache;
|
|
return this;
|
|
}
|
|
|
|
Builder<T> setOkHttpClient(OkHttpClient okHttpClient) {
|
|
mOkHttpClient = okHttpClient;
|
|
return this;
|
|
}
|
|
|
|
Builder<T> setDiskExecutor(Executor diskExecutor) {
|
|
mDiskExecutor = diskExecutor;
|
|
return this;
|
|
}
|
|
|
|
Builder<T> setNetworkExecutor(Executor networkExecutor) {
|
|
mNetworkExecutor = networkExecutor;
|
|
return this;
|
|
}
|
|
|
|
Builder<T> setConaco(Conaco<T> conaco) {
|
|
mConaco = conaco;
|
|
return this;
|
|
}
|
|
|
|
public void isValid() {
|
|
if (mUnikery == null) {
|
|
throw new IllegalStateException("Must set unikery");
|
|
}
|
|
if (mKey == null && mUrl == null) {
|
|
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
|
|
}
|
|
}
|
|
|
|
public ConacoTask<T> build() {
|
|
return new ConacoTask<>(this);
|
|
}
|
|
}
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
private class DiskLoadTask extends AsyncTask<Void, Void, V> {
|
|
|
|
@Override
|
|
protected V doInBackground(Void... params) {
|
|
if (isNotNecessary(this)) {
|
|
return null;
|
|
} else {
|
|
V value = null;
|
|
|
|
// Then check disk cache
|
|
if (mKey != null) {
|
|
if (value == null && mUseDiskCache) {
|
|
value = mCache.getFromDisk(mKey);
|
|
}
|
|
|
|
if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
|
mCache.putToMemory(mKey, value);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(V value) {
|
|
mDiskLoadTask = null;
|
|
|
|
if (isCancelled() || mStop) {
|
|
onCancelled(value);
|
|
} else {
|
|
Unikery<V> unikery = mUnikeryWeakReference.get();
|
|
if (unikery != null && unikery.getTaskId() == mId) {
|
|
boolean getValue = false;
|
|
if ((value == null || !(getValue = unikery.onGetValue(value, Conaco.SOURCE_DISK))) && mUseNetwork) {
|
|
unikery.onMiss(Conaco.SOURCE_DISK);
|
|
unikery.onRequest();
|
|
mNetworkLoadTask = new NetworkLoadTask();
|
|
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
|
return;
|
|
} else if (!getValue) {
|
|
unikery.onMiss(Conaco.SOURCE_DISK);
|
|
unikery.onFailure();
|
|
}
|
|
}
|
|
onFinish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onCancelled(V holder) {
|
|
onFinish();
|
|
}
|
|
}
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
private class NetworkLoadTask extends AsyncTask<Void, Long, V> implements ProgressNotifier {
|
|
|
|
@Override
|
|
public void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
|
if (!isNotNecessary(this)) {
|
|
publishProgress(singleReceivedSize, receivedSize, totalSize);
|
|
}
|
|
}
|
|
|
|
private boolean putToDiskCache(InputStream is, long length) {
|
|
SimpleDiskCache diskCache = mCache.getDiskCache();
|
|
if (diskCache == null) {
|
|
return false;
|
|
}
|
|
|
|
OutputStreamPipe pipe = diskCache.getOutputStreamPipe(mKey);
|
|
try {
|
|
pipe.obtain();
|
|
OutputStream os = pipe.open();
|
|
|
|
final byte[] buffer = new byte[1024 * 4];
|
|
long receivedSize = 0;
|
|
int bytesRead;
|
|
|
|
while ((bytesRead = is.read(buffer)) != -1) {
|
|
os.write(buffer, 0, bytesRead);
|
|
receivedSize += bytesRead;
|
|
notifyProgress(bytesRead, receivedSize, length);
|
|
}
|
|
|
|
return true;
|
|
} catch (IOException e) {
|
|
return false;
|
|
} finally {
|
|
pipe.close();
|
|
pipe.release();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected V doInBackground(Void... params) {
|
|
if (isNotNecessary(this)) {
|
|
return null;
|
|
}
|
|
|
|
V value;
|
|
InputStream is = null;
|
|
try {
|
|
Log.d("TAG", "Conaco " + mUrl);
|
|
|
|
// Load it from internet
|
|
Request request = new ChromeRequestBuilder(mUrl).build();
|
|
mCall = mOkHttpClient.newCall(request);
|
|
|
|
Response response = mCall.execute();
|
|
ResponseBody body = response.body();
|
|
if (body == null) {
|
|
return null;
|
|
}
|
|
is = body.byteStream();
|
|
|
|
if (isNotNecessary(this)) {
|
|
return null;
|
|
}
|
|
|
|
if (mKey != null) {
|
|
if (putToDiskCache(is, body.contentLength())) {
|
|
// Get object from disk cache
|
|
value = mCache.getFromDisk(mKey);
|
|
if (value == null) {
|
|
// Maybe bad download, remove it from disk cache
|
|
mCache.removeFromDisk(mKey);
|
|
} else if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
|
// Put it to memory
|
|
mCache.putToMemory(mKey, value);
|
|
}
|
|
return value;
|
|
} else {
|
|
// Maybe bad download, remove it from disk cache
|
|
mCache.removeFromDisk(mKey);
|
|
return null;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return null;
|
|
} finally {
|
|
mCall = null;
|
|
try {
|
|
if (is != null) {
|
|
is.close();
|
|
}
|
|
} catch (IOException e) {
|
|
// Ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(V holder) {
|
|
mNetworkLoadTask = null;
|
|
|
|
if (isCancelled() || mStop) {
|
|
onCancelled(holder);
|
|
} else {
|
|
Unikery<V> unikery = mUnikeryWeakReference.get();
|
|
if (unikery != null && unikery.getTaskId() == mId) {
|
|
if (holder == null || !unikery.onGetValue(holder, Conaco.SOURCE_NETWORK)) {
|
|
unikery.onFailure();
|
|
}
|
|
}
|
|
onFinish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onCancelled(V value) {
|
|
onFinish();
|
|
}
|
|
|
|
@Override
|
|
protected void onProgressUpdate(Long... values) {
|
|
Unikery unikery = mUnikeryWeakReference.get();
|
|
if (!mStop && !isCancelled() && unikery != null && unikery.getTaskId() == mId) {
|
|
unikery.onProgress(values[0], values[1], values[2]);
|
|
}
|
|
}
|
|
}
|
|
}
|