environment_monitoring_app/lib/services/retry_service.dart

711 lines
32 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';
import 'package:environment_monitoring_app/services/river_in_situ_sampling_service.dart';
import 'package:environment_monitoring_app/models/river_inves_manual_sampling_data.dart';
import 'package:environment_monitoring_app/services/river_investigative_sampling_service.dart';
import 'package:environment_monitoring_app/models/marine_inves_manual_sampling_data.dart';
import 'package:environment_monitoring_app/services/marine_investigative_sampling_service.dart';
import 'package:environment_monitoring_app/models/tarball_data.dart';
import 'package:environment_monitoring_app/services/marine_tarball_sampling_service.dart';
// --- MARINE REPORT IMPORTS ---
import 'package:environment_monitoring_app/models/marine_manual_npe_report_data.dart';
import 'package:environment_monitoring_app/services/marine_npe_report_service.dart';
import 'package:environment_monitoring_app/models/marine_manual_pre_departure_checklist_data.dart';
import 'package:environment_monitoring_app/services/marine_manual_pre_departure_service.dart';
import 'package:environment_monitoring_app/models/marine_manual_sonde_calibration_data.dart';
import 'package:environment_monitoring_app/services/marine_manual_sonde_calibration_service.dart';
import 'package:environment_monitoring_app/models/marine_manual_equipment_maintenance_data.dart';
import 'package:environment_monitoring_app/services/marine_manual_equipment_maintenance_service.dart';
// --- END MARINE REPORT IMPORTS ---
import 'package:environment_monitoring_app/services/database_helper.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';
import 'package:environment_monitoring_app/services/user_preferences_service.dart'; // ADDED
/// 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();
final UserPreferencesService _userPreferencesService = UserPreferencesService(); // ADDED
bool _isProcessing = false;
// Sampling Services
MarineInSituSamplingService? _marineInSituService;
RiverInSituSamplingService? _riverInSituService;
MarineInvestigativeSamplingService? _marineInvestigativeService;
RiverInvestigativeSamplingService? _riverInvestigativeService;
MarineTarballSamplingService? _marineTarballService;
// Report Services
MarineNpeReportService? _marineNpeService;
// *** ADDED: Other Marine Report Services ***
MarineManualPreDepartureService? _marinePreDepartureService;
MarineManualSondeCalibrationService? _marineSondeCalibrationService;
MarineManualEquipmentMaintenanceService? _marineEquipmentMaintenanceService;
// *** END ADDED ***
AuthProvider? _authProvider;
void initialize({
required MarineInSituSamplingService marineInSituService,
required RiverInSituSamplingService riverInSituService,
required MarineInvestigativeSamplingService marineInvestigativeService,
required RiverInvestigativeSamplingService riverInvestigativeService,
required MarineTarballSamplingService marineTarballService,
// Added Marine Report Services
required MarineNpeReportService marineNpeService,
required MarineManualPreDepartureService marinePreDepartureService,
required MarineManualSondeCalibrationService marineSondeCalibrationService,
required MarineManualEquipmentMaintenanceService marineEquipmentMaintenanceService,
required AuthProvider authProvider,
}) {
_marineInSituService = marineInSituService;
_riverInSituService = riverInSituService;
_marineInvestigativeService = marineInvestigativeService;
_riverInvestigativeService = riverInvestigativeService;
_marineTarballService = marineTarballService;
_marineNpeService = marineNpeService;
_marinePreDepartureService = marinePreDepartureService;
_marineSondeCalibrationService = marineSondeCalibrationService;
_marineEquipmentMaintenanceService = marineEquipmentMaintenanceService;
_authProvider = authProvider;
}
/// 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,
required int ftpConfigId,
}) async {
final payload = {
'localFilePath': localFilePath,
'ftpConfigId': ftpConfigId,
};
await _dbHelper.queueFailedRequest({
'type': 'ftp',
'endpoint_or_path': remotePath,
'payload': jsonEncode(payload),
'timestamp': DateTime.now().toIso8601String(),
'status': 'pending',
});
debugPrint("FTP upload for file '$localFilePath' to config ID $ftpConfigId has been queued for retry.");
}
Future<List<Map<String, dynamic>>> getPendingTasks() {
return _dbHelper.getPendingRequests();
}
Future<void> processRetryQueue() async {
if (_isProcessing) {
debugPrint("[RetryService] ⏳ Queue is already being processed. Skipping.");
return;
}
_isProcessing = true;
debugPrint("[RetryService] ▶️ Starting to process main retry queue...");
final pendingTasks = await getPendingTasks();
if (pendingTasks.isEmpty) {
debugPrint("[RetryService] ⏹️ Queue is empty. Nothing to process.");
_isProcessing = false;
return;
}
if (_authProvider == null || !await _authProvider!.isConnected()) {
debugPrint("[RetryService] ❌ No internet connection. Aborting queue processing.");
_isProcessing = false;
return;
}
debugPrint("[RetryService] 🔎 Found ${pendingTasks.length} pending tasks.");
for (final task in pendingTasks) {
if (await _dbHelper.getRequestById(task['id'] as int) != null) {
await retryTask(task['id'] as int);
}
}
debugPrint("[RetryService] ⏹️ Finished processing retry queue.");
_isProcessing = false;
}
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;
Map<String, dynamic> payload;
final String taskType = task['type'] as String;
try {
payload = jsonDecode(task['payload'] as String);
} catch (e) {
debugPrint("Error decoding payload for task $taskId (Type: $taskType): $e. Removing invalid task.");
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
try {
if (_authProvider == null) {
debugPrint("RetryService has not been initialized. Cannot process task $taskId.");
return false;
}
// --- Complex Task Handlers ---
if (taskType == 'insitu_submission') {
if (_marineInSituService == null) return false;
final String logDirectoryPath = payload['localLogPath'];
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
final file = File(jsonFilePath);
if (!await file.exists()) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final content = await file.readAsString();
final result = await _marineInSituService!.submitInSituSample(
data: InSituSamplingData.fromJson(jsonDecode(content)),
appSettings: _authProvider!.appSettings,
authProvider: _authProvider!,
logDirectory: logDirectoryPath,
);
success = result['success'];
} else if (taskType == 'river_insitu_submission') {
if (_riverInSituService == null) return false;
final String jsonFilePath = payload['localLogPath'];
final file = File(jsonFilePath);
if (!await file.exists()) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final result = await _riverInSituService!.submitData(
data: RiverInSituSamplingData.fromJson(jsonDecode(await file.readAsString())),
appSettings: _authProvider!.appSettings,
authProvider: _authProvider!,
logDirectory: p.dirname(jsonFilePath),
);
success = result['success'];
} else if (taskType == 'river_investigative_submission') {
if (_riverInvestigativeService == null) return false;
final String jsonFilePath = payload['localLogPath'];
final file = File(jsonFilePath);
if (!await file.exists()) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final result = await _riverInvestigativeService!.submitData(
data: RiverInvesManualSamplingData.fromJson(jsonDecode(await file.readAsString())),
appSettings: _authProvider!.appSettings,
authProvider: _authProvider!,
logDirectory: p.dirname(jsonFilePath),
);
success = result['success'];
} else if (taskType == 'investigative_submission') {
if (_marineInvestigativeService == null) return false;
final String logDirectoryPath = payload['localLogPath'];
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
final file = File(jsonFilePath);
if (!await file.exists()) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final result = await _marineInvestigativeService!.submitInvestigativeSample(
data: MarineInvesManualSamplingData.fromJson(jsonDecode(await file.readAsString())),
appSettings: _authProvider!.appSettings,
authProvider: _authProvider!,
logDirectory: logDirectoryPath,
);
success = result['success'];
} else if (taskType == 'tarball_submission') {
if (_marineTarballService == null) return false;
final String logDirectoryPath = payload['localLogPath'];
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
final file = File(jsonFilePath);
if (!await file.exists()) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final jsonData = jsonDecode(await file.readAsString());
// --- START: Manual Reconstruction of Tarball Data (Fixing .fromJson error) ---
final TarballSamplingData dataToResubmit = TarballSamplingData();
dataToResubmit.firstSampler = jsonData['firstSampler'];
dataToResubmit.firstSamplerUserId = jsonData['firstSamplerUserId'];
dataToResubmit.secondSampler = jsonData['secondSampler'];
dataToResubmit.samplingDate = jsonData['samplingDate'];
dataToResubmit.samplingTime = jsonData['samplingTime'];
dataToResubmit.selectedStateName = jsonData['selectedStateName'];
dataToResubmit.selectedCategoryName = jsonData['selectedCategoryName'];
dataToResubmit.selectedStation = jsonData['selectedStation'];
dataToResubmit.stationLatitude = jsonData['stationLatitude'];
dataToResubmit.stationLongitude = jsonData['stationLongitude'];
dataToResubmit.currentLatitude = jsonData['currentLatitude'];
dataToResubmit.currentLongitude = jsonData['currentLongitude'];
dataToResubmit.distanceDifference = (jsonData['distanceDifference'] as num?)?.toDouble();
dataToResubmit.distanceDifferenceRemarks = jsonData['distanceDifferenceRemarks'];
dataToResubmit.classificationId = jsonData['classificationId'];
dataToResubmit.selectedClassification = jsonData['selectedClassification'];
dataToResubmit.optionalRemark1 = jsonData['optionalRemark1'];
dataToResubmit.optionalRemark2 = jsonData['optionalRemark2'];
dataToResubmit.optionalRemark3 = jsonData['optionalRemark3'];
dataToResubmit.optionalRemark4 = jsonData['optionalRemark4'];
dataToResubmit.reportId = jsonData['reportId'];
dataToResubmit.submissionStatus = jsonData['submissionStatus'];
dataToResubmit.submissionMessage = jsonData['submissionMessage'];
// Helper to create File from path if it exists
File? fileFromPath(dynamic path) => (path is String && path.isNotEmpty) ? File(path) : null;
dataToResubmit.leftCoastalViewImage = fileFromPath(jsonData['leftCoastalViewImage']);
dataToResubmit.rightCoastalViewImage = fileFromPath(jsonData['rightCoastalViewImage']);
dataToResubmit.verticalLinesImage = fileFromPath(jsonData['verticalLinesImage']);
dataToResubmit.horizontalLineImage = fileFromPath(jsonData['horizontalLineImage']);
dataToResubmit.optionalImage1 = fileFromPath(jsonData['optionalImage1']);
dataToResubmit.optionalImage2 = fileFromPath(jsonData['optionalImage2']);
dataToResubmit.optionalImage3 = fileFromPath(jsonData['optionalImage3']);
dataToResubmit.optionalImage4 = fileFromPath(jsonData['optionalImage4']);
// --- END: Manual Reconstruction ---
final result = await _marineTarballService!.submitTarballSample(
data: dataToResubmit,
appSettings: _authProvider!.appSettings,
context: null,
logDirectory: logDirectoryPath,
);
success = result['success'];
// =======================================================================
// MARINE REPORT HANDLERS
// =======================================================================
} else if (taskType == 'marine_npe_submission' || taskType == 'npe_submission') {
if (_marineNpeService == null) return false;
final String logDirectoryPath = payload['localLogPath'];
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
final file = File(jsonFilePath);
if (!await file.exists()) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final content = await file.readAsString();
final jsonData = jsonDecode(content) as Map<String, dynamic>;
final MarineManualNpeReportData dataToResubmit = MarineManualNpeReportData();
// Reconstruction logic
dataToResubmit.firstSamplerName = jsonData['firstSamplerName'];
dataToResubmit.firstSamplerUserId = jsonData['firstSamplerUserId'];
dataToResubmit.eventDate = jsonData['eventDate'];
dataToResubmit.eventTime = jsonData['eventTime'];
dataToResubmit.sourceOrigin = jsonData['sourceOrigin'];
dataToResubmit.locationDescription = jsonData['locationDescription'];
dataToResubmit.stateName = jsonData['stateName'];
dataToResubmit.selectedStation = jsonData['selectedStation'];
dataToResubmit.latitude = jsonData['latitude'];
dataToResubmit.longitude = jsonData['longitude'];
dataToResubmit.oxygenSaturation = jsonData['oxygenSaturation'];
dataToResubmit.electricalConductivity = jsonData['electricalConductivity'];
dataToResubmit.oxygenConcentration = jsonData['oxygenConcentration'];
dataToResubmit.turbidity = jsonData['turbidity'];
dataToResubmit.ph = jsonData['ph'];
dataToResubmit.temperature = jsonData['temperature'];
if (jsonData['fieldObservations'] != null) {
dataToResubmit.fieldObservations = Map<String, bool>.from(jsonData['fieldObservations']);
}
dataToResubmit.othersObservationRemark = jsonData['othersObservationRemark'];
dataToResubmit.possibleSource = jsonData['possibleSource'];
dataToResubmit.image1Remark = jsonData['image1Remark'];
dataToResubmit.image2Remark = jsonData['image2Remark'];
dataToResubmit.image3Remark = jsonData['image3Remark'];
dataToResubmit.image4Remark = jsonData['image4Remark'];
dataToResubmit.tarballClassificationId = jsonData['tarballClassificationId'];
dataToResubmit.selectedTarballClassification = jsonData['selectedTarballClassification'];
dataToResubmit.reportId = jsonData['reportId'];
if (jsonData['npe_image_1'] != null) dataToResubmit.image1 = File(jsonData['npe_image_1']);
if (jsonData['npe_image_2'] != null) dataToResubmit.image2 = File(jsonData['npe_image_2']);
if (jsonData['npe_image_3'] != null) dataToResubmit.image3 = File(jsonData['npe_image_3']);
if (jsonData['npe_image_4'] != null) dataToResubmit.image4 = File(jsonData['npe_image_4']);
final result = await _marineNpeService!.submitNpeReport(
data: dataToResubmit,
authProvider: _authProvider!,
logDirectory: logDirectoryPath,
);
success = result['success'];
// *** START: ADDED Pre-Departure Checklist ***
} else if (taskType == 'pre_departure_submission') {
if (_marinePreDepartureService == null) return false;
final String logDirectoryPath = payload['localLogPath'];
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
final file = File(jsonFilePath);
if (!await file.exists()) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final content = await file.readAsString();
final jsonData = jsonDecode(content) as Map<String, dynamic>;
final MarineManualPreDepartureChecklistData dataToResubmit = MarineManualPreDepartureChecklistData();
// Reconstruct Data from JSON
dataToResubmit.reporterName = jsonData['reporterName'];
dataToResubmit.reporterUserId = jsonData['reporterUserId'];
dataToResubmit.submissionDate = jsonData['submissionDate'];
dataToResubmit.location = jsonData['location'];
if (jsonData['checklistItems'] != null) {
dataToResubmit.checklistItems = Map<String, bool>.from(jsonData['checklistItems']);
}
if (jsonData['remarks'] != null) {
dataToResubmit.remarks = Map<String, String>.from(jsonData['remarks']);
}
dataToResubmit.reportId = jsonData['reportId'];
final result = await _marinePreDepartureService!.submitChecklist(
data: dataToResubmit,
authProvider: _authProvider!,
appSettings: _authProvider!.appSettings,
logDirectory: logDirectoryPath,
);
success = result['success'];
// *** END: ADDED Pre-Departure Checklist ***
// *** START: ADDED Sonde Calibration ***
} else if (taskType == 'sonde_calibration_submission') {
if (_marineSondeCalibrationService == null) return false;
final String logDirectoryPath = payload['localLogPath'];
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
final file = File(jsonFilePath);
if (!await file.exists()) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final content = await file.readAsString();
final jsonData = jsonDecode(content) as Map<String, dynamic>;
final MarineManualSondeCalibrationData dataToResubmit = MarineManualSondeCalibrationData();
// Reconstruct Data
dataToResubmit.calibratedByUserId = jsonData['calibratedByUserId'];
dataToResubmit.calibratedByUserName = jsonData['calibratedByUserName'];
dataToResubmit.sondeSerialNumber = jsonData['sondeSerialNumber'];
dataToResubmit.firmwareVersion = jsonData['firmwareVersion'];
dataToResubmit.korVersion = jsonData['korVersion'];
dataToResubmit.location = jsonData['location'];
dataToResubmit.startDateTime = jsonData['startDateTime'];
dataToResubmit.endDateTime = jsonData['endDateTime'];
// Cast to double as JSON decodes numbers as int if no decimal places
dataToResubmit.ph7Mv = (jsonData['ph_7_mv'] as num?)?.toDouble();
dataToResubmit.ph7Before = (jsonData['ph_7_before'] as num?)?.toDouble();
dataToResubmit.ph7After = (jsonData['ph_7_after'] as num?)?.toDouble();
dataToResubmit.ph10Mv = (jsonData['ph_10_mv'] as num?)?.toDouble();
dataToResubmit.ph10Before = (jsonData['ph_10_before'] as num?)?.toDouble();
dataToResubmit.ph10After = (jsonData['ph_10_after'] as num?)?.toDouble();
dataToResubmit.condBefore = (jsonData['cond_before'] as num?)?.toDouble();
dataToResubmit.condAfter = (jsonData['cond_after'] as num?)?.toDouble();
dataToResubmit.doBefore = (jsonData['do_before'] as num?)?.toDouble();
dataToResubmit.doAfter = (jsonData['do_after'] as num?)?.toDouble();
dataToResubmit.turbidity0Before = (jsonData['turbidity_0_before'] as num?)?.toDouble();
dataToResubmit.turbidity0After = (jsonData['turbidity_0_after'] as num?)?.toDouble();
dataToResubmit.turbidity124Before = (jsonData['turbidity_124_before'] as num?)?.toDouble();
dataToResubmit.turbidity124After = (jsonData['turbidity_124_after'] as num?)?.toDouble();
dataToResubmit.calibrationStatus = jsonData['calibration_status'];
dataToResubmit.remarks = jsonData['remarks'];
dataToResubmit.reportId = jsonData['reportId'];
final result = await _marineSondeCalibrationService!.submitCalibration(
data: dataToResubmit,
authProvider: _authProvider!,
appSettings: _authProvider!.appSettings,
logDirectory: logDirectoryPath,
);
success = result['success'];
// *** END: ADDED Sonde Calibration ***
// *** START: ADDED Equipment Maintenance ***
} else if (taskType == 'equipment_maintenance_submission') {
if (_marineEquipmentMaintenanceService == null) return false;
final String logDirectoryPath = payload['localLogPath'];
final jsonFilePath = p.join(logDirectoryPath, 'data.json');
final file = File(jsonFilePath);
if (!await file.exists()) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final content = await file.readAsString();
final jsonData = jsonDecode(content) as Map<String, dynamic>;
final MarineManualEquipmentMaintenanceData dataToResubmit = MarineManualEquipmentMaintenanceData();
// Reconstruct Data
dataToResubmit.conductedByUserId = jsonData['conductedByUserId'];
dataToResubmit.conductedByUserName = jsonData['conductedByUserName'];
dataToResubmit.maintenanceDate = jsonData['maintenanceDate'];
dataToResubmit.lastMaintenanceDate = jsonData['lastMaintenanceDate'];
dataToResubmit.scheduleMaintenance = jsonData['scheduleMaintenance'];
dataToResubmit.isReplacement = jsonData['isReplacement'] ?? false;
dataToResubmit.timeStart = jsonData['timeStart'];
dataToResubmit.timeEnd = jsonData['timeEnd'];
dataToResubmit.location = jsonData['location'];
if (jsonData['ysiSondeChecks'] != null) {
dataToResubmit.ysiSondeChecks = Map<String, bool>.from(jsonData['ysiSondeChecks']);
}
dataToResubmit.ysiSondeComments = jsonData['ysiSondeComments'];
// Handle nested maps for Sensor Checks
if (jsonData['ysiSensorChecks'] != null) {
dataToResubmit.ysiSensorChecks = (jsonData['ysiSensorChecks'] as Map<String, dynamic>).map(
(key, value) => MapEntry(key, Map<String, bool>.from(value)),
);
}
dataToResubmit.ysiSensorComments = jsonData['ysiSensorComments'];
// Handle nested maps for Replacements
if (jsonData['ysiReplacements'] != null) {
dataToResubmit.ysiReplacements = (jsonData['ysiReplacements'] as Map<String, dynamic>).map(
(key, value) => MapEntry(key, Map<String, String>.from(value)),
);
}
if (jsonData['vanDornChecks'] != null) {
dataToResubmit.vanDornChecks = (jsonData['vanDornChecks'] as Map<String, dynamic>).map(
(key, value) => MapEntry(key, Map<String, bool>.from(value)),
);
}
dataToResubmit.vanDornComments = jsonData['vanDornComments'];
dataToResubmit.vanDornCurrentSerial = jsonData['vanDornCurrentSerial'];
dataToResubmit.vanDornNewSerial = jsonData['vanDornNewSerial'];
if (jsonData['vanDornReplacements'] != null) {
dataToResubmit.vanDornReplacements = (jsonData['vanDornReplacements'] as Map<String, dynamic>).map(
(key, value) => MapEntry(key, Map<String, String>.from(value)),
);
}
dataToResubmit.reportId = jsonData['reportId'];
final result = await _marineEquipmentMaintenanceService!.submitMaintenanceReport(
data: dataToResubmit,
authProvider: _authProvider!,
appSettings: _authProvider!.appSettings,
logDirectory: logDirectoryPath,
);
success = result['success'];
// *** END: ADDED Equipment Maintenance ***
// =======================================================================
// SIMPLE API / FTP HANDLERS
// =======================================================================
} else if (taskType == 'api') {
final endpoint = task['endpoint_or_path'] as String;
final method = payload['method'] as String;
final baseUrl = await _serverConfigService.getActiveApiUrl();
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))) ?? {};
bool allFilesExist = true;
for (var entry in files.entries) {
if (!await entry.value.exists()) {
allFilesExist = false;
}
}
if (!allFilesExist) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
final result = await _baseApiService.postMultipart(baseUrl: baseUrl, endpoint: endpoint, fields: fields, files: files);
success = result['success'];
} else {
final Map<String, dynamic> body = Map<String, dynamic>.from(payload['body'] ?? {});
final result = await _baseApiService.post(baseUrl, endpoint, body);
success = result['success'];
}
} else if (taskType == 'ftp') {
final remotePath = task['endpoint_or_path'] as String;
final localFile = File(payload['localFilePath'] as String);
final int? ftpConfigId = payload['ftpConfigId'] as int?;
if (ftpConfigId == null) {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
// --- START FIX: Check if this FTP module is enabled in preferences ---
final ftpConfigs = await _dbHelper.loadFtpConfigs() ?? [];
final config = ftpConfigs.firstWhere(
(c) => c['ftp_config_id'] == ftpConfigId,
orElse: () => <String, dynamic>{},
);
if (config.isNotEmpty) {
String? moduleKey = config['ftp_module'];
// Map legacy module names if needed (e.g., river_manual -> river_in_situ)
if (moduleKey == 'river_manual') {
moduleKey = 'river_in_situ';
} else if (moduleKey == 'marine_manual') {
moduleKey = 'marine_in_situ';
}
// Add other mappings if needed for consistency with user preferences keys
if (moduleKey != null) {
final pref = await _userPreferencesService.getModulePreference(moduleKey);
final bool isFtpEnabled = pref?['is_ftp_enabled'] ?? true;
if (!isFtpEnabled) {
debugPrint("RetryService: FTP upload for module '$moduleKey' is disabled by user. Removing task $taskId.");
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
}
}
// --- END FIX ---
if (await localFile.exists()) {
if (config.isEmpty) return false; // Config missing
final result = await _ftpService.uploadFile(config: config, fileToUpload: localFile, remotePath: remotePath);
success = result['success'];
} else {
await _dbHelper.deleteRequestFromQueue(taskId);
return false;
}
} else {
debugPrint("Unknown task type '$taskType' for task ID $taskId. Cannot retry. Removing task.");
await _dbHelper.deleteRequestFromQueue(taskId);
}
} on SessionExpiredException catch (e) {
debugPrint("Session expired during retry attempt: $e");
success = false;
} catch (e, stacktrace) {
debugPrint("A critical error occurred while retrying task $taskId: $e");
debugPrint("Stacktrace: $stacktrace");
success = false;
}
if (success) {
debugPrint("Task $taskId (Type: $taskType) completed successfully. Removing from queue.");
await _dbHelper.deleteRequestFromQueue(taskId);
if (taskType.endsWith('_submission') && payload['localLogPath'] != null) {
String pathToCheck = payload['localLogPath'];
bool isDirectory = await Directory(pathToCheck).exists();
if (!isDirectory && pathToCheck.endsWith('.json')) {
pathToCheck = p.dirname(pathToCheck);
isDirectory = true;
}
_cleanUpTemporaryZipFiles(pathToCheck, isDirectory: isDirectory);
}
if (taskType == 'ftp' && payload['localFilePath'] != null && (payload['localFilePath'] as String).endsWith('.zip')) {
_cleanUpTemporaryZipFiles(payload['localFilePath'], isDirectory: false);
}
} else {
debugPrint("Retry attempt for task $taskId (Type: $taskType) failed. It will remain in the queue.");
}
return success;
}
void _cleanUpTemporaryZipFiles(String path, {required bool isDirectory}) async {
try {
if (isDirectory) {
final dir = Directory(path);
if (await dir.exists()) {
final filesInDir = dir.listSync();
for (var entity in filesInDir) {
if (entity is File && entity.path.endsWith('.zip')) {
debugPrint("Deleting temporary zip file from directory: ${entity.path}");
await entity.delete();
}
}
}
} else {
final file = File(path);
if (await file.exists() && path.endsWith('.zip')) {
debugPrint("Deleting temporary zip file: ${file.path}");
await file.delete();
}
}
} catch (e) {
debugPrint("Error cleaning up temporary zip file(s) for path $path: $e");
}
}
}