import 'dart:io'; import 'package:flutter/services.dart'; import 'package:jni/jni.dart'; import 'package:k3vinb5_aniyomi_bridge/models/extension.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; import 'package:k3vinb5_aniyomi_bridge/jmodels/jvideo.dart'; import 'package:k3vinb5_aniyomi_bridge/jmodels/janiyomibridge.dart'; import 'package:k3vinb5_aniyomi_bridge/jmodels/jsanime.dart'; import 'package:k3vinb5_aniyomi_bridge/jmodels/jsepisode.dart'; import 'package:k3vinb5_aniyomi_bridge/jmodels/jsmanga.dart'; import 'package:k3vinb5_aniyomi_bridge/jmodels/jschapter.dart'; import 'package:k3vinb5_aniyomi_bridge/jmodels/jpage.dart'; class AniyomiBridge { static const String _aniyomiBridgeDir = "aniyomibridge"; static const String _aniyomiBridgeAnimeExtensionsDir = "animeExtensions"; static const String _aniyomiBridgeMangaExtensionsDir = "mangaExtensions"; static const String _aniyomiBridgeJarName = "aniyomibridge-core.jar"; static const String _packageAssetsDir = "packages/k3vinb5_aniyomi_bridge/assets"; static late final JAniyomiBridge _jAniyomiBridge; static late final String _supportDirectoryPath; bool _isReady = false; AniyomiBridge() { _initJvm(); } Future _initJvm() async { Directory supportDirectory = await getApplicationSupportDirectory(); _supportDirectoryPath = supportDirectory.path; await _loadJarIfNeeded(supportDirectory); Jni.spawnIfNotExists( dylibDir: _getDylibDir(supportDirectory), classPath: _getClassPath(supportDirectory), ); JAniyomiBridge.init(); _jAniyomiBridge = JAniyomiBridge(); _isReady = true; } bool isReady() { return _isReady; } List getAnimeSearchResults(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(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(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(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(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(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(String extensionUrl) { _jAniyomiBridge.loadAnimeExtension( JString.fromString(extensionUrl), JString.fromString( path.join(_supportDirectoryPath, _aniyomiBridgeAnimeExtensionsDir), ), ); } void unloadAnimeExtension(String extensionName, String extensionVersion) { _jAniyomiBridge.unloadAnimeExtension( JString.fromString(extensionName), JString.fromString(extensionVersion), JString.fromString( path.join(_supportDirectoryPath, _aniyomiBridgeAnimeExtensionsDir), ) ); } void loadMangaExtension(String extensionUrl) { _jAniyomiBridge.loadMangaExtension( JString.fromString(extensionUrl), JString.fromString( path.join(_supportDirectoryPath, _aniyomiBridgeMangaExtensionsDir), ), ); } void unloadMangaExtension(String extensionName, String extensionVersion) { _jAniyomiBridge.unloadMangaExtension( JString.fromString(extensionName), JString.fromString(extensionVersion), JString.fromString( path.join(_supportDirectoryPath, _aniyomiBridgeMangaExtensionsDir), ) ); } Future> getInstalledAnimeExtensions() async{ Directory animeExtDir = Directory( path.join(_supportDirectoryPath, _aniyomiBridgeAnimeExtensionsDir), ); Set extensions = await animeExtDir .list() .where((entityFileSystem) => entityFileSystem is File) .where((file) => path.extension(file.path) == ".jar") .map((jarFile) => path.basename(jarFile.path)) .map( (jarFileName) => Extension( name: jarFileName.split("-")[0], version: jarFileName.split("-")[1], ), ) .toSet(); return extensions; } Future> getInstalledMangaExtensions() async{ Directory mangaExtDir = Directory( path.join(_supportDirectoryPath, _aniyomiBridgeMangaExtensionsDir), ); Set extensions = await mangaExtDir .list() .where((entityFileSystem) => entityFileSystem is File) .where((file) => path.extension(file.path) == ".jar") .map((jarFile) => path.basename(jarFile.path)) .map( (jarFileName) => Extension( name: jarFileName.split("-")[0], version: jarFileName.split("-")[1], ), ) .toSet(); return extensions; } List? getLoadedAnimeExtensions() { JList? loadedExtensions = _jAniyomiBridge .getAnimeLoadedExtensions(); if (loadedExtensions == null) { return null; } return loadedExtensions .cast() .map((jStr) => jStr.toDartString()) .toList(); } List? getLoadedMangaExtensions() { JList? loadedExtensions = _jAniyomiBridge .getMangaLoadedExtensions(); if (loadedExtensions == null) { return null; } return loadedExtensions .cast() .map((jStr) => jStr.toDartString()) .toList(); } bool Function(JObject? jObj) get _jObjIsNotNull => (jObj) => jObj != null; String _getDylibDir(Directory supportDirectory) { String executablePath = File(Platform.resolvedExecutable).parent.path; if (Platform.isLinux) { return path.join(executablePath, "jre", "customjre", "lib", "server"); } else if (Platform.isMacOS) { return path.join( executablePath, "..", "Resources", "jre", "customjre", "lib", "server", ); } else if (Platform.isWindows) { return path.join( executablePath, "..", "jre", "customjre", "lib", "server", ); } else { throw UnsupportedError("Unsupported platform"); } } List _getClassPath(Directory supportDirectory) { return [ path.join( supportDirectory.absolute.path, _aniyomiBridgeDir, _aniyomiBridgeJarName, ), ]; } Future _loadJarIfNeeded(Directory supportDirectory) async { String aniyomiBridgeCorePath = path.join( supportDirectory.path, _aniyomiBridgeDir, _aniyomiBridgeJarName, ); File aniyomiBridgeCore = File(aniyomiBridgeCorePath); if (!(await aniyomiBridgeCore.exists())) { await _copyAssetToFile( "$_packageAssetsDir/$_aniyomiBridgeJarName", aniyomiBridgeCorePath, ); } } Future _copyAssetToFile(String assetPath, String outPath) async { final byteData = await rootBundle.load(assetPath); final buffer = byteData.buffer.asUint8List(); final file = File(outPath); await file.parent.create(recursive: true); await file.writeAsBytes(buffer, flush: true); return file; } }