// 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 queueTask({ required String type, required Map 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 addApiToQueue({ required String endpoint, required String method, Map? body, Map? fields, Map? 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 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>> getPendingTasks() { return _dbHelper.getPendingRequests(); } Future 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 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 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; 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.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; 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.from(jsonData['checklistItems']); } if (jsonData['remarks'] != null) { dataToResubmit.remarks = Map.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; 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; 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.from(jsonData['ysiSondeChecks']); } dataToResubmit.ysiSondeComments = jsonData['ysiSondeComments']; // Handle nested maps for Sensor Checks if (jsonData['ysiSensorChecks'] != null) { dataToResubmit.ysiSensorChecks = (jsonData['ysiSensorChecks'] as Map).map( (key, value) => MapEntry(key, Map.from(value)), ); } dataToResubmit.ysiSensorComments = jsonData['ysiSensorComments']; // Handle nested maps for Replacements if (jsonData['ysiReplacements'] != null) { dataToResubmit.ysiReplacements = (jsonData['ysiReplacements'] as Map).map( (key, value) => MapEntry(key, Map.from(value)), ); } if (jsonData['vanDornChecks'] != null) { dataToResubmit.vanDornChecks = (jsonData['vanDornChecks'] as Map).map( (key, value) => MapEntry(key, Map.from(value)), ); } dataToResubmit.vanDornComments = jsonData['vanDornComments']; dataToResubmit.vanDornCurrentSerial = jsonData['vanDornCurrentSerial']; dataToResubmit.vanDornNewSerial = jsonData['vanDornNewSerial']; if (jsonData['vanDornReplacements'] != null) { dataToResubmit.vanDornReplacements = (jsonData['vanDornReplacements'] as Map).map( (key, value) => MapEntry(key, Map.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 fields = Map.from(payload['fields'] ?? {}); final Map files = (payload['files'] as Map?) ?.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 body = Map.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: () => {}, ); 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"); } } }