import 'dart:io'; import 'dart:isolate'; import 'package:jni/jni.dart'; import 'package:k3vinb5_aniyomi_bridge/jni_isolate_message.dart'; import 'package:path/path.dart' as path; import 'jmodels/janiyomibridge.dart'; import 'jmodels/jpage.dart'; import 'jmodels/jsanime.dart'; import 'jmodels/jschapter.dart'; import 'jmodels/jsepisode.dart'; import 'jmodels/jsmanga.dart'; import 'jmodels/jvideo.dart'; class JniIsolate { // Static consts static const String _aniyomiBridgeAnimeExtensionsDir = "animeExtensions"; static const String _aniyomiBridgeMangaExtensionsDir = "mangaExtensions"; // Static variables static late Isolate _jvmIsolate; static late SendPort _jvmIsolateSendPort; // Instance variables late final Directory _supportDirectory; late final String _supportDirectoryPath; Future<(bool, SendPort)> initJniIsolate( ReceivePort readyPort, ReceivePort mainIsolateReceiverPort, Directory supportDirectory, String dyLibDir, List classPath, ) async { _supportDirectory = supportDirectory; _supportDirectoryPath = supportDirectory.path; _jvmIsolate = await Isolate.spawn( _jniWorkerEntryPoint, { 'readyPort': readyPort.sendPort, 'mainIsolateSendPort': mainIsolateReceiverPort.sendPort, 'dylibDir': dyLibDir, 'classPath': classPath, }, debugName: 'Aniyomi-JNI-Isolate'); final message = await readyPort.first; readyPort.close(); if (message is SendPort) { _jvmIsolateSendPort = message; } else if (message is Map && message['error'] != null) { throw Exception('Failed to initialize JVM: ${message['error']}'); } else { throw Exception('Unexpected message from JVM isolate: $message'); } return (true, _jvmIsolateSendPort); } void _jniWorkerEntryPoint(Map parameters) { final SendPort readyPort = parameters['readyPort']; final SendPort mainIsolateSendPort = parameters['mainIsolateSendPort']; final String dylibDir = parameters['dylibDir']; final List classPath = parameters['classPath']; final jniIsolateReceiverPort = ReceivePort(); readyPort.send(jniIsolateReceiverPort.sendPort); try { Jni.spawnIfNotExists(dylibDir: dylibDir, classPath: classPath); JAniyomiBridge.init(); JAniyomiBridge jAniyomiBridge = JAniyomiBridge(); jniIsolateReceiverPort.listen((message) { if (message is! JniIsolateMessage) { mainIsolateSendPort.send( JniIsolateError( id: 0, error: 'Invalid message type received', stackTrace: '', ), ); return; } try { switch ((message).type) { case JniIsolateMessageType.getAnimeSearchResults: List results = _getAnimeSearchResults( jAniyomiBridge, message.args['query'] as String, message.args['page'] as int, message.args['source'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id, results: results), ); case JniIsolateMessageType.getMangaSearchResults: List results = _getMangaSearchResults( jAniyomiBridge, message.args['query'] as String, message.args['page'] as int, message.args['source'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id, results: results), ); case JniIsolateMessageType.getEpisodeList: List results = _getEpisodeList( jAniyomiBridge, message.args['sAnime'] as JSAnime, message.args['source'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id, results: results), ); case JniIsolateMessageType.getChapterList: List results = _getChapterList( jAniyomiBridge, message.args['sManga'] as JSManga, message.args['source'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id, results: results), ); case JniIsolateMessageType.getVideoList: List results = _getVideoList( jAniyomiBridge, message.args['sEpisode'] as JSEpisode, message.args['source'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id, results: results), ); case JniIsolateMessageType.getPageList: List results = _getPageList( jAniyomiBridge, message.args['sChapter'] as JSChapter, message.args['source'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id, results: results), ); case JniIsolateMessageType.loadAnimeExtension: _loadAnimeExtension( jAniyomiBridge, message.args['extensionUrl'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id), ); case JniIsolateMessageType.loadMangaExtension: _loadMangaExtension( jAniyomiBridge, message.args['extensionUrl'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id), ); case JniIsolateMessageType.unloadAnimeExtension: _unloadAnimeExtension( jAniyomiBridge, message.args['extensionName'] as String, message.args['extensionVersion'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id), ); case JniIsolateMessageType.unloadMangaExtension: _unloadMangaExtension( jAniyomiBridge, message.args['extensionName'] as String, message.args['extensionVersion'] as String, ); mainIsolateSendPort.send( JniIsolateResponse(id: message.id), ); } } catch (e, stackTrace) { mainIsolateSendPort.send( JniIsolateError( id: message.id, error: e.toString(), stackTrace: stackTrace.toString(), ), ); } }); } catch (e, stackTrace) { mainIsolateSendPort.send( JniIsolateError( id: 0, error: e.toString(), stackTrace: stackTrace.toString(), ), ); } } dispose() { _jvmIsolate.kill(priority: Isolate.immediate); } List _getAnimeSearchResults(JAniyomiBridge jAniyomiBridge, String query, int page, String source) { JList? searchResults = jAniyomiBridge.getAnimeSearchResults( JString.fromString(query), page, JString.fromString(source), ); if (searchResults == null) { return []; } return searchResults .cast() .where(_jObjIsNotNull) .map((jObj) => jObj!.as(JSAnime.type)) .toList(); } List _getMangaSearchResults(JAniyomiBridge jAniyomiBridge, String query, int page, String source) { JList? searchResults = jAniyomiBridge.getMangaSearchResults( JString.fromString(query), page, JString.fromString(source), ); if (searchResults == null) { return []; } return searchResults .cast() .where(_jObjIsNotNull) .map((jObj) => jObj!.as(JSManga.type)) .toList(); } List _getEpisodeList(JAniyomiBridge jAniyomiBridge, JSAnime sAnime, String source) { JList? episodeList = jAniyomiBridge.getEpisodeList( sAnime, JString.fromString(source), ); if (episodeList == null) { return []; } return episodeList .cast() .where(_jObjIsNotNull) .map((jObj) => jObj!.as(JSEpisode.type)) .toList(); } List _getChapterList(JAniyomiBridge jAniyomiBridge, JSManga sManga, String source) { JList? episodeList = jAniyomiBridge.getEpisodeList( sManga, JString.fromString(source), ); if (episodeList == null) { return []; } return episodeList .cast() .where(_jObjIsNotNull) .map((jObj) => jObj!.as(JSChapter.type)) .toList(); } List _getVideoList(JAniyomiBridge jAniyomiBridge, JSEpisode sEpisode, String source) { JList? videoList = jAniyomiBridge.getVideoList( sEpisode, JString.fromString(source), ); if (videoList == null) { return []; } return videoList .cast() .where(_jObjIsNotNull) .map((jObj) => jObj!.as(JVideo.type)) .toList(); } List _getPageList(JAniyomiBridge jAniyomiBridge, JSChapter sChapter, String source) { JList? videoList = jAniyomiBridge.getVideoList( sChapter, JString.fromString(source), ); if (videoList == null) { return []; } return videoList .cast() .where(_jObjIsNotNull) .map((jObj) => jObj!.as(JPage.type)) .toList(); } void _loadAnimeExtension(JAniyomiBridge jAniyomiBridge, String extensionUrl) { jAniyomiBridge.loadAnimeExtension( JString.fromString(extensionUrl), JString.fromString( path.join(_supportDirectoryPath, _aniyomiBridgeAnimeExtensionsDir), ), ); } void _unloadAnimeExtension(JAniyomiBridge jAniyomiBridge, String extensionName, String extensionVersion) { jAniyomiBridge.unloadAnimeExtension( JString.fromString(extensionName), JString.fromString(extensionVersion), JString.fromString( path.join(_supportDirectoryPath, _aniyomiBridgeAnimeExtensionsDir), ), ); } void _loadMangaExtension(JAniyomiBridge jAniyomiBridge, String extensionUrl) { jAniyomiBridge.loadMangaExtension( JString.fromString(extensionUrl), JString.fromString( path.join(_supportDirectoryPath, _aniyomiBridgeMangaExtensionsDir), ), ); } void _unloadMangaExtension(JAniyomiBridge jAniyomiBridge, String extensionName, String extensionVersion) { jAniyomiBridge.unloadMangaExtension( JString.fromString(extensionName), JString.fromString(extensionVersion), JString.fromString( path.join(_supportDirectoryPath, _aniyomiBridgeMangaExtensionsDir), ), ); } bool Function(JObject? jObj) get _jObjIsNotNull => (jObj) => jObj != null; }