/* * 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 { private static final String TAG = ConacoTask.class.getSimpleName(); private final int mId; private final WeakReference> mUnikeryWeakReference; private final String mKey; private final String mUrl; private final boolean mUseMemoryCache; private final boolean mUseDiskCache; private final boolean mUseNetwork; private final ValueHelper mHelper; private final ValueCache mCache; private final OkHttpClient mOkHttpClient; private final Executor mDiskExecutor; private final Executor mNetworkExecutor; private final Conaco mConaco; private DiskLoadTask mDiskLoadTask; private NetworkLoadTask mNetworkLoadTask; private Call mCall; private boolean mStart; private volatile boolean mStop; private ConacoTask(Builder 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 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 { private int mId; private Unikery mUnikery; private String mKey; private String mUrl; private boolean mUseMemoryCache = true; private boolean mUseDiskCache = true; private boolean mUseNetwork = true; private ValueHelper mHelper; private ValueCache mCache; private OkHttpClient mOkHttpClient; private Executor mDiskExecutor; private Executor mNetworkExecutor; private Conaco mConaco; public Builder setId(int id) { mId = id; return this; } public Unikery getUnikery() { return mUnikery; } public Builder setUnikery(Unikery unikery) { mUnikery = unikery; return this; } public String getKey() { return mKey; } public Builder setKey(String key) { mKey = key; return this; } public String getUrl() { return mUrl; } public Builder setUrl(String url) { mUrl = url; return this; } boolean isUseMemoryCache() { return mUseMemoryCache; } public Builder setUseMemoryCache(boolean useMemoryCache) { mUseMemoryCache = useMemoryCache; return this; } boolean isUseDiskCache() { return mUseDiskCache; } public Builder setUseDiskCache(boolean useDiskCache) { mUseDiskCache = useDiskCache; return this; } boolean isUseNetwork() { return mUseNetwork; } public Builder setUseNetwork(boolean useNetwork) { mUseNetwork = useNetwork; return this; } ValueHelper getHelper() { return mHelper; } Builder setHelper(ValueHelper helper) { mHelper = helper; return this; } Builder setCache(ValueCache cache) { mCache = cache; return this; } Builder setOkHttpClient(OkHttpClient okHttpClient) { mOkHttpClient = okHttpClient; return this; } Builder setDiskExecutor(Executor diskExecutor) { mDiskExecutor = diskExecutor; return this; } Builder setNetworkExecutor(Executor networkExecutor) { mNetworkExecutor = networkExecutor; return this; } Builder setConaco(Conaco 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 build() { return new ConacoTask<>(this); } } @SuppressLint("StaticFieldLeak") private class DiskLoadTask extends AsyncTask { @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 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 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 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]); } } } }