environment_monitoring_app/lib/services/retry_service.dart

232 lines
9.1 KiB
Dart

// lib/services/retry_service.dart
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import 'package:environment_monitoring_app/models/in_situ_sampling_data.dart';
import 'package:environment_monitoring_app/services/marine_in_situ_sampling_service.dart';
import 'package:environment_monitoring_app/models/river_in_situ_sampling_data.dart'; // ADDED
import 'package:environment_monitoring_app/services/river_in_situ_sampling_service.dart'; // ADDED
import 'package:environment_monitoring_app/services/api_service.dart';
import 'package:environment_monitoring_app/services/base_api_service.dart';
import 'package:environment_monitoring_app/services/ftp_service.dart';
import 'package:environment_monitoring_app/services/server_config_service.dart';
import 'package:environment_monitoring_app/auth_provider.dart';
/// A dedicated service to manage the queue of failed API, FTP, and complex submission tasks.
class RetryService {
final DatabaseHelper _dbHelper = DatabaseHelper();
final BaseApiService _baseApiService = BaseApiService();
final FtpService _ftpService = FtpService();
final ServerConfigService _serverConfigService = ServerConfigService();
// --- START: MODIFICATION FOR HANDLING COMPLEX TASKS ---
// These services will be provided after the RetryService is created.
MarineInSituSamplingService? _marineInSituService;
RiverInSituSamplingService? _riverInSituService; // ADDED
AuthProvider? _authProvider;
// Call this method from your main app setup to provide the necessary services.
void initialize({
required MarineInSituSamplingService marineInSituService,
required RiverInSituSamplingService riverInSituService, // ADDED
required AuthProvider authProvider,
}) {
_marineInSituService = marineInSituService;
_riverInSituService = riverInSituService; // ADDED
_authProvider = authProvider;
}
// --- END: MODIFICATION FOR HANDLING COMPLEX TASKS ---
/// Adds a generic, complex task to the queue, to be handled by a background processor.
Future<void> queueTask({
required String type,
required Map<String, dynamic> payload,
}) async {
await _dbHelper.queueFailedRequest({
'type': type,
'endpoint_or_path': 'N/A',
'payload': jsonEncode(payload),
'timestamp': DateTime.now().toIso8601String(),
'status': 'pending',
});
debugPrint("Task of type '$type' has been queued for background processing.");
}
/// Adds a failed API request to the local database queue.
Future<void> addApiToQueue({
required String endpoint,
required String method,
Map<String, dynamic>? body,
Map<String, String>? fields,
Map<String, File>? files,
}) async {
final serializableFiles = files?.map((key, value) => MapEntry(key, value.path));
final payload = {
'method': method,
'body': body,
'fields': fields,
'files': serializableFiles,
};
await _dbHelper.queueFailedRequest({
'type': 'api',
'endpoint_or_path': endpoint,
'payload': jsonEncode(payload),
'timestamp': DateTime.now().toIso8601String(),
'status': 'pending',
});
debugPrint("API request for endpoint '$endpoint' has been queued for retry.");
}
/// Adds a failed FTP upload to the local database queue.
Future<void> addFtpToQueue({
required String localFilePath,
required String remotePath,
}) async {
final payload = {'localFilePath': localFilePath};
await _dbHelper.queueFailedRequest({
'type': 'ftp',
'endpoint_or_path': remotePath,
'payload': jsonEncode(payload),
'timestamp': DateTime.now().toIso8601String(),
'status': 'pending',
});
debugPrint("FTP upload for file '$localFilePath' has been queued for retry.");
}
/// Retrieves all tasks currently in the 'pending' state from the queue.
Future<List<Map<String, dynamic>>> getPendingTasks() {
return _dbHelper.getPendingRequests();
}
/// Attempts to re-execute a single failed task from the queue.
/// Returns `true` on success, `false` on failure.
Future<bool> retryTask(int taskId) async {
final task = await _dbHelper.getRequestById(taskId);
if (task == null) {
debugPrint("Retry failed: Task with ID $taskId not found in the queue.");
return false;
}
bool success = false;
final payload = jsonDecode(task['payload'] as String);
try {
if (_authProvider == null) {
debugPrint("RetryService has not been initialized. Cannot process task.");
return false;
}
if (task['type'] == 'insitu_submission') {
debugPrint("Retrying complex task 'insitu_submission' with ID $taskId.");
if (_marineInSituService == null) return false;
final String logFilePath = payload['localLogPath'];
final file = File(logFilePath);
if (!await file.exists()) {
debugPrint("Retry failed: Source log file no longer exists at $logFilePath");
await _dbHelper.deleteRequestFromQueue(taskId); // Remove invalid task
return false;
}
final content = await file.readAsString();
final jsonData = jsonDecode(content) as Map<String, dynamic>;
final InSituSamplingData dataToResubmit = InSituSamplingData.fromJson(jsonData);
final String logDirectoryPath = p.dirname(logFilePath);
final result = await _marineInSituService!.submitInSituSample(
data: dataToResubmit,
appSettings: _authProvider!.appSettings,
authProvider: _authProvider!,
logDirectory: logDirectoryPath,
);
success = result['success'];
// --- START: ADDED LOGIC FOR RIVER IN-SITU SUBMISSION ---
} else if (task['type'] == 'river_insitu_submission') {
debugPrint("Retrying complex task 'river_insitu_submission' with ID $taskId.");
if (_riverInSituService == null) return false;
final String logFilePath = payload['localLogPath'];
final file = File(logFilePath);
if (!await file.exists()) {
debugPrint("Retry failed: Source log file no longer exists at $logFilePath");
await _dbHelper.deleteRequestFromQueue(taskId); // Remove invalid task
return false;
}
final content = await file.readAsString();
final jsonData = jsonDecode(content) as Map<String, dynamic>;
final RiverInSituSamplingData dataToResubmit = RiverInSituSamplingData.fromJson(jsonData);
final String logDirectoryPath = p.dirname(logFilePath);
final result = await _riverInSituService!.submitData(
data: dataToResubmit,
appSettings: _authProvider!.appSettings,
authProvider: _authProvider!,
logDirectory: logDirectoryPath,
);
success = result['success'];
// --- END: ADDED LOGIC FOR RIVER IN-SITU SUBMISSION ---
} else if (task['type'] == 'api') {
final endpoint = task['endpoint_or_path'] as String;
final method = payload['method'] as String;
final baseUrl = await _serverConfigService.getActiveApiUrl();
debugPrint("Retrying API task $taskId: $method to $baseUrl/$endpoint");
Map<String, dynamic> result;
if (method == 'POST_MULTIPART') {
final Map<String, String> fields = Map<String, String>.from(payload['fields'] ?? {});
final Map<String, File> files = (payload['files'] as Map<String, dynamic>?)
?.map((key, value) => MapEntry(key, File(value as String))) ?? {};
result = await _baseApiService.postMultipart(baseUrl: baseUrl, endpoint: endpoint, fields: fields, files: files);
} else {
final Map<String, dynamic> body = Map<String, dynamic>.from(payload['body'] ?? {});
result = await _baseApiService.post(baseUrl, endpoint, body);
}
success = result['success'];
} else if (task['type'] == 'ftp') {
final remotePath = task['endpoint_or_path'] as String;
final localFile = File(payload['localFilePath'] as String);
debugPrint("Retrying FTP task $taskId: Uploading ${localFile.path} to $remotePath");
if (await localFile.exists()) {
final ftpConfigs = await _dbHelper.loadFtpConfigs() ?? [];
if (ftpConfigs.isEmpty) {
debugPrint("Retry failed for FTP task $taskId: No FTP configurations found.");
return false;
}
for (final config in ftpConfigs) {
final result = await _ftpService.uploadFile(config: config, fileToUpload: localFile, remotePath: remotePath);
if (result['success']) {
success = true;
break;
}
}
} else {
debugPrint("Retry failed for FTP task $taskId: Source file no longer exists at ${localFile.path}");
success = false;
}
}
} catch (e) {
debugPrint("A critical error occurred while retrying task $taskId: $e");
success = false;
}
if (success) {
debugPrint("Task $taskId completed successfully. Removing from queue.");
await _dbHelper.deleteRequestFromQueue(taskId);
} else {
debugPrint("Retry attempt for task $taskId failed. It will remain in the queue.");
}
return success;
}
}