Coroutinize Decoder
This commit is contained in:
1 parent
a6f1e91747
commit
e5ce3450e8
3 files changed
+70
-140
No files matched your search
@@ -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<SpiderQueen> 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<SpiderInfo> 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<Integer> 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<Integer> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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<Int, Job>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user