680 lines
30 KiB
Dart
680 lines
30 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';
|
|
|
|
/// 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();
|
|
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;
|
|
}
|
|
if (await localFile.exists()) {
|
|
final ftpConfigs = await _dbHelper.loadFtpConfigs() ?? [];
|
|
final config = ftpConfigs.firstWhere((c) => c['ftp_config_id'] == ftpConfigId, orElse: () => <String, dynamic>{});
|
|
if (config.isEmpty) return false;
|
|
|
|
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");
|
|
}
|
|
}
|
|
|
|
} |