167 lines
4.6 KiB
Dart
167 lines
4.6 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:worldhopper/models/opds_server.dart';
|
|
import 'package:worldhopper/services/auth_service.dart';
|
|
import 'package:worldhopper/services/opds_parser.dart'
|
|
show OPDSParser, ParsedFeed;
|
|
|
|
/// Service for fetching OPDS feeds over HTTP
|
|
class OPDSService {
|
|
final AuthService _authService;
|
|
final OPDSParser _parser;
|
|
final Map<String, Dio> _clients = {};
|
|
|
|
OPDSService({
|
|
AuthService? authService,
|
|
OPDSParser? parser,
|
|
}) : _authService = authService ?? AuthService(),
|
|
_parser = parser ?? OPDSParser();
|
|
|
|
/// Get or create a Dio client for a server
|
|
Dio _getClient(OPDSServer server) {
|
|
if (!_clients.containsKey(server.id)) {
|
|
_clients[server.id] = _authService.createAuthenticatedClient(server);
|
|
}
|
|
return _clients[server.id]!;
|
|
}
|
|
|
|
/// Fetch and parse an OPDS feed from a URL
|
|
Future<ParsedFeed> fetchFeed(
|
|
OPDSServer server,
|
|
String url, {
|
|
CancelToken? cancelToken,
|
|
}) async {
|
|
try {
|
|
final client = _getClient(server);
|
|
|
|
// Log the URL being fetched for debugging
|
|
debugPrint('Fetching OPDS feed: $url');
|
|
|
|
final response = await client.get(
|
|
url,
|
|
cancelToken: cancelToken,
|
|
options: Options(
|
|
responseType: ResponseType.plain,
|
|
),
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final xmlContent = response.data as String;
|
|
return _parser.parseFeed(xmlContent, server: server);
|
|
} else {
|
|
throw OPDSException(
|
|
'Failed to fetch feed: HTTP ${response.statusCode}',
|
|
statusCode: response.statusCode,
|
|
);
|
|
}
|
|
} on DioException catch (e) {
|
|
debugPrint(
|
|
'DioException fetching feed from $url: ${e.type}, ${e.message}');
|
|
throw _handleDioException(e);
|
|
} catch (e) {
|
|
debugPrint('Error fetching feed from $url: $e');
|
|
throw OPDSException('Error fetching feed: $e');
|
|
}
|
|
}
|
|
|
|
/// Fetch the root feed for a server
|
|
Future<ParsedFeed> fetchRootFeed(
|
|
OPDSServer server, {
|
|
CancelToken? cancelToken,
|
|
}) async {
|
|
return fetchFeed(server, server.url, cancelToken: cancelToken);
|
|
}
|
|
|
|
/// Test connection to a server
|
|
Future<bool> testConnection(
|
|
OPDSServer server, {
|
|
CancelToken? cancelToken,
|
|
}) async {
|
|
try {
|
|
await fetchRootFeed(server, cancelToken: cancelToken);
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Clear cached clients (useful when server credentials change)
|
|
void clearClients() {
|
|
_clients.clear();
|
|
}
|
|
|
|
/// Clear client for a specific server
|
|
void clearClient(String serverId) {
|
|
_clients.remove(serverId);
|
|
}
|
|
|
|
/// Handle Dio exceptions and convert to OPDSException
|
|
OPDSException _handleDioException(DioException e) {
|
|
switch (e.type) {
|
|
case DioExceptionType.connectionTimeout:
|
|
case DioExceptionType.sendTimeout:
|
|
case DioExceptionType.receiveTimeout:
|
|
return OPDSException(
|
|
'Connection timeout. Please check your network connection.',
|
|
statusCode: null,
|
|
isNetworkError: true,
|
|
);
|
|
|
|
case DioExceptionType.badResponse:
|
|
final statusCode = e.response?.statusCode;
|
|
if (statusCode == 401 || statusCode == 403) {
|
|
return OPDSException(
|
|
'Authentication failed. Please check your credentials.',
|
|
statusCode: statusCode,
|
|
isAuthError: true,
|
|
);
|
|
} else if (statusCode == 404) {
|
|
return OPDSException(
|
|
'Feed not found (404).',
|
|
statusCode: statusCode,
|
|
);
|
|
} else {
|
|
return OPDSException(
|
|
'Server error: HTTP $statusCode',
|
|
statusCode: statusCode,
|
|
);
|
|
}
|
|
|
|
case DioExceptionType.cancel:
|
|
return OPDSException('Request cancelled');
|
|
|
|
case DioExceptionType.connectionError:
|
|
return OPDSException(
|
|
'Connection error: ${e.message ?? "Could not connect to server"}',
|
|
isNetworkError: true,
|
|
);
|
|
|
|
case DioExceptionType.unknown:
|
|
return OPDSException(
|
|
'Network error: ${e.message ?? e.error?.toString() ?? "Unknown error"}',
|
|
isNetworkError: true,
|
|
);
|
|
|
|
default:
|
|
return OPDSException('Error: ${e.message ?? "Unknown error"}');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Custom exception for OPDS-related errors
|
|
class OPDSException implements Exception {
|
|
final String message;
|
|
final int? statusCode;
|
|
final bool isNetworkError;
|
|
final bool isAuthError;
|
|
|
|
OPDSException(
|
|
this.message, {
|
|
this.statusCode,
|
|
this.isNetworkError = false,
|
|
this.isAuthError = false,
|
|
});
|
|
|
|
@override
|
|
String toString() => message;
|
|
}
|