diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java index c099a1c29..f1858271d 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java @@ -45,9 +45,7 @@ import com.hippo.yorozuya.thread.PriorityThread; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; -import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -66,20 +64,17 @@ public final class SpiderQueen implements Runnable { public static final int STATE_DOWNLOADING = 1; public static final int STATE_FINISHED = 2; public static final int STATE_FAILED = 3; - public static final int DECODE_THREAD_NUM = 1; public static final String SPIDER_INFO_FILENAME = ".ehviewer"; private static final String TAG = SpiderQueen.class.getSimpleName(); private static final AtomicInteger sIdGenerator = new AtomicInteger(); private static final boolean DEBUG_LOG = true; private static final boolean DEBUG_PTOKEN = true; - private static final String[] URL_509_SUFFIX_ARRAY = { - "/509.gif", - "/509s.gif" - }; private static final LongSparseArray sQueenMap = new LongSparseArray<>(); public static String PTOKEN_FAILED_MESSAGE = GetText.getString(R.string.error_get_ptoken_error); public static String ERROR_509 = GetText.getString(R.string.error_509); public static String NETWORK_ERROR = GetText.getString(R.string.error_socket); + + public static String DECODE_ERROR = GetText.getString(R.string.error_decoding_failed); @NonNull public final SpiderDen mSpiderDen; public final AtomicReference mSpiderInfo = new AtomicReference<>(); @@ -90,9 +85,6 @@ public final class SpiderQueen implements Runnable { @NonNull private final GalleryInfo mGalleryInfo; private final Object mQueenLock = new Object(); - private final Thread[] mDecodeThreadArray = new Thread[DECODE_THREAD_NUM]; - private final int[] mDecodeIndexArray = new int[DECODE_THREAD_NUM]; - private final Queue mDecodeRequestQueue = new LinkedList<>(); private final Object mPageStateLock = new Object(); private final AtomicInteger mDownloadedPages = new AtomicInteger(0); private final AtomicInteger mFinishedPages = new AtomicInteger(0); @@ -106,20 +98,13 @@ public final class SpiderQueen implements Runnable { @Nullable private volatile Thread mQueenThread; private SpiderQueenWorker mWorkerScope; - private SpiderQueenDecoder mDecoderScope; private boolean mStoped = false; private SpiderQueen(@NonNull GalleryInfo galleryInfo) { mHttpClient = EhApplication.getOkHttpClient(); mGalleryInfo = galleryInfo; mSpiderDen = new SpiderDen(mGalleryInfo); - - for (int i = 0; i < DECODE_THREAD_NUM; i++) { - mDecodeIndexArray[i] = -1; - } - mWorkerScope = new SpiderQueenWorker(this); - mDecoderScope = new SpiderQueenDecoder(this); } @UiThread @@ -257,7 +242,7 @@ public final class SpiderQueen implements Runnable { } } - private void notifyGetImageSuccess(int index, Image image) { + public void notifyGetImageSuccess(int index, Image image) { synchronized (mSpiderListeners) { for (OnSpiderListener listener : mSpiderListeners) { listener.onGetImageSuccess(index, image); @@ -265,7 +250,7 @@ public final class SpiderQueen implements Runnable { } } - private void notifyGetImageFailure(int index, String error) { + public void notifyGetImageFailure(int index, String error) { if (error == null) { error = GetText.getString(R.string.error_unknown); } @@ -349,14 +334,8 @@ public final class SpiderQueen implements Runnable { synchronized (mQueenLock) { mQueenLock.notifyAll(); } - synchronized (mDecodeRequestQueue) { - mDecodeRequestQueue.notifyAll(); - } - CoroutineScopeKt.cancel(mWorkerScope, null); mWorkerScope = null; - CoroutineScopeKt.cancel(mDecoderScope, null); - mDecoderScope = null; } public int size() { @@ -401,9 +380,6 @@ public final class SpiderQueen implements Runnable { } mWorkerScope.cancel(index); - synchronized (mDecodeRequestQueue) { - mDecodeRequestQueue.remove(index); - } } public void preloadPages(@NonNull List pages) { @@ -440,7 +416,7 @@ public final class SpiderQueen implements Runnable { Object result; switch (state) { - case STATE_NONE -> result = null; + case STATE_NONE, STATE_FINISHED -> result = null; case STATE_DOWNLOADING -> result = mPagePercentMap.get(index); case STATE_FAILED -> { String error = mPageErrorMap.get(index); @@ -449,15 +425,6 @@ public final class SpiderQueen implements Runnable { } result = error; } - case STATE_FINISHED -> { - synchronized (mDecodeRequestQueue) { - if (!contain(mDecodeIndexArray, index) && !mDecodeRequestQueue.contains(index)) { - mDecodeRequestQueue.add(index); - mDecodeRequestQueue.notifyAll(); - } - } - result = null; - } default -> throw new IllegalStateException("Unexpected value: " + state); } @@ -689,14 +656,6 @@ public final class SpiderQueen implements Runnable { if (shouldIntoDownload) mWorkerScope.enterDownloadMode(); - // Start decoder - for (int i = 0; i < DECODE_THREAD_NUM; i++) { - Thread decoderThread = new PriorityThread(new SpiderDecoder(i), - "SpiderDecoder-" + i, Process.THREAD_PRIORITY_DEFAULT); - mDecodeThreadArray[i] = decoderThread; - decoderThread.start(); - } - // Wait finish synchronized (mQueenLock) { try { @@ -809,82 +768,4 @@ public final class SpiderQueen implements Runnable { void onGetImageFailure(int index, String error); } - - private class SpiderDecoder implements Runnable { - - private final int mThreadIndex; - - public SpiderDecoder(int index) { - mThreadIndex = index; - } - - private void resetDecodeIndex() { - synchronized (mDecodeRequestQueue) { - mDecodeIndexArray[mThreadIndex] = -1; - } - } - - @Override - public void run() { - if (DEBUG_LOG) { - Log.i(TAG, Thread.currentThread().getName() + ": start"); - } - - while (!mStoped) { - int index; - synchronized (mDecodeRequestQueue) { - if (mStoped) break; - if (mDecodeRequestQueue.isEmpty()) { - try { - mDecodeRequestQueue.wait(); - } catch (InterruptedException e) { - // Interrupted - break; - } - if (mStoped) break; - continue; - } - index = mDecodeRequestQueue.remove(); - mDecodeIndexArray[mThreadIndex] = index; - } - - // Check index valid - if (index < 0 || index >= mPageStateArray.length) { - resetDecodeIndex(); - notifyGetImageFailure(index, GetText.getString(R.string.error_out_of_range)); - continue; - } - - Image.CloseableSource src = mSpiderDen.getImageSource(index); - if (src == null) { - resetDecodeIndex(); - // Can't find the file, it might be removed from cache, - // Reset it state and request it - updatePageState(index, STATE_NONE, null); - request(index, false, false); - continue; - } - - Image image = Image.decode(src); - String error = null; - - if (image == null) { - error = GetText.getString(R.string.error_decoding_failed); - } - - // Notify - if (image != null) { - notifyGetImageSuccess(index, image); - } else { - notifyGetImageFailure(index, error); - } - - resetDecodeIndex(); - } - - if (DEBUG_LOG) { - Log.i(TAG, Thread.currentThread().getName() + ": end"); - } - } - } } diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueenDecoder.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueenDecoder.kt deleted file mode 100644 index af137b0e7..000000000 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueenDecoder.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.hippo.ehviewer.spider - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlin.coroutines.CoroutineContext - -class SpiderQueenDecoder(private val queen: SpiderQueen) : CoroutineScope { - override val coroutineContext: CoroutineContext - get() = Dispatchers.IO.limitedParallelism(2) + SupervisorJob() -} diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueenWorker.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueenWorker.kt index a88a6a4f4..77abb3956 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueenWorker.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueenWorker.kt @@ -6,17 +6,21 @@ import com.hippo.ehviewer.client.EhEngine.getGalleryPage import com.hippo.ehviewer.client.EhEngine.getGalleryPageApi import com.hippo.ehviewer.client.EhUrl.getPageUrl import com.hippo.ehviewer.client.exception.ParseException +import com.hippo.ehviewer.spider.SpiderQueen.DECODE_ERROR import com.hippo.ehviewer.spider.SpiderQueen.ERROR_509 import com.hippo.ehviewer.spider.SpiderQueen.NETWORK_ERROR import com.hippo.ehviewer.spider.SpiderQueen.PTOKEN_FAILED_MESSAGE import com.hippo.ehviewer.spider.SpiderQueen.STATE_FAILED import com.hippo.ehviewer.spider.SpiderQueen.STATE_FINISHED +import com.hippo.image.Image.Companion.decode import com.hippo.util.ExceptionUtils import com.hippo.yorozuya.StringUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Semaphore @@ -42,6 +46,7 @@ class SpiderQueenWorker(private val queen: SpiderQueen) : CoroutineScope { get() = Dispatchers.IO + Job() fun cancel(index: Int) { + decoder.cancel(index) if (isDownloadMode) return synchronized(mJobMap) { mJobMap.remove(index)?.cancel() @@ -68,19 +73,21 @@ class SpiderQueenWorker(private val queen: SpiderQueen) : CoroutineScope { }.toSet().forEach { mJobMap.remove(it) } list.forEach { if (mJobMap[it]?.isActive != true) - launch(it) + launch(it, false, downloadOnly = true) } } } @JvmOverloads - fun launch(index: Int, force: Boolean = false) { - if (isDownloadMode) return + fun launch(index: Int, force: Boolean = false, downloadOnly: Boolean = false) { check(index in 0 until size) val state = queen.mPageStateArray[index] - if (!force && state == STATE_FINISHED) return + if (!force && state == STATE_FINISHED) { + decoder.launch(index) + return + } - synchronized(mJobMap) { + if (!downloadOnly) synchronized(mJobMap) { val currentJob = mJobMap[index] if (force) currentJob?.cancel() if (currentJob?.isActive != true) { @@ -91,6 +98,8 @@ class SpiderQueenWorker(private val queen: SpiderQueen) : CoroutineScope { } } } + + decoder.launch(index) } private suspend fun doInJob(index: Int, force: Boolean) { @@ -287,4 +296,55 @@ class SpiderQueenWorker(private val queen: SpiderQueen) : CoroutineScope { private const val DEBUG_TAG = "SpiderQueenWorker" } + + private val decoder = Decoder() + + inner class Decoder { + private val mSemaphore = Semaphore(2) + private val mDecodeJobMap = hashMapOf() + private val size + get() = queen.size() + + fun cancel(index: Int) { + synchronized(mDecodeJobMap) { + mDecodeJobMap.remove(index)?.cancel() + } + } + + fun launch(index: Int) { + check(index in 0 until size) + synchronized(mDecodeJobMap) { + val currentJob = mDecodeJobMap[index] + if (currentJob?.isActive != true) { + mDecodeJobMap[index] = launch { + mJobMap[index]?.takeIf { it.isActive }?.join() + mSemaphore.withPermit { + doInJob(index) + } + } + } + } + } + + private suspend fun doInJob(index: Int) { + val src = spiderDen.getImageSource(index) ?: return + + val image = decode(src) + runCatching { + currentCoroutineContext().ensureActive() + }.onFailure { + image?.recycle() + throw it + } + var error: String? = null + + if (image == null) { + error = DECODE_ERROR + } + + // Notify + image?.let { queen.notifyGetImageSuccess(index, it) } + ?: queen.notifyGetImageFailure(index, error) + } + } }