Coroutinize EhClient (#622)

* Rename .java to .kt

* Coroutinize EhClient to improve performance and clarity

* Nullability Compatibility

* Refine control flow

* Fix nullability
This commit is contained in:
tarsin authored and GitHub committed 2023-01-04 11:51:31 +08:00
1 parent 3000b36dbd
commit 9f79e2505c
2 files changed
+260 -202

No files matched your search

@@ -1,202 +0,0 @@
/*
* 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.ehviewer.client;
import android.os.AsyncTask;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.client.exception.CancelledException;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.yorozuya.SimpleHandler;
import java.io.File;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.Call;
import okhttp3.OkHttpClient;
public class EhClient {
public static final String TAG = EhClient.class.getSimpleName();
public static final int METHOD_SIGN_IN = 0;
public static final int METHOD_GET_GALLERY_LIST = 1;
public static final int METHOD_GET_GALLERY_DETAIL = 3;
public static final int METHOD_GET_PREVIEW_SET = 4;
public static final int METHOD_GET_RATE_GALLERY = 5;
public static final int METHOD_GET_COMMENT_GALLERY = 6;
public static final int METHOD_GET_GALLERY_TOKEN = 7;
public static final int METHOD_GET_FAVORITES = 8;
public static final int METHOD_ADD_FAVORITES = 9;
public static final int METHOD_ADD_FAVORITES_RANGE = 10;
public static final int METHOD_MODIFY_FAVORITES = 11;
public static final int METHOD_GET_TORRENT_LIST = 12;
public static final int METHOD_GET_PROFILE = 14;
public static final int METHOD_VOTE_COMMENT = 15;
public static final int METHOD_IMAGE_SEARCH = 16;
public static final int METHOD_ARCHIVE_LIST = 17;
public static final int METHOD_DOWNLOAD_ARCHIVE = 18;
public static final int METHOD_VOTE_TAG = 19;
public static final int METHOD_GET_UCONFIG = 20;
private static final OkHttpClient mOkHttpClient = EhApplication.getOkHttpClient();
private final ThreadPoolExecutor mRequestThreadPool;
public EhClient() {
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
}
@SuppressWarnings("unchecked")
public void execute(EhRequest request) {
if (!request.isCancelled()) {
Task task = new Task(request.getMethod(), request.getCallback());
task.executeOnExecutor(mRequestThreadPool, request.getArgs());
request.task = task;
} else {
request.getCallback().onCancel();
}
}
public interface Callback<E> {
void onSuccess(E result);
void onFailure(Exception e);
void onCancel();
}
@SuppressWarnings("deprecation")
public static class Task extends AsyncTask<Object, Void, Object> {
private final int mMethod;
private final AtomicReference<Call> mCall = new AtomicReference<>();
private final AtomicBoolean mStop = new AtomicBoolean();
private Callback<Object> mCallback;
public Task(int method, Callback<Object> callback) {
mMethod = method;
mCallback = callback;
}
// Called in Job thread
public void setCall(Call call) throws CancelledException {
if (mStop.get()) {
// Stopped Job thread
throw new CancelledException();
} else {
mCall.lazySet(call);
}
}
public void stop() {
if (!mStop.get()) {
mStop.lazySet(true);
if (mCallback != null) {
// TODO Avoid new runnable
SimpleHandler.getInstance().post(mCallback::onCancel);
}
Status status = getStatus();
if (status == Status.PENDING) {
cancel(false);
} else if (status == Status.RUNNING) {
// It is running, cancel call if it is created
Call call = mCall.get();
if (call != null) {
call.cancel();
}
}
// Clear
mCallback = null;
mCall.lazySet(null);
}
}
@Override
protected Object doInBackground(Object... params) {
try {
return switch (mMethod) {
case METHOD_SIGN_IN ->
EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
case METHOD_GET_GALLERY_LIST ->
EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_GALLERY_DETAIL ->
EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_PREVIEW_SET ->
EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_RATE_GALLERY ->
EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
case METHOD_GET_COMMENT_GALLERY ->
EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
case METHOD_GET_GALLERY_TOKEN ->
EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
case METHOD_GET_FAVORITES ->
EhEngine.getFavorites(this, mOkHttpClient, (String) params[0]);
case METHOD_ADD_FAVORITES ->
EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
case METHOD_ADD_FAVORITES_RANGE ->
EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
case METHOD_MODIFY_FAVORITES ->
EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2]);
case METHOD_GET_TORRENT_LIST ->
EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_GET_PROFILE -> EhEngine.getProfile(this, mOkHttpClient);
case METHOD_VOTE_COMMENT ->
EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
case METHOD_IMAGE_SEARCH ->
EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
case METHOD_ARCHIVE_LIST ->
EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_DOWNLOAD_ARCHIVE ->
EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3], (Boolean) params[4]);
case METHOD_VOTE_TAG ->
EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
case METHOD_GET_UCONFIG -> EhEngine.getUConfig(this, mOkHttpClient);
default -> new IllegalStateException("Can't detect method " + mMethod);
};
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
return e;
}
}
@Override
protected void onPostExecute(Object result) {
if (mCallback != null) {
//noinspection StatementWithEmptyBody
if (!(result instanceof CancelledException)) {
if (result instanceof Exception) {
mCallback.onFailure((Exception) result);
} else {
mCallback.onSuccess(result);
}
} else {
// onCancel is called in stop
}
}
// Clear
mCallback = null;
mCall.lazySet(null);
}
}
}
@@ -0,0 +1,260 @@
/*
* 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.ehviewer.client
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
import com.hippo.ehviewer.client.exception.CancelledException
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import okhttp3.Call
import java.io.File
import java.util.concurrent.atomic.AtomicReference
class EhClient {
fun execute(request: EhRequest) {
if (!request.isCancelled) {
val task = Task()
val job = scope.launch(Dispatchers.IO, CoroutineStart.LAZY) {
val method = request.method
val params = request.args
val callback: Callback<Any?>? = request.callback
try {
val result: Any? = when (method) {
METHOD_SIGN_IN -> EhEngine.signIn(
task,
mOkHttpClient,
params[0] as String?,
params[1] as String?
)
METHOD_GET_GALLERY_LIST -> EhEngine.getGalleryList(
task,
mOkHttpClient,
params[0] as String?
)
METHOD_GET_GALLERY_DETAIL -> EhEngine.getGalleryDetail(
task,
mOkHttpClient,
params[0] as String?
)
METHOD_GET_PREVIEW_SET -> EhEngine.getPreviewSet(
task,
mOkHttpClient,
params[0] as String?
)
METHOD_GET_RATE_GALLERY -> EhEngine.rateGallery(
task,
mOkHttpClient,
(params[0] as Long),
params[1] as String?,
(params[2] as Long),
params[3] as String?,
(params[4] as Float)
)
METHOD_GET_COMMENT_GALLERY -> EhEngine.commentGallery(
task,
mOkHttpClient,
params[0] as String?,
params[1] as String?,
params[2] as String?
)
METHOD_GET_GALLERY_TOKEN -> EhEngine.getGalleryToken(
task,
mOkHttpClient,
(params[0] as Long),
params[1] as String?,
(params[2] as Int)
)
METHOD_GET_FAVORITES -> EhEngine.getFavorites(
task,
mOkHttpClient,
params[0] as String?
)
METHOD_ADD_FAVORITES -> EhEngine.addFavorites(
task,
mOkHttpClient,
(params[0] as Long),
params[1] as String?,
(params[2] as Int),
params[3] as String?
)
METHOD_ADD_FAVORITES_RANGE -> @Suppress("UNCHECKED_CAST") EhEngine.addFavoritesRange(
task,
mOkHttpClient,
params[0] as LongArray?,
params[1] as Array<String?>?,
(params[2] as Int)
)
METHOD_MODIFY_FAVORITES -> EhEngine.modifyFavorites(
task,
mOkHttpClient,
params[0] as String?,
params[1] as LongArray?,
(params[2] as Int)
)
METHOD_GET_TORRENT_LIST -> EhEngine.getTorrentList(
task,
mOkHttpClient,
params[0] as String?,
(params[1] as Long),
params[2] as String?
)
METHOD_GET_PROFILE -> EhEngine.getProfile(task, mOkHttpClient)
METHOD_VOTE_COMMENT -> EhEngine.voteComment(
task,
mOkHttpClient,
(params[0] as Long),
params[1] as String?,
(params[2] as Long),
params[3] as String?,
(params[4] as Long),
(params[5] as Int)
)
METHOD_IMAGE_SEARCH -> EhEngine.imageSearch(
task,
mOkHttpClient,
params[0] as File?,
(params[1] as Boolean),
(params[2] as Boolean),
(params[3] as Boolean)
)
METHOD_ARCHIVE_LIST -> EhEngine.getArchiveList(
task,
mOkHttpClient,
params[0] as String?,
(params[1] as Long),
params[2] as String?
)
METHOD_DOWNLOAD_ARCHIVE -> EhEngine.downloadArchive(
task,
mOkHttpClient,
(params[0] as Long),
params[1] as String?,
params[2] as String?,
params[3] as String?,
(params[4] as Boolean)
)
METHOD_VOTE_TAG -> EhEngine.voteTag(
task,
mOkHttpClient,
(params[0] as Long),
params[1] as String?,
(params[2] as Long),
params[3] as String?,
params[4] as String?,
(params[5] as Int)
)
METHOD_GET_UCONFIG -> EhEngine.getUConfig(task, mOkHttpClient)
else -> throw IllegalStateException("Can't detect method $method")
}
withUIContext { callback?.onSuccess(result) }
} catch (e: Exception) {
e.printStackTrace()
if (e !is CancelledException)
withUIContext { callback?.onFailure(e) }
}
request.task = null
request.callback = null
}
task.job = job
task.mCallback = request.callback
request.task = task
job.start()
} else {
request.callback?.onCancel()
}
}
interface Callback<E> {
fun onSuccess(result: E)
fun onFailure(e: Exception)
fun onCancel()
}
class Task {
private val mCall = AtomicReference<Call?>()
internal var mCallback: Callback<*>? = null
internal var job: Job? = null
private val mStop
get() = !(job?.isActive ?: false)
// Called in Job thread
@Throws(CancelledException::class)
fun setCall(call: Call?) {
if (mStop) {
// Stopped Job thread
throw CancelledException()
} else {
mCall.lazySet(call)
}
}
fun stop() {
if (!mStop) {
job?.cancel()
mCall.get()?.cancel()
mCallback?.let {
scope.launchUI { it.onCancel() }
}
}
}
}
companion object {
val scope = CoroutineScope(Dispatchers.IO)
const val METHOD_SIGN_IN = 0
const val METHOD_GET_GALLERY_LIST = 1
const val METHOD_GET_GALLERY_DETAIL = 3
const val METHOD_GET_PREVIEW_SET = 4
const val METHOD_GET_RATE_GALLERY = 5
const val METHOD_GET_COMMENT_GALLERY = 6
const val METHOD_GET_GALLERY_TOKEN = 7
const val METHOD_GET_FAVORITES = 8
const val METHOD_ADD_FAVORITES = 9
const val METHOD_ADD_FAVORITES_RANGE = 10
const val METHOD_MODIFY_FAVORITES = 11
const val METHOD_GET_TORRENT_LIST = 12
const val METHOD_GET_PROFILE = 14
const val METHOD_VOTE_COMMENT = 15
const val METHOD_IMAGE_SEARCH = 16
const val METHOD_ARCHIVE_LIST = 17
const val METHOD_DOWNLOAD_ARCHIVE = 18
const val METHOD_VOTE_TAG = 19
const val METHOD_GET_UCONFIG = 20
private val mOkHttpClient = okHttpClient
}
}