Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async RPC call of Android Binder #1201

Merged
merged 14 commits into from
Nov 16, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
package me.him188.ani.app.domain.torrent;

import me.him188.ani.app.domain.torrent.IRemoteTorrentDownloader;
import me.him188.ani.app.domain.torrent.IAnitorrentConfigCollector;
import me.him188.ani.app.domain.torrent.IProxySettingsCollector;
import me.him188.ani.app.domain.torrent.ITorrentPeerConfigCollector;
import me.him188.ani.app.domain.torrent.collector.IAnitorrentConfigCollector;
import me.him188.ani.app.domain.torrent.collector.IProxySettingsCollector;
import me.him188.ani.app.domain.torrent.collector.ITorrentPeerConfigCollector;

// Declare any non-default types here with import statements

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// IRemoteTorrentDownloader.aidl
package me.him188.ani.app.domain.torrent;

import me.him188.ani.app.domain.torrent.ITorrentDownloaderStatsCallback;
import me.him188.ani.app.domain.torrent.callback.ITorrentDownloaderStatsCallback;
import me.him188.ani.app.domain.torrent.cont.ContTorrentDownloaderFetchTorrent;
import me.him188.ani.app.domain.torrent.cont.ContTorrentDownloaderStartDownload;
import me.him188.ani.app.domain.torrent.IRemoteTorrentSession;
import me.him188.ani.app.domain.torrent.IDisposableHandle;
import me.him188.ani.app.domain.torrent.parcel.PTorrentLibInfo;
Expand All @@ -14,9 +16,9 @@ interface IRemoteTorrentDownloader {

PTorrentLibInfo getVendor();

PEncodedTorrentInfo fetchTorrent(in String uri, int timeoutSeconds);
IDisposableHandle fetchTorrent(in String uri, int timeoutSeconds, in ContTorrentDownloaderFetchTorrent cont);

IRemoteTorrentSession startDownload(in PEncodedTorrentInfo data, in String overrideSaveDir);
IDisposableHandle startDownload(in PEncodedTorrentInfo data, in String overrideSaveDir, in ContTorrentDownloaderStartDownload cont);

String getSaveDirForTorrent(in PEncodedTorrentInfo data);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// IRemoteTorrentFileEntry.aidl
package me.him188.ani.app.domain.torrent;

import me.him188.ani.app.domain.torrent.ITorrentFileEntryStatsCallback;
import me.him188.ani.app.domain.torrent.callback.ITorrentFileEntryStatsCallback;
import me.him188.ani.app.domain.torrent.cont.ContTorrentFileEntryGetInputParams;
import me.him188.ani.app.domain.torrent.cont.ContTorrentFileEntryResolveFile;
import me.him188.ani.app.domain.torrent.IRemotePieceList;
import me.him188.ani.app.domain.torrent.IRemoteTorrentFileHandle;
import me.him188.ani.app.domain.torrent.IDisposableHandle;
Expand All @@ -22,11 +24,11 @@ interface IRemoteTorrentFileEntry {

IRemoteTorrentFileHandle createHandle();

String resolveFile();
IDisposableHandle resolveFile(in ContTorrentFileEntryResolveFile cont);

String resolveFileMaybeEmptyOrNull();

PTorrentInputParameter getTorrentInputParams();
IDisposableHandle getTorrentInputParams(in ContTorrentFileEntryGetInputParams cont);

void torrentInputOnWait(int pieceIndex);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// IRemoteTorrentSession.aidl
package me.him188.ani.app.domain.torrent;

import me.him188.ani.app.domain.torrent.ITorrentSessionStatsCallback;
import me.him188.ani.app.domain.torrent.callback.ITorrentSessionStatsCallback;
import me.him188.ani.app.domain.torrent.cont.ContTorrentSessionGetFiles;
import me.him188.ani.app.domain.torrent.IRemoteTorrentFileEntryList;
import me.him188.ani.app.domain.torrent.parcel.PPeerInfo;
import me.him188.ani.app.domain.torrent.IDisposableHandle;
Expand All @@ -13,7 +14,7 @@ interface IRemoteTorrentSession {

String getName();

IRemoteTorrentFileEntryList getFiles();
IDisposableHandle getFiles(in ContTorrentSessionGetFiles cont);

PPeerInfo[] getPeers();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ITorrentDownloaderStatsCallback.aidl
package me.him188.ani.app.domain.torrent;
package me.him188.ani.app.domain.torrent.callback;

import me.him188.ani.app.domain.torrent.parcel.PTorrentDownloaderStats;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ITorrentFileEntryStatsCallback.aidl
package me.him188.ani.app.domain.torrent;
package me.him188.ani.app.domain.torrent.callback;

import me.him188.ani.app.domain.torrent.parcel.PTorrentFileEntryStats;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ITorrentSessionStatsCallback.aidl
package me.him188.ani.app.domain.torrent;
package me.him188.ani.app.domain.torrent.callback;

import me.him188.ani.app.domain.torrent.parcel.PTorrentSessionStats;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// IAnitorrentConfigCollector.aidl
package me.him188.ani.app.domain.torrent;
package me.him188.ani.app.domain.torrent.collector;

import me.him188.ani.app.domain.torrent.parcel.PAnitorrentConfig;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// IProxySettingsCollector.aidl
package me.him188.ani.app.domain.torrent;
package me.him188.ani.app.domain.torrent.collector;

import me.him188.ani.app.domain.torrent.parcel.PProxySettings;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ITorrentPeerConfigCollector.aidl
package me.him188.ani.app.domain.torrent;
package me.him188.ani.app.domain.torrent.collector;

import me.him188.ani.app.domain.torrent.parcel.PTorrentPeerConfig;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ContTorrentDownloaderFetchTorrent.aidl
package me.him188.ani.app.domain.torrent.cont;

import me.him188.ani.app.domain.torrent.parcel.PEncodedTorrentInfo;
import me.him188.ani.app.domain.torrent.parcel.RemoteContinuationException;

// Declare any non-default types here with import statements

interface ContTorrentDownloaderFetchTorrent {
void resume(in PEncodedTorrentInfo value);
void resumeWithException(in RemoteContinuationException exception);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ContTorrentDownloaderStartDownload.aidl
package me.him188.ani.app.domain.torrent.cont;

import me.him188.ani.app.domain.torrent.IRemoteTorrentSession;
import me.him188.ani.app.domain.torrent.parcel.RemoteContinuationException;

// Declare any non-default types here with import statements

interface ContTorrentDownloaderStartDownload {
void resume(in IRemoteTorrentSession value);
void resumeWithException(in RemoteContinuationException exception);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ContTorrentFileEntryGetInputParams.aidl
package me.him188.ani.app.domain.torrent.cont;

import me.him188.ani.app.domain.torrent.parcel.PTorrentInputParameter;
import me.him188.ani.app.domain.torrent.parcel.RemoteContinuationException;

// Declare any non-default types here with import statements

interface ContTorrentFileEntryGetInputParams {
void resume(in PTorrentInputParameter value);
void resumeWithException(in RemoteContinuationException exception);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ContTorrentFileEntryResolveFile.aidl
package me.him188.ani.app.domain.torrent.cont;

import me.him188.ani.app.domain.torrent.parcel.PTorrentInputParameter;
import me.him188.ani.app.domain.torrent.parcel.RemoteContinuationException;

// Declare any non-default types here with import statements

interface ContTorrentFileEntryResolveFile {
void resume(in String value);
void resumeWithException(in RemoteContinuationException exception);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ContTorrentSessionGetFiles.aidl
package me.him188.ani.app.domain.torrent.cont;

import me.him188.ani.app.domain.torrent.IRemoteTorrentFileEntryList;
import me.him188.ani.app.domain.torrent.parcel.RemoteContinuationException;

// Declare any non-default types here with import statements

interface ContTorrentSessionGetFiles {
void resume(in IRemoteTorrentFileEntryList value);
void resumeWithException(in RemoteContinuationException exception);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// RemoteContinuationException.aidl
package me.him188.ani.app.domain.torrent.parcel;

parcelable RemoteContinuationException;
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@ package me.him188.ani.app.domain.torrent.client
import android.os.DeadObjectException
import android.os.IInterface
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.coroutines.suspendCancellableCoroutine
import me.him188.ani.app.domain.torrent.IDisposableHandle
import me.him188.ani.app.domain.torrent.parcel.RemoteContinuationException
import me.him188.ani.utils.coroutines.CancellationException
import me.him188.ani.utils.logging.logger
import me.him188.ani.utils.logging.warn
import java.util.concurrent.CompletableFuture
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
* Wrapper for remote call
Expand Down Expand Up @@ -72,4 +79,89 @@ class RetryRemoteCall<I : IInterface>(
null
}
}
}

/**
* Wrapper for call which takes a continuation-like argument and returns [IDisposableHandle],
* which means this is a asynchronous RPC call.
*
* [IDisposableHandle] takes responsibility to pass cancellation to server.
*/
suspend inline fun <I : IInterface, T, P> RemoteCall<I>.callSuspendCancellable(
crossinline transact: I.(
resolve: (P?) -> Unit,
reject: (RemoteContinuationException?) -> Unit
) -> IDisposableHandle?,
StageGuard marked this conversation as resolved.
Show resolved Hide resolved
crossinline convert: (P) -> T,
): T = suspendCancellableCoroutine { cont ->
StageGuard marked this conversation as resolved.
Show resolved Hide resolved
val disposable = call {
transact(
{ value ->
if (value == null) {
cont.resumeWithException(CancellationException("Remote resume a null value."))
StageGuard marked this conversation as resolved.
Show resolved Hide resolved
} else {
cont.resume(convert(value))
}
},
{ exception ->
cont.resumeWithException(
exception?.smartCast() ?: Exception("Remote resume a null exception."),
StageGuard marked this conversation as resolved.
Show resolved Hide resolved
)
},
)
}

if (disposable != null) {
cont.invokeOnCancellation { disposable.callOnceOrNull { dispose() } }
} else {
cont.resumeWithException(CancellationException("Remote disposable is null."))
StageGuard marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* Wrapper for call which takes a continuation-like argument and returns [IDisposableHandle],
* which means this is a asynchronous RPC call.
*
* Returns [CompletableFuture] to get the result.
*
* Cancellation of [scope] will also cancel the future.
*/
inline fun <I : IInterface, T, P> RemoteCall<I>.callSuspendCancellableAsFuture(
crossinline transact: I.(
resolve: (P?) -> Unit,
reject: (RemoteContinuationException?) -> Unit
) -> IDisposableHandle?,
crossinline convert: (P) -> T,
StageGuard marked this conversation as resolved.
Show resolved Hide resolved
): CompletableFuture<T> {
val completableFuture = CompletableFuture<T>()

val disposable = call {
transact(
{ value ->
if (completableFuture.isDone) return@transact
if (value == null) {
completableFuture.completeExceptionally(CancellationException("Remote resume a null value."))
} else {
completableFuture.complete(convert(value))
}
},
{ exception ->
if (completableFuture.isDone) return@transact
completableFuture.completeExceptionally(
exception?.smartCast() ?: Exception("Remote resume a null exception."),
)
},
)
}

if (disposable == null) {
completableFuture.completeExceptionally(CancellationException("Remote disposable is null."))
} else {
completableFuture.handle { _, _ ->
// We don't care about the result. Just dispose service.
disposable.callOnceOrNull { dispose() }
}
}

return completableFuture
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ package me.him188.ani.app.domain.torrent.client

import android.os.Build
import androidx.annotation.RequiresApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.withContext
import kotlinx.io.files.Path
import me.him188.ani.app.domain.torrent.IDisposableHandle
import me.him188.ani.app.domain.torrent.IRemoteTorrentDownloader
import me.him188.ani.app.domain.torrent.ITorrentDownloaderStatsCallback
import me.him188.ani.app.domain.torrent.IRemoteTorrentSession
import me.him188.ani.app.domain.torrent.callback.ITorrentDownloaderStatsCallback
import me.him188.ani.app.domain.torrent.cont.ContTorrentDownloaderFetchTorrent
import me.him188.ani.app.domain.torrent.cont.ContTorrentDownloaderStartDownload
import me.him188.ani.app.domain.torrent.parcel.PEncodedTorrentInfo
import me.him188.ani.app.domain.torrent.parcel.PTorrentDownloaderStats
import me.him188.ani.app.domain.torrent.parcel.RemoteContinuationException
import me.him188.ani.app.domain.torrent.parcel.toParceled
import me.him188.ani.app.torrent.api.TorrentDownloader
import me.him188.ani.app.torrent.api.TorrentLibInfo
import me.him188.ani.app.torrent.api.TorrentSession
import me.him188.ani.app.torrent.api.files.EncodedTorrentInfo
import me.him188.ani.utils.coroutines.IO_
import me.him188.ani.utils.io.SystemPath
import me.him188.ani.utils.io.absolutePath
import me.him188.ani.utils.io.inSystem
Expand Down Expand Up @@ -62,22 +64,40 @@ class RemoteTorrentDownloader(

override val vendor: TorrentLibInfo get() = call { vendor.toTorrentLibInfo() }

override suspend fun fetchTorrent(uri: String, timeoutSeconds: Int): EncodedTorrentInfo {
return withContext(Dispatchers.IO_) {
val result = call { fetchTorrent(uri, timeoutSeconds) }
result.toEncodedTorrentInfo()
}
}
override suspend fun fetchTorrent(uri: String, timeoutSeconds: Int): EncodedTorrentInfo =
callSuspendCancellable(
transact = { resolve, reject ->
fetchTorrent(
uri, timeoutSeconds,
object : ContTorrentDownloaderFetchTorrent.Stub() {
override fun resume(value: PEncodedTorrentInfo?) = resolve(value)
override fun resumeWithException(exception: RemoteContinuationException?) = reject(exception)
},
)
},
convert = { it.toEncodedTorrentInfo() },
)

override suspend fun startDownload(
data: EncodedTorrentInfo,
parentCoroutineContext: CoroutineContext,
overrideSaveDir: SystemPath?
): TorrentSession {
return withContext(Dispatchers.IO_) {
RemoteTorrentSession(this@RemoteTorrentDownloader) {
call { startDownload(data.toParceled(), overrideSaveDir?.absolutePath) }
}
return RemoteTorrentSession(this@RemoteTorrentDownloader) {
callSuspendCancellableAsFuture(
transact = { resolve, reject ->
startDownload(
data.toParceled(),
overrideSaveDir?.absolutePath,
object : ContTorrentDownloaderStartDownload.Stub() {
override fun resume(value: IRemoteTorrentSession?) = resolve(value)
override fun resumeWithException(exception: RemoteContinuationException?) =
reject(exception)
},
)
},
convert = { it },
).get()
}
}

Expand Down
Loading
Loading