mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
add star rating, network cache, advanced search option, loginWithCookies, loadNext; fix some minor issues
This commit is contained in:
@@ -6,9 +6,12 @@ import 'package:dio/io.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:venera/foundation/appdata.dart';
|
||||
import 'package:venera/foundation/log.dart';
|
||||
import 'package:venera/network/cache.dart';
|
||||
import 'package:venera/utils/ext.dart';
|
||||
|
||||
import '../foundation/app.dart';
|
||||
import 'cloudflare.dart';
|
||||
import 'cookie_jar.dart';
|
||||
|
||||
export 'package:dio/dio.dart';
|
||||
|
||||
@@ -107,6 +110,9 @@ class AppDio with DioMixin {
|
||||
this.options = options ?? BaseOptions();
|
||||
interceptors.add(MyLogInterceptor());
|
||||
httpClientAdapter = IOHttpClientAdapter(createHttpClient: createHttpClient);
|
||||
interceptors.add(CookieManagerSql(SingleInstanceCookieJar.instance!));
|
||||
interceptors.add(NetworkCacheManager());
|
||||
interceptors.add(CloudflareInterceptor());
|
||||
}
|
||||
|
||||
static HttpClient createHttpClient() {
|
||||
|
197
lib/network/cache.dart
Normal file
197
lib/network/cache.dart
Normal file
@@ -0,0 +1,197 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class NetworkCache {
|
||||
final Uri uri;
|
||||
|
||||
final Map<String, dynamic> requestHeaders;
|
||||
|
||||
final Map<String, List<String>> responseHeaders;
|
||||
|
||||
final Object? data;
|
||||
|
||||
final DateTime time;
|
||||
|
||||
final int size;
|
||||
|
||||
const NetworkCache({
|
||||
required this.uri,
|
||||
required this.requestHeaders,
|
||||
required this.responseHeaders,
|
||||
required this.data,
|
||||
required this.time,
|
||||
required this.size,
|
||||
});
|
||||
}
|
||||
|
||||
class NetworkCacheManager implements Interceptor {
|
||||
NetworkCacheManager._();
|
||||
|
||||
static final NetworkCacheManager instance = NetworkCacheManager._();
|
||||
|
||||
factory NetworkCacheManager() => instance;
|
||||
|
||||
final Map<Uri, NetworkCache> _cache = {};
|
||||
|
||||
int size = 0;
|
||||
|
||||
NetworkCache? getCache(Uri uri) {
|
||||
return _cache[uri];
|
||||
}
|
||||
|
||||
static const _maxCacheSize = 10 * 1024 * 1024;
|
||||
|
||||
void setCache(NetworkCache cache) {
|
||||
while(size > _maxCacheSize){
|
||||
size -= _cache.values.first.size;
|
||||
_cache.remove(_cache.keys.first);
|
||||
}
|
||||
_cache[cache.uri] = cache;
|
||||
size += cache.size;
|
||||
}
|
||||
|
||||
void removeCache(Uri uri) {
|
||||
var cache = _cache[uri];
|
||||
if(cache != null){
|
||||
size -= cache.size;
|
||||
}
|
||||
_cache.remove(uri);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_cache.clear();
|
||||
size = 0;
|
||||
}
|
||||
|
||||
var preventParallel = <Uri, Completer>{};
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
if(err.requestOptions.method != "GET"){
|
||||
return handler.next(err);
|
||||
}
|
||||
if(preventParallel[err.requestOptions.uri] != null){
|
||||
preventParallel[err.requestOptions.uri]!.complete();
|
||||
preventParallel.remove(err.requestOptions.uri);
|
||||
}
|
||||
return handler.next(err);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRequest(
|
||||
RequestOptions options, RequestInterceptorHandler handler) async {
|
||||
if(options.method != "GET"){
|
||||
return handler.next(options);
|
||||
}
|
||||
if(preventParallel[options.uri] != null){
|
||||
await preventParallel[options.uri]!.future;
|
||||
}
|
||||
var cache = getCache(options.uri);
|
||||
if (cache == null || !compareHeaders(options.headers, cache.requestHeaders)) {
|
||||
if(options.headers['cache-time'] != null){
|
||||
options.headers.remove('cache-time');
|
||||
}
|
||||
if(options.headers['prevent-parallel'] != null){
|
||||
options.headers.remove('prevent-parallel');
|
||||
preventParallel[options.uri] = Completer();
|
||||
}
|
||||
return handler.next(options);
|
||||
} else {
|
||||
if(options.headers['cache-time'] == 'no'){
|
||||
options.headers.remove('cache-time');
|
||||
removeCache(options.uri);
|
||||
return handler.next(options);
|
||||
}
|
||||
}
|
||||
var time = DateTime.now();
|
||||
var diff = time.difference(cache.time);
|
||||
if (options.headers['cache-time'] == 'long'
|
||||
&& diff < const Duration(hours: 2)) {
|
||||
return handler.resolve(Response(
|
||||
requestOptions: options,
|
||||
data: cache.data,
|
||||
headers: Headers.fromMap(cache.responseHeaders),
|
||||
statusCode: 200,
|
||||
));
|
||||
}
|
||||
else if (diff < const Duration(seconds: 5)) {
|
||||
return handler.resolve(Response(
|
||||
requestOptions: options,
|
||||
data: cache.data,
|
||||
headers: Headers.fromMap(cache.responseHeaders),
|
||||
statusCode: 200,
|
||||
));
|
||||
} else if (diff < const Duration(hours: 1)) {
|
||||
var o = options.copyWith(
|
||||
method: "HEAD",
|
||||
);
|
||||
var dio = Dio();
|
||||
var response = await dio.fetch(o);
|
||||
if (response.statusCode == 200 &&
|
||||
compareHeaders(cache.responseHeaders, response.headers.map)) {
|
||||
return handler.resolve(Response(
|
||||
requestOptions: options,
|
||||
data: cache.data,
|
||||
headers: Headers.fromMap(cache.responseHeaders),
|
||||
statusCode: 200,
|
||||
));
|
||||
}
|
||||
}
|
||||
removeCache(options.uri);
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
static bool compareHeaders(Map<String, dynamic> a, Map<String, dynamic> b) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
for (var key in a.keys) {
|
||||
if (a[key] != b[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(
|
||||
Response<dynamic> response, ResponseInterceptorHandler handler) {
|
||||
if (response.requestOptions.method != "GET") {
|
||||
return handler.next(response);
|
||||
}
|
||||
var size = _calculateSize(response.data);
|
||||
if(size != null && size < 1024 * 1024) {
|
||||
var cache = NetworkCache(
|
||||
uri: response.requestOptions.uri,
|
||||
requestHeaders: response.requestOptions.headers,
|
||||
responseHeaders: response.headers.map,
|
||||
data: response.data,
|
||||
time: DateTime.now(),
|
||||
size: size,
|
||||
);
|
||||
setCache(cache);
|
||||
}
|
||||
if(preventParallel[response.requestOptions.uri] != null){
|
||||
preventParallel[response.requestOptions.uri]!.complete();
|
||||
preventParallel.remove(response.requestOptions.uri);
|
||||
}
|
||||
handler.next(response);
|
||||
}
|
||||
|
||||
static int? _calculateSize(Object? data){
|
||||
if(data == null){
|
||||
return 0;
|
||||
}
|
||||
if(data is List<int>) {
|
||||
return data.length;
|
||||
}
|
||||
if(data is String) {
|
||||
return data.length * 4;
|
||||
}
|
||||
if(data is Map) {
|
||||
return data.toString().length * 4;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -202,6 +202,9 @@ class CookieManagerSql extends Interceptor {
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
var cookies = cookieJar.loadForRequestCookieHeader(options.uri);
|
||||
if (cookies.isNotEmpty) {
|
||||
if(options.headers["cookie"] != null) {
|
||||
cookies = "${options.headers["cookie"]}; $cookies";
|
||||
}
|
||||
options.headers["cookie"] = cookies;
|
||||
}
|
||||
handler.next(options);
|
||||
|
@@ -7,7 +7,8 @@ import 'package:venera/foundation/consts.dart';
|
||||
import 'app_dio.dart';
|
||||
|
||||
class ImageDownloader {
|
||||
static Stream<ImageDownloadProgress> loadThumbnail(String url, String? sourceKey) async* {
|
||||
static Stream<ImageDownloadProgress> loadThumbnail(
|
||||
String url, String? sourceKey) async* {
|
||||
final cacheKey = "$url@$sourceKey";
|
||||
final cache = await CacheManager().findCache(cacheKey);
|
||||
|
||||
@@ -25,12 +26,14 @@ class ImageDownloader {
|
||||
var comicSource = ComicSource.find(sourceKey);
|
||||
configs = comicSource!.getThumbnailLoadingConfig?.call(url) ?? {};
|
||||
}
|
||||
configs['headers'] ??= {
|
||||
'user-agent': webUA,
|
||||
};
|
||||
configs['headers'] ??= {};
|
||||
if(configs['headers']['user-agent'] == null
|
||||
&& configs['headers']['User-Agent'] == null) {
|
||||
configs['headers']['user-agent'] = webUA;
|
||||
}
|
||||
|
||||
var dio = AppDio(BaseOptions(
|
||||
headers: configs['headers'],
|
||||
headers: Map<String, dynamic>.from(configs['headers']),
|
||||
method: configs['method'] ?? 'GET',
|
||||
responseType: ResponseType.stream,
|
||||
));
|
||||
@@ -53,7 +56,7 @@ class ImageDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
if(configs['onResponse'] != null) {
|
||||
if (configs['onResponse'] != null) {
|
||||
buffer = configs['onResponse'](buffer);
|
||||
}
|
||||
|
||||
@@ -65,7 +68,8 @@ class ImageDownloader {
|
||||
);
|
||||
}
|
||||
|
||||
static Stream<ImageDownloadProgress> loadComicImage(String imageKey, String? sourceKey, String cid, String eid) async* {
|
||||
static Stream<ImageDownloadProgress> loadComicImage(
|
||||
String imageKey, String? sourceKey, String cid, String eid) async* {
|
||||
final cacheKey = "$imageKey@$sourceKey@$cid@$eid";
|
||||
final cache = await CacheManager().findCache(cacheKey);
|
||||
|
||||
@@ -81,7 +85,8 @@ class ImageDownloader {
|
||||
var configs = <String, dynamic>{};
|
||||
if (sourceKey != null) {
|
||||
var comicSource = ComicSource.find(sourceKey);
|
||||
configs = comicSource!.getImageLoadingConfig?.call(imageKey, cid, eid) ?? {};
|
||||
configs = (await comicSource!.getImageLoadingConfig
|
||||
?.call(imageKey, cid, eid)) ?? {};
|
||||
}
|
||||
configs['headers'] ??= {
|
||||
'user-agent': webUA,
|
||||
@@ -111,7 +116,7 @@ class ImageDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
if(configs['onResponse'] != null) {
|
||||
if (configs['onResponse'] != null) {
|
||||
buffer = configs['onResponse'](buffer);
|
||||
}
|
||||
|
||||
@@ -136,4 +141,4 @@ class ImageDownloadProgress {
|
||||
required this.totalBytes,
|
||||
this.imageBytes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user