diff --git a/lib/aniyomi_bridge.dart b/lib/aniyomi_bridge.dart index 8009313..fbe0a6e 100644 --- a/lib/aniyomi_bridge.dart +++ b/lib/aniyomi_bridge.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; import 'dart:math'; @@ -7,6 +8,7 @@ import 'package:jni/jni.dart'; import 'package:k3vinb5_aniyomi_bridge/jni_isolate.dart'; import 'package:k3vinb5_aniyomi_bridge/jni_isolate_message.dart'; import 'package:k3vinb5_aniyomi_bridge/models/extension.dart'; +import 'package:logger/logger.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; import 'package:k3vinb5_aniyomi_bridge/jmodels/jvideo.dart'; @@ -16,6 +18,8 @@ import 'package:k3vinb5_aniyomi_bridge/jmodels/jsmanga.dart'; import 'package:k3vinb5_aniyomi_bridge/jmodels/jschapter.dart'; import 'package:k3vinb5_aniyomi_bridge/jmodels/jpage.dart'; +import 'config.dart' as config; + class AniyomiBridge { // static consts static const String _aniyomiBridgeAnimeExtensionsDir = "animeExtensions"; @@ -24,6 +28,7 @@ class AniyomiBridge { static const String _aniyomiBridgeJarName = "aniyomibridge-core.jar"; static const String _packageAssetsDir = "packages/k3vinb5_aniyomi_bridge/assets"; + static const Duration _cacheTtl = Duration(minutes: 20); // static variables static bool _isReady = false; @@ -34,8 +39,14 @@ class AniyomiBridge { // instance variables late final String _supportDirectoryPath; final Random _random = Random(); + final Logger _logger = Logger( + printer: PrettyPrinter(methodCount: 9), + level: Level.debug, + // output: FileOutput(), + filter: ProductionFilter(), + ); final Map _pendingRequests = {}; - + final Map _responsesCache = {}; AniyomiBridge() { _initJvm(); @@ -78,91 +89,120 @@ class AniyomiBridge { } completer.complete(message.results); } - }); - } + });} bool isReady() { return _isReady; } Future> getAnimeSearchResults(String query, int page, String source) async { - return _sendJniIsolateMessage>( + Map args = { + 'query': query, + 'page': page, + 'source': source, + }; + Future>? cachedResponse = _getCachedResponse>>(JniIsolateMessageType.getAnimeSearchResults, args); + if (cachedResponse != null) return cachedResponse; + Future> response = _sendJniIsolateMessage>( JniIsolateMessage( id: _random.nextInt(1 << 32), type: JniIsolateMessageType.getAnimeSearchResults, - args: { - 'query': query, - 'page': page, - 'source': source, - }, + args: args, ), ); + _cacheResponse(response, JniIsolateMessageType.getAnimeSearchResults, args); + return response; } Future> getMangaSearchResults(String query, int page, String source) async { - return _sendJniIsolateMessage>( + Map args = { + 'query': query, + 'page': page, + 'source': source, + }; + Future>? cachedResponse = _getCachedResponse>>(JniIsolateMessageType.getMangaSearchResults, args); + if (cachedResponse != null) return cachedResponse; + Future> response = _sendJniIsolateMessage>( JniIsolateMessage( id: _random.nextInt(1 << 32), type: JniIsolateMessageType.getMangaSearchResults, - args: { - 'query': query, - 'page': page, - 'source': source, - }, + args: args, ), ); + _cacheResponse(response, JniIsolateMessageType.getMangaSearchResults, args); + return response; } Future> getEpisodeList(JSAnime sAnime, String source) async { - return _sendJniIsolateMessage>( + Map args = { + 'sAnime': sAnime, + 'source': source, + }; + Future>? cachedResponse = _getCachedResponse>>(JniIsolateMessageType.getEpisodeList, args); + if (cachedResponse != null) return cachedResponse; + Future> response = _sendJniIsolateMessage>( JniIsolateMessage( id: _random.nextInt(1 << 32), type: JniIsolateMessageType.getEpisodeList, - args: { - 'sAnime': sAnime, - 'source': source, - }, + args: args, ), ); + _cacheResponse(response, JniIsolateMessageType.getEpisodeList, args); + return response; } Future> getChapterList(JSManga sManga, String source) async { - return _sendJniIsolateMessage>( + Map args = { + 'sManga': sManga, + 'source': source, + }; + Future>? cachedResponse = _getCachedResponse>>(JniIsolateMessageType.getChapterList, args); + if (cachedResponse != null) return cachedResponse; + Future> response = _sendJniIsolateMessage>( JniIsolateMessage( id: _random.nextInt(1 << 32), type: JniIsolateMessageType.getChapterList, - args: { - 'sManga': sManga, - 'source': source, - }, + args: args, ), ); + _cacheResponse(response, JniIsolateMessageType.getChapterList, args); + return response; } Future> getVideoList(JSEpisode sEpisode, String source) async { - return _sendJniIsolateMessage>( + Map args = { + 'sEpisode': sEpisode, + 'source': source, + }; + Future>? cachedResponse = _getCachedResponse>>(JniIsolateMessageType.getVideoList, args); + if (cachedResponse != null) return cachedResponse; + Future> response = _sendJniIsolateMessage>( JniIsolateMessage( id: _random.nextInt(1 << 32), type: JniIsolateMessageType.getVideoList, - args: { - 'sEpisode': sEpisode, - 'source': source, - }, + args: args, ), ); + _cacheResponse(response, JniIsolateMessageType.getVideoList, args); + return response; } Future> getPageList(JSChapter sChapter, String source) async { - return _sendJniIsolateMessage>( + Map args = { + 'sChapter': sChapter, + 'source': source, + }; + Future>? cachedResponse = _getCachedResponse>>(JniIsolateMessageType.getPageList, args); + if (cachedResponse != null) return cachedResponse; + Future> response = _sendJniIsolateMessage>( JniIsolateMessage( id: _random.nextInt(1 << 32), type: JniIsolateMessageType.getPageList, - args: { - 'sChapter': sChapter, - 'source': source, - }, + args: args, ), ); + _cacheResponse(response, JniIsolateMessageType.getPageList, args); + return response; } Future loadAnimeExtension(String extensionUrl) async { @@ -348,4 +388,60 @@ class AniyomiBridge { await file.writeAsBytes(buffer, flush: true); return file; } + + T? _getCachedResponse(JniIsolateMessageType messageType, Map args) { + // if (!_isCacheable(messageType)) return null; + final cacheKey = _generateCacheKey(messageType, args); + if (_responsesCache.containsKey(cacheKey)) { + _logger.d("Cached response found for ${messageType.name}"); + try { + final (expiry, cachedResponse as T) = + _responsesCache[cacheKey]!; + if (expiry < DateTime.now().millisecondsSinceEpoch) { + _logger.d( + "Cached response for ${messageType.name} with cache key $cacheKey has expired, making new request", + ); + _responsesCache.remove(cacheKey); + } else { + _logger.d("Cached response for ${messageType.name} with cache key $cacheKey is still valid for ${(expiry - DateTime.now().millisecondsSinceEpoch) / 1000}s"); + return cachedResponse; + } + } catch (e, stackTrace) { + _logger.e( + "Error while retrieving cached response for ${messageType.name}: $e", + stackTrace: stackTrace, + ); + _responsesCache.remove(cacheKey); + return null; + } + } + _logger.d("No cached response found for ${messageType.name} with cache key $cacheKey"); + return null; + } + + void _cacheResponse(T response, JniIsolateMessageType messageType, Map args) { + final cacheKey = _generateCacheKey(messageType, args); + _logger.d("Caching response for ${messageType.name} with cache key $cacheKey"); + _responsesCache[cacheKey] = + ( + DateTime.now().millisecondsSinceEpoch + _cacheTtl.inMilliseconds, + response, + ); + } + + String _generateCacheKey(JniIsolateMessageType messageType, Map args) { + // Create a stable string representation of the args + final argsString = jsonEncode(args); + return "${messageType.name}_$argsString"; + } + + + + // bool _isCacheable(JniIsolateMessageType messageType) { + // if (config.cacheDisabledMessageType.contains(messageType)){ + // _logger.w("Endpoint $messageType is disabled for caching"); + // return false; + // } + // return true; + // } } diff --git a/lib/config.dart b/lib/config.dart new file mode 100644 index 0000000..fb92511 --- /dev/null +++ b/lib/config.dart @@ -0,0 +1,3 @@ +import 'package:k3vinb5_aniyomi_bridge/jni_isolate_message.dart'; + +const Set cacheDisabledMessageType = {}; \ No newline at end of file