From d52ec329a4342344ac76fc870b1cb80e86bf41a5 Mon Sep 17 00:00:00 2001 From: NekoInverter <7741672-NekoInverter@users.noreply.gitlab.com> Date: Sun, 24 Jan 2021 19:50:26 +0800 Subject: [PATCH] Fix thumb load --- app/build.gradle | 1 - .../main/java/com/hippo/conaco/Conaco.java | 227 +++++++ .../java/com/hippo/conaco/ConacoTask.java | 588 ++++++++++++++++++ .../java/com/hippo/conaco/DataContainer.java | 52 ++ .../com/hippo/conaco/ProgressNotifier.java | 22 + .../main/java/com/hippo/conaco/Register.java | 72 +++ .../main/java/com/hippo/conaco/Unikery.java | 77 +++ .../java/com/hippo/conaco/ValueCache.java | 61 ++ .../java/com/hippo/conaco/ValueHelper.java | 55 ++ .../com/hippo/ehviewer/client/EhEngine.java | 6 +- 10 files changed, 1158 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/hippo/conaco/Conaco.java create mode 100644 app/src/main/java/com/hippo/conaco/ConacoTask.java create mode 100644 app/src/main/java/com/hippo/conaco/DataContainer.java create mode 100644 app/src/main/java/com/hippo/conaco/ProgressNotifier.java create mode 100644 app/src/main/java/com/hippo/conaco/Register.java create mode 100644 app/src/main/java/com/hippo/conaco/Unikery.java create mode 100644 app/src/main/java/com/hippo/conaco/ValueCache.java create mode 100644 app/src/main/java/com/hippo/conaco/ValueHelper.java diff --git a/app/build.gradle b/app/build.gradle index bcfe97202..c98bc48be 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -101,7 +101,6 @@ dependencies { implementation 'com.github.seven332.a7zip:extract-lite:1b21783' implementation 'com.github.seven332:android-resource:0.1.0' implementation 'com.github.seven332:beerbelly:0.1.4' - implementation 'com.github.seven332:conaco:0.1.5-eh' implementation 'com.github.seven332:glgallery:0.1.2' implementation 'com.github.seven332:glview:0.1.0' implementation 'com.github.seven332:glview-image:0.1.0' diff --git a/app/src/main/java/com/hippo/conaco/Conaco.java b/app/src/main/java/com/hippo/conaco/Conaco.java new file mode 100644 index 000000000..19bf047f7 --- /dev/null +++ b/app/src/main/java/com/hippo/conaco/Conaco.java @@ -0,0 +1,227 @@ +/* + * 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.os.Process; +import android.util.Log; + +import androidx.annotation.IntDef; +import androidx.annotation.UiThread; + +import com.hippo.beerbelly.BeerBelly; +import com.hippo.yorozuya.thread.PriorityThreadFactory; +import com.hippo.yorozuya.thread.SerialThreadExecutor; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.LinkedList; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import okhttp3.OkHttpClient; + +@SuppressWarnings("rawtypes") +public final class Conaco { + + private static final String TAG = Conaco.class.getSimpleName(); + + @IntDef({SOURCE_MEMORY, SOURCE_DISK, SOURCE_NETWORK}) + @Retention(RetentionPolicy.SOURCE) + public @interface Source { + } + + public static final int SOURCE_MEMORY = 0; + public static final int SOURCE_DISK = 1; + public static final int SOURCE_NETWORK = 2; + + private final ValueHelper mHelper; + private final ValueCache mCache; + private final OkHttpClient mOkHttpClient; + private final Register mRegister; + + private final SerialThreadExecutor mDiskExecutor; + private final ThreadPoolExecutor mNetworkExecutor; + private final AtomicInteger mIdGenerator; + + private final boolean mDebug; + + private Conaco(Builder builder) { + mHelper = builder.objectHelper; + + BeerBelly.BeerBellyParams beerBellyParams = new BeerBelly.BeerBellyParams(); + beerBellyParams.hasMemoryCache = builder.hasMemoryCache; + beerBellyParams.memoryCacheMaxSize = builder.memoryCacheMaxSize; + beerBellyParams.hasDiskCache = builder.hasDiskCache; + beerBellyParams.diskCacheDir = builder.diskCacheDir; + beerBellyParams.diskCacheMaxSize = builder.diskCacheMaxSize; + + mCache = new ValueCache<>(beerBellyParams, mHelper); + mOkHttpClient = builder.okHttpClient; + + mRegister = new Register<>(); + + mDiskExecutor = new SerialThreadExecutor(3000L, new LinkedList<>(), + new PriorityThreadFactory("Conaco-Disk", Process.THREAD_PRIORITY_BACKGROUND)); + mNetworkExecutor = new ThreadPoolExecutor(3, 3, 5L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new PriorityThreadFactory("Conaco-Network", Process.THREAD_PRIORITY_BACKGROUND)); + + mIdGenerator = new AtomicInteger(); + + mDebug = builder.debug; + } + + public BeerBelly getBeerBelly() { + return mCache; + } + + private void onUnregisterConacoTask(ConacoTask task) { + ConacoTask next = mRegister.getByKey(task.getKey()); + if (next != null) { + startConacoTask(next); + } + } + + private void startConacoTask(ConacoTask task) { + Unikery unikery = task.getUnikery(); + + // Can't unikery is gone, finish the task now + if (unikery == null) { + finishConacoTask(task); + return; + } + + String key = task.getKey(); + V value = null; + + // Get from memory + if (key != null && task.useMemoryCache() && mHelper.useMemoryCache(key, null)) { + value = mCache.getFromMemory(key); + } + + if (value == null || !unikery.onGetValue(value, SOURCE_MEMORY)) { + unikery.onMiss(SOURCE_MEMORY); + task.start(); + } else { + // Get the object, finish the task + finishConacoTask(task); + } + } + + @UiThread + public void load(ConacoTask.Builder builder) { + builder.isValid(); + + if (mDebug) { + Log.d(TAG, "Key " + builder.getKey()); + Log.d(TAG, "Url " + builder.getUrl()); + } + + Unikery unikery = builder.getUnikery(); + + // Cancel first + cancel(unikery); + + // Build conaco task + int id; + //noinspection StatementWithEmptyBody + while ((id = mIdGenerator.getAndIncrement()) == Unikery.INVALID_ID) ; + unikery.setTaskId(id); + builder.setId(id) + .setHelper(mHelper) + .setCache(mCache) + .setOkHttpClient(mOkHttpClient) + .setDiskExecutor(mDiskExecutor) + .setNetworkExecutor(mNetworkExecutor) + .setConaco(this); + ConacoTask task = builder.build(); + + if (!mRegister.register(id, task)) { + startConacoTask(task); + } else { + // The key is repeated, wait + unikery.onWait(); + } + } + + @UiThread + public void cancel(Unikery unikery) { + int id = unikery.getTaskId(); + if (id != Unikery.INVALID_ID) { + unikery.setTaskId(Unikery.INVALID_ID); + ConacoTask task = mRegister.unregister(id); + if (task != null) { + task.stop(); + // Don't need unikery anymore + task.clearUnikery(); + // Callback + onUnregisterConacoTask(task); + } else { + Log.e(TAG, "Can't find conaco task by id " + id); + } + } + } + + public boolean isLoading(Unikery unikery) { + int id = unikery.getTaskId(); + return id != Unikery.INVALID_ID && mRegister.contain(id); + } + + /** + * Unregister task, reset unikery id, clear unikery in task, call next + */ + void finishConacoTask(ConacoTask task) { + mRegister.unregister(task.getId()); + Unikery unikery = task.getUnikery(); + if (unikery != null) { + unikery.setTaskId(Unikery.INVALID_ID); + } + task.clearUnikery(); + + onUnregisterConacoTask(task); + } + + public static class Builder extends BeerBelly.BeerBellyParams { + /** + * The client to get image from internet + */ + public OkHttpClient okHttpClient = null; + + /** + * Decode, get size and others + */ + public ValueHelper objectHelper = null; + + public boolean debug = false; + + @Override + public void isValid() throws IllegalStateException { + super.isValid(); + + if (okHttpClient == null) { + throw new IllegalStateException("No http client? How can I load image via url?"); + } + } + + public Conaco build() { + isValid(); + return new Conaco<>(this); + } + } +} diff --git a/app/src/main/java/com/hippo/conaco/ConacoTask.java b/app/src/main/java/com/hippo/conaco/ConacoTask.java new file mode 100644 index 000000000..285224a0c --- /dev/null +++ b/app/src/main/java/com/hippo/conaco/ConacoTask.java @@ -0,0 +1,588 @@ +/* + * 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.InputStreamPipe; +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.HttpUrl; +import okhttp3.MediaType; +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 DataContainer mDataContainer; + 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; + mDataContainer = builder.mDataContainer; + 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.cancel(); + 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; + } + + private static void putFromDiskCacheToDataContainer(String key, ValueCache cache, DataContainer container) { + SimpleDiskCache diskCache = cache.getDiskCache(); + if (diskCache != null) { + InputStreamPipe pipe = diskCache.getInputStreamPipe(key); + if (pipe != null) { + try { + pipe.obtain(); + container.save(pipe.open(), -1L, null, null); + } catch (IOException e) { + Log.d(TAG, "Can't save value from disk cache to data container"); + e.printStackTrace(); + container.remove(); + } finally { + pipe.close(); + pipe.release(); + } + } + } + } + + private static void putFromDataContainerToDiskCache(String key, ValueCache cache, DataContainer container) { + InputStreamPipe pipe = container.get(); + if (pipe != null) { + try { + pipe.obtain(); + cache.putRawToDisk(key, pipe.open()); + } catch (IOException e) { + Log.w(TAG, "Can't save value from data container to disk cache", e); + cache.removeFromDisk(key); + } finally { + pipe.close(); + pipe.release(); + } + } + } + + @SuppressLint("StaticFieldLeak") + private class DiskLoadTask extends AsyncTask { + + @Override + protected V doInBackground(Void... params) { + if (isNotNecessary(this)) { + return null; + } else { + V value = null; + + // First check data container + if (mDataContainer != null && mDataContainer.isEnabled()) { + InputStreamPipe isp = mDataContainer.get(); + if (isp != null) { + value = mHelper.decode(isp); + } + } + + // Then check disk cache + if (mKey != null) { + if (value == null && mUseDiskCache) { + value = mCache.getFromDisk(mKey); + // Put back to data container + if (value != null && mDataContainer != null && mDataContainer.isEnabled()) { + putFromDiskCacheToDataContainer(mKey, mCache, mDataContainer); + } + } + + if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) { + // Put it to memory + 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(); + } + } + + private boolean putToDataContainer(InputStream is, ResponseBody body) { + // Get media type + String mediaType; + MediaType mt = body.contentType(); + if (mt != null) { + mediaType = mt.type() + '/' + mt.subtype(); + } else { + mediaType = null; + } + return mDataContainer.save(is, body.contentLength(), mediaType, this); + } + + @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 ((mDataContainer == null || !mDataContainer.isEnabled()) && 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 if (mDataContainer != null && mDataContainer.isEnabled()) { + // Check url Moved + HttpUrl requestHttpUrl = request.url(); + HttpUrl responseHttpUrl = response.request().url(); + if (!responseHttpUrl.equals(requestHttpUrl)) { + mDataContainer.onUrlMoved(mUrl, responseHttpUrl.url().toString()); + } + + // Put to data container + if (!putToDataContainer(is, body)) { + return null; + } + + // Get value from data container + InputStreamPipe isp = mDataContainer.get(); + if (isp == null) { + return null; + } + value = mHelper.decode(isp); + if (value == null) { + mDataContainer.remove(); + } else if (mKey != null) { + // Put to disk cache + putFromDataContainerToDiskCache(mKey, mCache, mDataContainer); + + if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) { + // Put it to memory + mCache.putToMemory(mKey, value); + } + } + return value; + } 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]); + } + } + } + + @SuppressWarnings({"unused", "UnusedReturnValue", "RedundantSuppression"}) + public static class Builder { + + private int mId; + private Unikery mUnikery; + private String mKey; + private String mUrl; + private DataContainer mDataContainer; + 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 Builder setUnikery(Unikery unikery) { + mUnikery = unikery; + return this; + } + + public Unikery getUnikery() { + return mUnikery; + } + + public Builder setKey(String key) { + mKey = key; + return this; + } + + public String getKey() { + return mKey; + } + + public Builder setUrl(String url) { + mUrl = url; + return this; + } + + public String getUrl() { + return mUrl; + } + + public Builder setDataContainer(DataContainer dataContainer) { + mDataContainer = dataContainer; + return this; + } + + public Builder setUseMemoryCache(boolean useMemoryCache) { + mUseMemoryCache = useMemoryCache; + return this; + } + + boolean isUseMemoryCache() { + return mUseMemoryCache; + } + + public Builder setUseDiskCache(boolean useDiskCache) { + mUseDiskCache = useDiskCache; + return this; + } + + boolean isUseDiskCache() { + return mUseDiskCache; + } + + public Builder setUseNetwork(boolean useNetwork) { + mUseNetwork = useNetwork; + return this; + } + + boolean isUseNetwork() { + return mUseNetwork; + } + + Builder setHelper(ValueHelper helper) { + mHelper = helper; + return this; + } + + ValueHelper getHelper() { + return mHelper; + } + + 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 && mDataContainer == 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); + } + } +} diff --git a/app/src/main/java/com/hippo/conaco/DataContainer.java b/app/src/main/java/com/hippo/conaco/DataContainer.java new file mode 100644 index 000000000..3513111d5 --- /dev/null +++ b/app/src/main/java/com/hippo/conaco/DataContainer.java @@ -0,0 +1,52 @@ +/* + * 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 androidx.annotation.Nullable; + +import com.hippo.streampipe.InputStreamPipe; + +import java.io.InputStream; + +public interface DataContainer { + + /** + * Returns the enabled status for this DataContainer. + */ + boolean isEnabled(); + + /** + * Get 304 or something like that + */ + void onUrlMoved(String requestUrl, String responseUrl); + + /** + * Save the {@code InputStream} + */ + boolean save(InputStream is, long length, @Nullable String mediaType, @Nullable ProgressNotifier notify); + + /** + * Get {@code InputStreamPipe} for saved before + */ + @Nullable + InputStreamPipe get(); + + /** + * Remove saved stuff + */ + void remove(); +} diff --git a/app/src/main/java/com/hippo/conaco/ProgressNotifier.java b/app/src/main/java/com/hippo/conaco/ProgressNotifier.java new file mode 100644 index 000000000..ff4af1648 --- /dev/null +++ b/app/src/main/java/com/hippo/conaco/ProgressNotifier.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015 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; + +public interface ProgressNotifier { + + void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize); +} diff --git a/app/src/main/java/com/hippo/conaco/Register.java b/app/src/main/java/com/hippo/conaco/Register.java new file mode 100644 index 000000000..d6c983951 --- /dev/null +++ b/app/src/main/java/com/hippo/conaco/Register.java @@ -0,0 +1,72 @@ +/* + * Copyright 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 com.hippo.yorozuya.collect.Multimap; +import com.hippo.yorozuya.collect.SparseILArray; + +import java.util.List; + +class Register { + + private final SparseILArray> mIdMap = new SparseILArray<>(); + private final Multimap> mKeyMap = Multimap.create(); + + /** + * @return true for the key is already registered + */ + public synchronized boolean register(int id, ConacoTask task) { + boolean repeatedKey = false; + String taskKey = task.getKey(); + + // For no-key task, there no need to check + if (taskKey != null) { + repeatedKey = mKeyMap.countElements(taskKey) != 0; + } + + // Append task + mKeyMap.putElement(taskKey, task); + mIdMap.append(id, task); + + return repeatedKey; + } + + public synchronized ConacoTask unregister(int id) { + ConacoTask task = mIdMap.remove(id); + if (task != null) { + mKeyMap.removeElement(task.getKey(), task); + } + return task; + } + + public synchronized boolean contain(int id) { + return mIdMap.indexOfKey(id) >= 0; + } + + public synchronized ConacoTask getByKey(String key) { + if (key == null) { + return null; + } + + List> list = mKeyMap.get(key); + if (list != null && list.size() > 0) { + return list.get(0); + } else { + return null; + } + } +} diff --git a/app/src/main/java/com/hippo/conaco/Unikery.java b/app/src/main/java/com/hippo/conaco/Unikery.java new file mode 100644 index 000000000..07c0ef4fe --- /dev/null +++ b/app/src/main/java/com/hippo/conaco/Unikery.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015 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 androidx.annotation.NonNull; + +public interface Unikery { + + int INVALID_ID = -1; + + /** + * Store the conaco task id + * + * @param id the conaco task id + */ + void setTaskId(int id); + + /** + * Get the conaco task id + * + * @return the conaco task id + */ + int getTaskId(); + + /** + * On miss in the source + */ + void onMiss(@Conaco.Source int source); + + /** + * On start http request + */ + void onRequest(); + + /** + * On http request progress + */ + void onProgress(long singleReceivedSize, long receivedSize, long totalSize); + + /** + * On start to wait because repeated key + */ + void onWait(); + + /** + * On get the value + * + * @return {@code true} for the {@code Unikery} accepts the value + * and the task ends. {@code false} for the {@code Unikery} + * reject the value and the task goes on or failed + */ + boolean onGetValue(@NonNull V value, @Conaco.Source int source); + + /** + * On failed to get value + */ + void onFailure(); + + /** + * On user cancel the conaco task + */ + void onCancel(); +} diff --git a/app/src/main/java/com/hippo/conaco/ValueCache.java b/app/src/main/java/com/hippo/conaco/ValueCache.java new file mode 100644 index 000000000..d9b75d5b1 --- /dev/null +++ b/app/src/main/java/com/hippo/conaco/ValueCache.java @@ -0,0 +1,61 @@ +/* + * Copyright 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 androidx.annotation.NonNull; + +import com.hippo.beerbelly.BeerBelly; +import com.hippo.streampipe.InputStreamPipe; + +import java.io.OutputStream; + +class ValueCache extends BeerBelly { + + private final ValueHelper mHelper; + + public ValueCache(BeerBelly.BeerBellyParams params, ValueHelper helper) { + super(params); + mHelper = helper; + } + + @Override + protected int sizeOf(String key, V value) { + return mHelper.sizeOf(key, value); + } + + @Override + protected void memoryEntryAdded(V value) { + mHelper.onAddToMemoryCache(value); + } + + @Override + protected void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue) { + if (oldValue != null) { + mHelper.onRemoveFromMemoryCache(key, oldValue); + } + } + + @Override + protected V read(@NonNull InputStreamPipe isPipe) { + return mHelper.decode(isPipe); + } + + @Override + protected boolean write(OutputStream os, V value) { + throw new UnsupportedOperationException("Not support write object"); + } +} diff --git a/app/src/main/java/com/hippo/conaco/ValueHelper.java b/app/src/main/java/com/hippo/conaco/ValueHelper.java new file mode 100644 index 000000000..f2f39d323 --- /dev/null +++ b/app/src/main/java/com/hippo/conaco/ValueHelper.java @@ -0,0 +1,55 @@ +/* + * Copyright 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 androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.hippo.streampipe.InputStreamPipe; + +public interface ValueHelper { + + /** + * Decode object for the {@code InputStreamPipe} + * + * @param isPipe the {@code InputStreamPipe} + * @return the decoded object + */ + @Nullable + V decode(@NonNull InputStreamPipe isPipe); + + /** + * Get the size of the object + */ + int sizeOf(@NonNull String key, @NonNull V value); + + /** + * Called when the object added to memory cache + */ + void onAddToMemoryCache(@NonNull V oldValue); + + /** + * Called when the object removed from memory cache + */ + void onRemoveFromMemoryCache(@NonNull String key, @NonNull V oldValue); + + /** + * Use cache memory or not. Sometimes large object + * should not be stored in memory cache. + */ + boolean useMemoryCache(@NonNull String key, V holder); +} diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java index 4400ce2f4..9a3ad1280 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -244,8 +244,10 @@ public class EhEngine { } } - for (GalleryInfo info : list) { - info.thumb = EhUrl.getFixedPreviewThumbUrl(info.thumb); + if (Settings.getFixThumbUrl()) { + for (GalleryInfo info : list) { + info.thumb = EhUrl.getFixedPreviewThumbUrl(info.thumb); + } } }