mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
284 lines
7.5 KiB
Dart
284 lines
7.5 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'dart:io';
|
|
|
|
/// Advanced network configuration for streaming
|
|
class NetworkConfig {
|
|
final String? proxyUrl;
|
|
final String? userAgent;
|
|
final Map<String, String> customHeaders;
|
|
final Duration connectTimeout;
|
|
final Duration receiveTimeout;
|
|
final bool enableValidateSSL;
|
|
|
|
const NetworkConfig({
|
|
this.proxyUrl,
|
|
this.userAgent = 'VLC/3.0.18 LibVLC/3.0.18',
|
|
this.customHeaders = const {},
|
|
this.connectTimeout = const Duration(seconds: 30),
|
|
this.receiveTimeout = const Duration(seconds: 60),
|
|
this.enableValidateSSL = true,
|
|
});
|
|
|
|
NetworkConfig copyWith({
|
|
String? proxyUrl,
|
|
String? userAgent,
|
|
Map<String, String>? customHeaders,
|
|
Duration? connectTimeout,
|
|
Duration? receiveTimeout,
|
|
bool? enableValidateSSL,
|
|
}) =>
|
|
NetworkConfig(
|
|
proxyUrl: proxyUrl ?? this.proxyUrl,
|
|
userAgent: userAgent ?? this.userAgent,
|
|
customHeaders: customHeaders ?? this.customHeaders,
|
|
connectTimeout: connectTimeout ?? this.connectTimeout,
|
|
receiveTimeout: receiveTimeout ?? this.receiveTimeout,
|
|
enableValidateSSL: enableValidateSSL ?? this.enableValidateSSL,
|
|
);
|
|
}
|
|
|
|
/// Advanced network service with proxy and optimization support
|
|
class OptimizedNetworkService {
|
|
late Dio _dio;
|
|
late NetworkConfig _config;
|
|
late CacheInterceptor _cacheInterceptor;
|
|
|
|
OptimizedNetworkService({required NetworkConfig config}) {
|
|
_config = config;
|
|
_initializeDio();
|
|
}
|
|
|
|
void _initializeDio() {
|
|
_cacheInterceptor = CacheInterceptor(
|
|
options: CacheOptions(
|
|
store: MemCacheStore(),
|
|
policy: CachePolicy.requestFirst,
|
|
hitCacheOnErrorExcept: [],
|
|
allowPostMethod: false,
|
|
),
|
|
);
|
|
|
|
_dio = Dio(
|
|
BaseOptions(
|
|
connectTimeout: _config.connectTimeout,
|
|
receiveTimeout: _config.receiveTimeout,
|
|
validateStatus: (status) => status != null && status < 500,
|
|
headers: {
|
|
'User-Agent': _config.userAgent,
|
|
..._config.customHeaders,
|
|
},
|
|
),
|
|
);
|
|
|
|
// Configure proxy if specified
|
|
if (_config.proxyUrl != null) {
|
|
_configureProxy(_config.proxyUrl!);
|
|
}
|
|
|
|
// Add interceptors
|
|
_dio.interceptors.addAll([
|
|
_cacheInterceptor,
|
|
_BandwidthTrackingInterceptor(),
|
|
_RetryInterceptor(),
|
|
]);
|
|
}
|
|
|
|
/// Configure native Dart HttpClient with proxy
|
|
void _configureProxy(String proxyUrl) {
|
|
try {
|
|
final uri = Uri.parse(proxyUrl);
|
|
_dio.httpClientAdapter = HttpClientAdapter(
|
|
onHttpClientCreate: (client) {
|
|
client.findProxy = (uri) {
|
|
return 'PROXY ${uri.host}:${uri.port}';
|
|
};
|
|
return client;
|
|
},
|
|
);
|
|
} catch (e) {
|
|
print('Error configuring proxy: $e');
|
|
}
|
|
}
|
|
|
|
/// Update network configuration
|
|
void updateConfig(NetworkConfig config) {
|
|
_config = config;
|
|
_initializeDio();
|
|
}
|
|
|
|
Dio get dio => _dio;
|
|
|
|
/// Download file with resume support
|
|
Future<void> downloadFile({
|
|
required String url,
|
|
required String savePath,
|
|
required Function(int, int) onProgress,
|
|
CancelToken? cancelToken,
|
|
}) async {
|
|
try {
|
|
await _dio.download(
|
|
url,
|
|
savePath,
|
|
onReceiveProgress: onProgress,
|
|
cancelToken: cancelToken,
|
|
options: Options(
|
|
headers: {'Range': 'bytes=0-'},
|
|
),
|
|
);
|
|
} catch (e) {
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Stream request with large response handling
|
|
Future<void> streamRequest({
|
|
required String url,
|
|
required Function(List<int>) onData,
|
|
required VoidCallback onDone,
|
|
required Function(dynamic) onError,
|
|
CancelToken? cancelToken,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.get<ResponseBody>(
|
|
url,
|
|
options: Options(
|
|
responseType: ResponseType.stream,
|
|
receiveTimeout: const Duration(minutes: 5),
|
|
),
|
|
cancelToken: cancelToken,
|
|
);
|
|
|
|
final stream = response.data?.stream;
|
|
if (stream != null) {
|
|
stream.listen(
|
|
onData,
|
|
onError: onError,
|
|
onDone: onDone,
|
|
);
|
|
}
|
|
} catch (e) {
|
|
onError(e);
|
|
}
|
|
}
|
|
|
|
/// Clear cache
|
|
void clearCache() {
|
|
_cacheInterceptor.clearCache();
|
|
}
|
|
}
|
|
|
|
/// Interceptor for tracking bandwidth
|
|
class _BandwidthTrackingInterceptor extends Interceptor {
|
|
int _bytesReceived = 0;
|
|
DateTime _lastReset = DateTime.now();
|
|
|
|
int get bytesReceivedLastSecond {
|
|
final now = DateTime.now();
|
|
final secondsElapsed = now.difference(_lastReset).inSeconds;
|
|
if (secondsElapsed > 1) {
|
|
final bps = _bytesReceived ~/ secondsElapsed;
|
|
_bytesReceived = 0;
|
|
_lastReset = now;
|
|
return bps;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
@override
|
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
|
if (response.data is ResponseBody) {
|
|
// Track bytes for streaming responses
|
|
} else if (response.data is List<int>) {
|
|
_bytesReceived += (response.data as List<int>).length;
|
|
}
|
|
handler.next(response);
|
|
}
|
|
}
|
|
|
|
/// Interceptor for automatic retry on failure
|
|
class _RetryInterceptor extends Interceptor {
|
|
static const _maxRetries = 3;
|
|
|
|
@override
|
|
Future<void> onError(
|
|
DioException err, ErrorInterceptorHandler handler) async {
|
|
// Only retry on timeout or connection errors
|
|
if (err.type == DioExceptionType.connectionTimeout ||
|
|
err.type == DioExceptionType.receiveTimeout) {
|
|
final requestOptions = err.requestOptions;
|
|
int retryCount = requestOptions.extra['retryCount'] ?? 0;
|
|
|
|
if (retryCount < _maxRetries) {
|
|
requestOptions.extra['retryCount'] = retryCount + 1;
|
|
|
|
// Exponential backoff: 100ms, 200ms, 400ms
|
|
final delay = Duration(milliseconds: 100 * (1 << retryCount));
|
|
await Future.delayed(delay);
|
|
|
|
final dio = Dio();
|
|
try {
|
|
final response = await dio.request<dynamic>(
|
|
requestOptions.path,
|
|
options: Options(
|
|
method: requestOptions.method,
|
|
headers: requestOptions.headers,
|
|
),
|
|
data: requestOptions.data,
|
|
queryParameters: requestOptions.queryParameters,
|
|
);
|
|
return handler.resolve(response);
|
|
} on DioException catch (e) {
|
|
return handler.next(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
handler.next(err);
|
|
}
|
|
}
|
|
|
|
// Riverpod providers
|
|
|
|
final networkConfigProvider =
|
|
StateNotifierProvider<NetworkConfigNotifier, NetworkConfig>((ref) {
|
|
return NetworkConfigNotifier();
|
|
});
|
|
|
|
class NetworkConfigNotifier extends StateNotifier<NetworkConfig> {
|
|
NetworkConfigNotifier()
|
|
: super(const NetworkConfig(
|
|
userAgent: 'VLC/3.0.18 LibVLC/3.0.18',
|
|
));
|
|
|
|
void setProxy(String? proxyUrl) {
|
|
state = state.copyWith(proxyUrl: proxyUrl);
|
|
}
|
|
|
|
void setUserAgent(String userAgent) {
|
|
state = state.copyWith(userAgent: userAgent);
|
|
}
|
|
|
|
void addCustomHeader(String key, String value) {
|
|
final newHeaders = {...state.customHeaders};
|
|
newHeaders[key] = value;
|
|
state = state.copyWith(customHeaders: newHeaders);
|
|
}
|
|
|
|
void removeCustomHeader(String key) {
|
|
final newHeaders = {...state.customHeaders};
|
|
newHeaders.remove(key);
|
|
state = state.copyWith(customHeaders: newHeaders);
|
|
}
|
|
|
|
void clearCustomHeaders() {
|
|
state = state.copyWith(customHeaders: {});
|
|
}
|
|
}
|
|
|
|
final networkServiceProvider = Provider<OptimizedNetworkService>((ref) {
|
|
final config = ref.watch(networkConfigProvider);
|
|
return OptimizedNetworkService(config: config);
|
|
});
|