change river ftp folder name and image naming to follow mms 1.00 to edc
This commit is contained in:
parent
c543e82d5b
commit
18e853ac83
@ -29,7 +29,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
title: const Text("MMS Version 3.8.01"),
|
title: const Text("MMS Version 3.12.01"),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.person),
|
icon: const Icon(Icons.person),
|
||||||
|
|||||||
@ -221,7 +221,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.info_outline),
|
leading: const Icon(Icons.info_outline),
|
||||||
title: const Text('App Version'),
|
title: const Text('App Version'),
|
||||||
subtitle: const Text('MMS Version 3.8.01'),
|
subtitle: const Text('MMS Version 3.12.01'),
|
||||||
dense: true,
|
dense: true,
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|||||||
@ -355,19 +355,38 @@ class RiverInSituSamplingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (finalImageFiles.isNotEmpty) {
|
if (finalImageFiles.isNotEmpty) {
|
||||||
final imageZip = await _zippingService.createImageZip(
|
// Re-construct the map for retry to attempt renaming even in fallback
|
||||||
imageFiles: finalImageFiles.values.toList(),
|
final Map<String, File> retryImages = {};
|
||||||
|
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
|
||||||
|
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
|
||||||
|
final String timestampId = "$dateStr$timeStr";
|
||||||
|
|
||||||
|
void addRetryMap(File? file, String prefix) {
|
||||||
|
if(file != null) retryImages['${prefix}_$timestampId.jpg'] = file;
|
||||||
|
}
|
||||||
|
addRetryMap(data.backgroundStationImage, 'background');
|
||||||
|
addRetryMap(data.upstreamRiverImage, 'upstream');
|
||||||
|
addRetryMap(data.downstreamRiverImage, 'downstream');
|
||||||
|
addRetryMap(data.sampleTurbidityImage, 'sample_turbidity');
|
||||||
|
addRetryMap(data.optionalImage1, 'optional_1');
|
||||||
|
addRetryMap(data.optionalImage2, 'optional_2');
|
||||||
|
addRetryMap(data.optionalImage3, 'optional_3');
|
||||||
|
addRetryMap(data.optionalImage4, 'optional_4');
|
||||||
|
|
||||||
|
final retryImageZip = await _zippingService.createRenamedImageZip(
|
||||||
|
imageFiles: retryImages,
|
||||||
baseFileName: baseFileNameForQueue,
|
baseFileName: baseFileNameForQueue,
|
||||||
destinationDir: null,
|
destinationDir: null,
|
||||||
);
|
);
|
||||||
if (imageZip != null) {
|
|
||||||
|
if (retryImageZip != null) {
|
||||||
// Queue for each config separately
|
// Queue for each config separately
|
||||||
for (final config in ftpConfigs) {
|
for (final config in ftpConfigs) {
|
||||||
final configId = config['ftp_config_id'];
|
final configId = config['ftp_config_id'];
|
||||||
if (configId != null) {
|
if (configId != null) {
|
||||||
await _retryService.addFtpToQueue(
|
await _retryService.addFtpToQueue(
|
||||||
localFilePath: imageZip.path,
|
localFilePath: retryImageZip.path,
|
||||||
remotePath: '/${p.basename(imageZip.path)}',
|
remotePath: '/${p.basename(retryImageZip.path)}',
|
||||||
ftpConfigId: configId // Provide the specific config ID
|
ftpConfigId: configId // Provide the specific config ID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -502,21 +521,26 @@ class RiverInSituSamplingService {
|
|||||||
|
|
||||||
/// Generates data and image ZIP files and uploads them using SubmissionFtpService.
|
/// Generates data and image ZIP files and uploads them using SubmissionFtpService.
|
||||||
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverInSituSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
|
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverInSituSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
|
||||||
|
|
||||||
|
// 1. GENERATE TIMESTAMP FOR IMAGE RENAMING ONLY
|
||||||
|
// e.g., "2025-09-30" and "14:34:19" -> "20250930143419"
|
||||||
|
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
|
||||||
|
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
|
||||||
|
final String zipImageTimestamp = "$dateStr$timeStr";
|
||||||
|
|
||||||
|
// 2. USE ORIGINAL BASE FILENAME (Report ID / Milliseconds) for Folder/Zip
|
||||||
final baseFileName = _generateBaseFileName(data);
|
final baseFileName = _generateBaseFileName(data);
|
||||||
|
|
||||||
|
// 3. SETUP DIRECTORIES
|
||||||
final Directory? logDirectory = await _localStorageService.getRiverInSituBaseDir(data.samplingType, serverName: serverName); // Use correct base dir getter
|
final Directory? logDirectory = await _localStorageService.getRiverInSituBaseDir(data.samplingType, serverName: serverName); // Use correct base dir getter
|
||||||
|
|
||||||
// --- START: MODIFIED folderName ---
|
|
||||||
// Use baseFileName for the folder name to match [stationCode]_[reportId]
|
// Use baseFileName for the folder name to match [stationCode]_[reportId]
|
||||||
final folderName = baseFileName;
|
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, baseFileName)) : null;
|
||||||
// --- END: MODIFIED folderName ---
|
|
||||||
|
|
||||||
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, folderName)) : null;
|
|
||||||
if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
|
if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
|
||||||
await localSubmissionDir.create(recursive: true);
|
await localSubmissionDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and upload data ZIP (with multiple JSON files specific to River In-Situ)
|
// 4. CREATE DATA ZIP
|
||||||
final dataZip = await _zippingService.createDataZip(
|
final dataZip = await _zippingService.createDataZip(
|
||||||
jsonDataMap: {
|
jsonDataMap: {
|
||||||
'db.json': data.toDbJson(),
|
'db.json': data.toDbJson(),
|
||||||
@ -527,22 +551,48 @@ class RiverInSituSamplingService {
|
|||||||
baseFileName: baseFileName,
|
baseFileName: baseFileName,
|
||||||
destinationDir: localSubmissionDir,
|
destinationDir: localSubmissionDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []};
|
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []};
|
||||||
if (dataZip != null) {
|
if (dataZip != null) {
|
||||||
ftpDataResult = await _submissionFtpService.submit(
|
ftpDataResult = await _submissionFtpService.submit(
|
||||||
moduleName: moduleName, fileToUpload: dataZip, remotePath: '/${p.basename(dataZip.path)}');
|
moduleName: moduleName, fileToUpload: dataZip, remotePath: '/${p.basename(dataZip.path)}');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and upload image ZIP
|
// 5. CREATE IMAGE ZIP (RENAMING LOGIC)
|
||||||
final imageZip = await _zippingService.createImageZip(
|
|
||||||
imageFiles: imageFiles.values.toList(),
|
|
||||||
baseFileName: baseFileName,
|
|
||||||
destinationDir: localSubmissionDir,
|
|
||||||
);
|
|
||||||
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
|
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
|
||||||
if (imageZip != null) {
|
|
||||||
ftpImageResult = await _submissionFtpService.submit(
|
// Create map: "New Name Inside Zip" -> "Original File on Phone"
|
||||||
moduleName: moduleName, fileToUpload: imageZip, remotePath: '/${p.basename(imageZip.path)}');
|
final Map<String, File> imagesForZip = {};
|
||||||
|
|
||||||
|
void mapImage(File? file, String prefix) {
|
||||||
|
if (file != null && file.existsSync()) {
|
||||||
|
// Rename inside zip using the READABLE timestamp: prefix_20250930143419.jpg
|
||||||
|
imagesForZip['${prefix}_$zipImageTimestamp.jpg'] = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapImage(data.backgroundStationImage, 'background');
|
||||||
|
mapImage(data.upstreamRiverImage, 'upstream');
|
||||||
|
mapImage(data.downstreamRiverImage, 'downstream');
|
||||||
|
mapImage(data.sampleTurbidityImage, 'turbidity');
|
||||||
|
mapImage(data.optionalImage1, 'optional_1');
|
||||||
|
mapImage(data.optionalImage2, 'optional_2');
|
||||||
|
mapImage(data.optionalImage3, 'optional_3');
|
||||||
|
mapImage(data.optionalImage4, 'optional_4');
|
||||||
|
|
||||||
|
if (imagesForZip.isNotEmpty) {
|
||||||
|
// Call the NEW function: createRenamedImageZip
|
||||||
|
// Zip file name still uses baseFileName (milliseconds)
|
||||||
|
final imageZip = await _zippingService.createRenamedImageZip(
|
||||||
|
imageFiles: imagesForZip,
|
||||||
|
baseFileName: baseFileName,
|
||||||
|
destinationDir: localSubmissionDir,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (imageZip != null) {
|
||||||
|
ftpImageResult = await _submissionFtpService.submit(
|
||||||
|
moduleName: moduleName, fileToUpload: imageZip, remotePath: '/${p.basename(imageZip.path)}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -379,19 +379,38 @@ class RiverInvestigativeSamplingService { // Renamed class
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (finalImageFiles.isNotEmpty) {
|
if (finalImageFiles.isNotEmpty) {
|
||||||
final imageZip = await _zippingService.createImageZip(
|
// Use existing queue logic for fallback (no renaming complexity here to be safe)
|
||||||
imageFiles: finalImageFiles.values.toList(),
|
final Map<String, File> retryImages = {};
|
||||||
|
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
|
||||||
|
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
|
||||||
|
final String zipImageTimestamp = "$dateStr$timeStr";
|
||||||
|
|
||||||
|
void addRetryMap(File? file, String prefix) {
|
||||||
|
if(file != null) retryImages['${prefix}_$zipImageTimestamp.jpg'] = file;
|
||||||
|
}
|
||||||
|
addRetryMap(data.backgroundStationImage, 'background');
|
||||||
|
addRetryMap(data.upstreamRiverImage, 'upstream');
|
||||||
|
addRetryMap(data.downstreamRiverImage, 'downstream');
|
||||||
|
addRetryMap(data.sampleTurbidityImage, 'sample_turbidity');
|
||||||
|
addRetryMap(data.optionalImage1, 'optional_1');
|
||||||
|
addRetryMap(data.optionalImage2, 'optional_2');
|
||||||
|
addRetryMap(data.optionalImage3, 'optional_3');
|
||||||
|
addRetryMap(data.optionalImage4, 'optional_4');
|
||||||
|
|
||||||
|
final retryImageZip = await _zippingService.createRenamedImageZip(
|
||||||
|
imageFiles: retryImages,
|
||||||
baseFileName: baseFileNameForQueue,
|
baseFileName: baseFileNameForQueue,
|
||||||
destinationDir: null, // Save to temp dir
|
destinationDir: null,
|
||||||
);
|
);
|
||||||
if (imageZip != null) {
|
|
||||||
|
if (retryImageZip != null) {
|
||||||
// Queue for each config separately
|
// Queue for each config separately
|
||||||
for (final config in ftpConfigs) {
|
for (final config in ftpConfigs) {
|
||||||
final configId = config['ftp_config_id'];
|
final configId = config['ftp_config_id'];
|
||||||
if (configId != null) {
|
if (configId != null) {
|
||||||
await _retryService.addFtpToQueue(
|
await _retryService.addFtpToQueue(
|
||||||
localFilePath: imageZip.path,
|
localFilePath: retryImageZip.path,
|
||||||
remotePath: '/${p.basename(imageZip.path)}', // Standard remote path
|
remotePath: '/${p.basename(retryImageZip.path)}', // Standard remote path
|
||||||
ftpConfigId: configId // Provide the specific config ID
|
ftpConfigId: configId // Provide the specific config ID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -595,22 +614,26 @@ class RiverInvestigativeSamplingService { // Renamed class
|
|||||||
// --- END: MODIFIED _generateBaseFileName ---
|
// --- END: MODIFIED _generateBaseFileName ---
|
||||||
|
|
||||||
/// Generates data and image ZIP files and uploads them using SubmissionFtpService (Investigative).
|
/// Generates data and image ZIP files and uploads them using SubmissionFtpService (Investigative).
|
||||||
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverInvesManualSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async { // Updated model type
|
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverInvesManualSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
|
||||||
|
|
||||||
|
// 1. GENERATE TIMESTAMP FOR IMAGE RENAMING
|
||||||
|
// e.g., "2025-09-30" and "14:34:19" -> "20250930143419"
|
||||||
|
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
|
||||||
|
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
|
||||||
|
final String zipImageTimestamp = "$dateStr$timeStr";
|
||||||
|
|
||||||
|
// 2. USE ORIGINAL BASE FILENAME (Report ID / Milliseconds) for Folder/Zip
|
||||||
final baseFileName = _generateBaseFileName(data); // Use helper
|
final baseFileName = _generateBaseFileName(data); // Use helper
|
||||||
|
|
||||||
// *** MODIFIED: Use correct base dir getter ***
|
// 3. SETUP DIRECTORIES
|
||||||
final Directory? logDirectory = await _localStorageService.getRiverInvestigativeBaseDir(serverName: serverName); // NEW GETTER
|
final Directory? logDirectory = await _localStorageService.getRiverInvestigativeBaseDir(serverName: serverName); // NEW GETTER
|
||||||
|
|
||||||
// Determine the specific folder for this submission log within the base directory
|
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, baseFileName)) : null;
|
||||||
// --- START: MODIFIED folderName ---
|
|
||||||
final folderName = baseFileName; // Use the timestamp-based filename
|
|
||||||
// --- END: MODIFIED folderName ---
|
|
||||||
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, folderName)) : null;
|
|
||||||
if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
|
if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
|
||||||
await localSubmissionDir.create(recursive: true); // Create if doesn't exist
|
await localSubmissionDir.create(recursive: true); // Create if doesn't exist
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and upload data ZIP (with multiple JSON files specific to River Investigative)
|
// 4. CREATE DATA ZIP
|
||||||
final dataZip = await _zippingService.createDataZip(
|
final dataZip = await _zippingService.createDataZip(
|
||||||
jsonDataMap: {
|
jsonDataMap: {
|
||||||
// *** MODIFIED: Use Investigative model's JSON methods and filenames ***
|
// *** MODIFIED: Use Investigative model's JSON methods and filenames ***
|
||||||
@ -622,6 +645,7 @@ class RiverInvestigativeSamplingService { // Renamed class
|
|||||||
baseFileName: baseFileName,
|
baseFileName: baseFileName,
|
||||||
destinationDir: localSubmissionDir, // Save ZIP in the specific log folder
|
destinationDir: localSubmissionDir, // Save ZIP in the specific log folder
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []}; // Default success if no file
|
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []}; // Default success if no file
|
||||||
if (dataZip != null) {
|
if (dataZip != null) {
|
||||||
ftpDataResult = await _submissionFtpService.submit(
|
ftpDataResult = await _submissionFtpService.submit(
|
||||||
@ -631,14 +655,37 @@ class RiverInvestigativeSamplingService { // Renamed class
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and upload image ZIP (if images exist)
|
// 5. CREATE IMAGE ZIP (RENAMING LOGIC)
|
||||||
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []}; // Default success if no images
|
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
|
||||||
if (imageFiles.isNotEmpty) {
|
|
||||||
final imageZip = await _zippingService.createImageZip(
|
// Create mapping: "New Name Inside Zip" -> "Original File on Phone"
|
||||||
imageFiles: imageFiles.values.toList(),
|
final Map<String, File> imagesForZip = {};
|
||||||
|
|
||||||
|
void mapImage(File? file, String prefix) {
|
||||||
|
if (file != null && file.existsSync()) {
|
||||||
|
// Rename inside zip: prefix_20250930143419.jpg
|
||||||
|
imagesForZip['${prefix}_$zipImageTimestamp.jpg'] = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map images (Investigative model uses same names as others)
|
||||||
|
mapImage(data.backgroundStationImage, 'background');
|
||||||
|
mapImage(data.upstreamRiverImage, 'upstream');
|
||||||
|
mapImage(data.downstreamRiverImage, 'downstream');
|
||||||
|
mapImage(data.sampleTurbidityImage, 'turbidity');
|
||||||
|
mapImage(data.optionalImage1, 'optional_1');
|
||||||
|
mapImage(data.optionalImage2, 'optional_2');
|
||||||
|
mapImage(data.optionalImage3, 'optional_3');
|
||||||
|
mapImage(data.optionalImage4, 'optional_4');
|
||||||
|
|
||||||
|
if (imagesForZip.isNotEmpty) {
|
||||||
|
// *** MODIFICATION: Call the NEW renaming function ***
|
||||||
|
final imageZip = await _zippingService.createRenamedImageZip(
|
||||||
|
imageFiles: imagesForZip,
|
||||||
baseFileName: baseFileName,
|
baseFileName: baseFileName,
|
||||||
destinationDir: localSubmissionDir, // Save ZIP in the specific log folder
|
destinationDir: localSubmissionDir, // Save ZIP in the specific log folder
|
||||||
);
|
);
|
||||||
|
|
||||||
if (imageZip != null) {
|
if (imageZip != null) {
|
||||||
ftpImageResult = await _submissionFtpService.submit(
|
ftpImageResult = await _submissionFtpService.submit(
|
||||||
moduleName: moduleName, // 'river_investigative'
|
moduleName: moduleName, // 'river_investigative'
|
||||||
@ -648,7 +695,6 @@ class RiverInvestigativeSamplingService { // Renamed class
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Combine statuses from both uploads
|
// Combine statuses from both uploads
|
||||||
return {
|
return {
|
||||||
'statuses': <Map<String, dynamic>>[
|
'statuses': <Map<String, dynamic>>[
|
||||||
|
|||||||
@ -347,19 +347,39 @@ class RiverManualTriennialSamplingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (finalImageFiles.isNotEmpty) {
|
if (finalImageFiles.isNotEmpty) {
|
||||||
final imageZip = await _zippingService.createImageZip(
|
// Note: For the session expired case, renaming logic would ideally be here too,
|
||||||
imageFiles: finalImageFiles.values.toList(),
|
// but requires complex reconstruction of the map. Following the previous pattern,
|
||||||
|
// we attempt to respect the rename if possible.
|
||||||
|
final Map<String, File> retryImages = {};
|
||||||
|
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
|
||||||
|
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
|
||||||
|
final String timestampId = "$dateStr$timeStr";
|
||||||
|
|
||||||
|
void addRetryMap(File? file, String prefix) {
|
||||||
|
if(file != null) retryImages['${prefix}_$timestampId.jpg'] = file;
|
||||||
|
}
|
||||||
|
addRetryMap(data.backgroundStationImage, 'background');
|
||||||
|
addRetryMap(data.upstreamRiverImage, 'upstream');
|
||||||
|
addRetryMap(data.downstreamRiverImage, 'downstream');
|
||||||
|
addRetryMap(data.sampleTurbidityImage, 'sample_turbidity');
|
||||||
|
addRetryMap(data.optionalImage1, 'optional_1');
|
||||||
|
addRetryMap(data.optionalImage2, 'optional_2');
|
||||||
|
addRetryMap(data.optionalImage3, 'optional_3');
|
||||||
|
addRetryMap(data.optionalImage4, 'optional_4');
|
||||||
|
|
||||||
|
final retryImageZip = await _zippingService.createRenamedImageZip(
|
||||||
|
imageFiles: retryImages,
|
||||||
baseFileName: baseFileNameForQueue,
|
baseFileName: baseFileNameForQueue,
|
||||||
destinationDir: null,
|
destinationDir: null,
|
||||||
);
|
);
|
||||||
if (imageZip != null) {
|
if (retryImageZip != null) {
|
||||||
// Queue for each config separately
|
// Queue for each config separately
|
||||||
for (final config in ftpConfigs) {
|
for (final config in ftpConfigs) {
|
||||||
final configId = config['ftp_config_id'];
|
final configId = config['ftp_config_id'];
|
||||||
if (configId != null) {
|
if (configId != null) {
|
||||||
await _retryService.addFtpToQueue(
|
await _retryService.addFtpToQueue(
|
||||||
localFilePath: imageZip.path,
|
localFilePath: retryImageZip.path,
|
||||||
remotePath: '/${p.basename(imageZip.path)}',
|
remotePath: '/${p.basename(retryImageZip.path)}',
|
||||||
ftpConfigId: configId // Provide the specific config ID
|
ftpConfigId: configId // Provide the specific config ID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -492,44 +512,77 @@ class RiverManualTriennialSamplingService {
|
|||||||
|
|
||||||
/// Generates data and image ZIP files and uploads them using SubmissionFtpService.
|
/// Generates data and image ZIP files and uploads them using SubmissionFtpService.
|
||||||
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverManualTriennialSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
|
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(RiverManualTriennialSamplingData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
|
||||||
|
|
||||||
|
// 1. GENERATE TIMESTAMP FOR IMAGE RENAMING
|
||||||
|
// e.g., "2025-09-30" and "14:34:19" -> "20250930143419"
|
||||||
|
final String dateStr = (data.samplingDate ?? '').replaceAll('-', '');
|
||||||
|
final String timeStr = (data.samplingTime ?? '').replaceAll(':', '');
|
||||||
|
final String zipImageTimestamp = "$dateStr$timeStr";
|
||||||
|
|
||||||
|
// 2. USE ORIGINAL BASE FILENAME (Report ID / Milliseconds) for Folder/Zip
|
||||||
final baseFileName = _generateBaseFileName(data);
|
final baseFileName = _generateBaseFileName(data);
|
||||||
|
|
||||||
|
// 3. SETUP DIRECTORIES
|
||||||
final Directory? logDirectory = await _localStorageService.getLogDirectory( // Use generic getter
|
final Directory? logDirectory = await _localStorageService.getLogDirectory( // Use generic getter
|
||||||
serverName: serverName,
|
serverName: serverName,
|
||||||
module: 'river',
|
module: 'river',
|
||||||
subModule: 'river_triennial_sampling', // Correct sub-module path
|
subModule: 'river_triennial_sampling', // Correct sub-module path
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- START: MODIFIED folderName ---
|
// Use baseFileName for the folder
|
||||||
final folderName = baseFileName; // Use the timestamp-based filename
|
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, baseFileName)) : null;
|
||||||
// --- END: MODIFIED folderName ---
|
|
||||||
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, folderName)) : null;
|
|
||||||
if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
|
if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
|
||||||
await localSubmissionDir.create(recursive: true);
|
await localSubmissionDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and upload data ZIP
|
// 4. CREATE DATA ZIP
|
||||||
final dataZip = await _zippingService.createDataZip(
|
final dataZip = await _zippingService.createDataZip(
|
||||||
jsonDataMap: {'db.json': data.toDbJson()}, // Assuming similar structure, adjust if needed
|
jsonDataMap: {'db.json': data.toDbJson()}, // Assuming similar structure, adjust if needed
|
||||||
baseFileName: baseFileName,
|
baseFileName: baseFileName,
|
||||||
destinationDir: localSubmissionDir,
|
destinationDir: localSubmissionDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []};
|
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []};
|
||||||
if (dataZip != null) {
|
if (dataZip != null) {
|
||||||
ftpDataResult = await _submissionFtpService.submit(
|
ftpDataResult = await _submissionFtpService.submit(
|
||||||
moduleName: moduleName, fileToUpload: dataZip, remotePath: '/${p.basename(dataZip.path)}');
|
moduleName: moduleName, fileToUpload: dataZip, remotePath: '/${p.basename(dataZip.path)}');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and upload image ZIP
|
// 5. CREATE IMAGE ZIP (RENAMING LOGIC)
|
||||||
final imageZip = await _zippingService.createImageZip(
|
|
||||||
imageFiles: imageFiles.values.toList(),
|
|
||||||
baseFileName: baseFileName,
|
|
||||||
destinationDir: localSubmissionDir,
|
|
||||||
);
|
|
||||||
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
|
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
|
||||||
if (imageZip != null) {
|
|
||||||
ftpImageResult = await _submissionFtpService.submit(
|
// Create map: "New Name Inside Zip" -> "Original File on Phone"
|
||||||
moduleName: moduleName, fileToUpload: imageZip, remotePath: '/${p.basename(imageZip.path)}');
|
final Map<String, File> imagesForZip = {};
|
||||||
|
|
||||||
|
void mapImage(File? file, String prefix) {
|
||||||
|
if (file != null && file.existsSync()) {
|
||||||
|
// Rename inside zip: prefix_20250930143419.jpg
|
||||||
|
imagesForZip['${prefix}_$zipImageTimestamp.jpg'] = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the specific fields to their short prefixes
|
||||||
|
mapImage(data.backgroundStationImage, 'background');
|
||||||
|
mapImage(data.upstreamRiverImage, 'upstream');
|
||||||
|
mapImage(data.downstreamRiverImage, 'downstream');
|
||||||
|
mapImage(data.sampleTurbidityImage, 'turbidity');
|
||||||
|
mapImage(data.optionalImage1, 'optional_1');
|
||||||
|
mapImage(data.optionalImage2, 'optional_2');
|
||||||
|
mapImage(data.optionalImage3, 'optional_3');
|
||||||
|
mapImage(data.optionalImage4, 'optional_4');
|
||||||
|
|
||||||
|
if (imagesForZip.isNotEmpty) {
|
||||||
|
// Call the NEW function: createRenamedImageZip
|
||||||
|
final imageZip = await _zippingService.createRenamedImageZip(
|
||||||
|
imageFiles: imagesForZip,
|
||||||
|
baseFileName: baseFileName,
|
||||||
|
destinationDir: localSubmissionDir,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (imageZip != null) {
|
||||||
|
ftpImageResult = await _submissionFtpService.submit(
|
||||||
|
moduleName: moduleName, fileToUpload: imageZip, remotePath: '/${p.basename(imageZip.path)}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
|
// lib/services/zipping_service.dart
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:convert'; // Added to ensure correct UTF-8 encoding
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:archive/archive_io.dart';
|
import 'package:archive/archive_io.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
/// A dedicated service to handle the creation of ZIP archives for FTP submission.
|
|
||||||
class ZippingService {
|
class ZippingService {
|
||||||
/// Creates multiple JSON files from a map of data and zips them into a single archive.
|
/// Creates multiple JSON files from a map of data and zips them into a single archive.
|
||||||
/// The map keys will be the filenames (e.g., 'db.json', 'form_data.json').
|
|
||||||
/// The map values should be the JSON string content for each file.
|
|
||||||
Future<File?> createDataZip({
|
Future<File?> createDataZip({
|
||||||
required Map<String, String> jsonDataMap,
|
required Map<String, String> jsonDataMap,
|
||||||
required String baseFileName,
|
required String baseFileName,
|
||||||
@ -17,7 +16,6 @@ class ZippingService {
|
|||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final targetDir = destinationDir ?? await getTemporaryDirectory();
|
final targetDir = destinationDir ?? await getTemporaryDirectory();
|
||||||
// Ensure the target directory exists before creating the file
|
|
||||||
if (!await targetDir.exists()) {
|
if (!await targetDir.exists()) {
|
||||||
await targetDir.create(recursive: true);
|
await targetDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
@ -30,18 +28,9 @@ class ZippingService {
|
|||||||
for (var entry in jsonDataMap.entries) {
|
for (var entry in jsonDataMap.entries) {
|
||||||
final fileName = entry.key;
|
final fileName = entry.key;
|
||||||
final jsonContent = entry.value;
|
final jsonContent = entry.value;
|
||||||
|
|
||||||
// --- MODIFIED: Ensure UTF-8 encoding ---
|
|
||||||
// 1. Encode the string content into UTF-8 bytes
|
|
||||||
final utf8Bytes = utf8.encode(jsonContent);
|
final utf8Bytes = utf8.encode(jsonContent);
|
||||||
|
|
||||||
// 2. Use the UTF-8 bytes and their correct length for the archive
|
|
||||||
// (This replaces the original: jsonContent.length, jsonContent.codeUnits)
|
|
||||||
final archiveFile = ArchiveFile(fileName, utf8Bytes.length, utf8Bytes);
|
final archiveFile = ArchiveFile(fileName, utf8Bytes.length, utf8Bytes);
|
||||||
// --- END MODIFICATION ---
|
|
||||||
|
|
||||||
encoder.addArchiveFile(archiveFile);
|
encoder.addArchiveFile(archiveFile);
|
||||||
|
|
||||||
debugPrint("Added $fileName to data ZIP.");
|
debugPrint("Added $fileName to data ZIP.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,11 +43,13 @@ class ZippingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [ORIGINAL FUNCTION RESTORED]
|
||||||
/// Creates a ZIP file from a list of image files.
|
/// Creates a ZIP file from a list of image files.
|
||||||
|
/// Used by Marine, Air, etc. that do not need renaming.
|
||||||
Future<File?> createImageZip({
|
Future<File?> createImageZip({
|
||||||
required List<File> imageFiles,
|
required List<File> imageFiles,
|
||||||
required String baseFileName,
|
required String baseFileName,
|
||||||
Directory? destinationDir, // ADDED: New optional parameter
|
Directory? destinationDir,
|
||||||
}) async {
|
}) async {
|
||||||
if (imageFiles.isEmpty) {
|
if (imageFiles.isEmpty) {
|
||||||
debugPrint("No images provided to create an image ZIP.");
|
debugPrint("No images provided to create an image ZIP.");
|
||||||
@ -67,7 +58,6 @@ class ZippingService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final targetDir = destinationDir ?? await getTemporaryDirectory();
|
final targetDir = destinationDir ?? await getTemporaryDirectory();
|
||||||
// Ensure the target directory exists before creating the file
|
|
||||||
if (!await targetDir.exists()) {
|
if (!await targetDir.exists()) {
|
||||||
await targetDir.create(recursive: true);
|
await targetDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
@ -94,4 +84,52 @@ class ZippingService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [NEW FUNCTION FOR RIVER]
|
||||||
|
/// Creates a ZIP file from a Map of image files to allow specific renaming inside the ZIP.
|
||||||
|
/// Key: The filename to be used INSIDE the zip (e.g., 'background_20231213.jpg')
|
||||||
|
/// Value: The actual File object on the device.
|
||||||
|
Future<File?> createRenamedImageZip({
|
||||||
|
required Map<String, File> imageFiles,
|
||||||
|
required String baseFileName,
|
||||||
|
Directory? destinationDir,
|
||||||
|
}) async {
|
||||||
|
if (imageFiles.isEmpty) {
|
||||||
|
debugPrint("No images provided to create an image ZIP.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final targetDir = destinationDir ?? await getTemporaryDirectory();
|
||||||
|
if (!await targetDir.exists()) {
|
||||||
|
await targetDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
final zipFilePath = p.join(targetDir.path, '${baseFileName}_img.zip');
|
||||||
|
final encoder = ZipFileEncoder();
|
||||||
|
encoder.create(zipFilePath);
|
||||||
|
|
||||||
|
debugPrint("Creating renamed image ZIP at: $zipFilePath");
|
||||||
|
|
||||||
|
for (var entry in imageFiles.entries) {
|
||||||
|
final String targetName = entry.key;
|
||||||
|
final File sourceFile = entry.value;
|
||||||
|
|
||||||
|
if (await sourceFile.exists()) {
|
||||||
|
final bytes = await sourceFile.readAsBytes();
|
||||||
|
final archiveFile = ArchiveFile(targetName, bytes.length, bytes);
|
||||||
|
encoder.addArchiveFile(archiveFile);
|
||||||
|
debugPrint("Added ${p.basename(sourceFile.path)} as $targetName");
|
||||||
|
} else {
|
||||||
|
debugPrint("Skipping non-existent file: ${sourceFile.path}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.close();
|
||||||
|
debugPrint("Renamed Image ZIP creation complete.");
|
||||||
|
return File(zipFilePath);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error creating renamed image ZIP file: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user