From 383f07a1e2a4d88408c3433ca2a338be72c1f84f Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Mon, 5 Jan 2026 15:09:34 +0530 Subject: [PATCH 01/12] RCF-1278: Editable Cron Job in Scheduled Job Settings Signed-off-by: sachin.sp --- .../registration_client/HostApiModule.java | 4 +- .../api_services/MasterDataSyncApi.java | 39 +++- .../clientmanager/config/AppModule.java | 5 +- .../clientmanager/dao/LocalConfigDAO.java | 14 ++ .../clientmanager/dao/LocalConfigDAOImpl.java | 45 +++++ .../service/JobManagerServiceImpl.java | 22 ++- .../service/LocalConfigServiceImpl.java | 15 ++ .../clientmanager/spi/LocalConfigService.java | 20 ++ .../sync_response_service_impl.dart | 42 ++++ lib/platform_spi/sync_response_service.dart | 4 + .../widgets/scheduled_jobs_settings.dart | 181 +++++++++++++++++- pigeon/master_data_sync.dart | 6 + 12 files changed, 382 insertions(+), 15 deletions(-) diff --git a/android/app/src/main/java/io/mosip/registration_client/HostApiModule.java b/android/app/src/main/java/io/mosip/registration_client/HostApiModule.java index 72713195e..e35053c57 100644 --- a/android/app/src/main/java/io/mosip/registration_client/HostApiModule.java +++ b/android/app/src/main/java/io/mosip/registration_client/HostApiModule.java @@ -199,7 +199,7 @@ MasterDataSyncApi getSyncResponseApi( AuditManagerService auditManagerService, MasterDataService masterDataService, PacketService packetService, - GlobalParamDao globalParamDao, FileSignatureDao fileSignatureDao,PreRegistrationDataSyncService preRegistrationDataSyncService) { + GlobalParamDao globalParamDao, FileSignatureDao fileSignatureDao,PreRegistrationDataSyncService preRegistrationDataSyncService, LocalConfigService localConfigService) { return new MasterDataSyncApi(clientCryptoManagerService, machineRepository, registrationCenterRepository, syncRestService, certificateManagerService, @@ -209,7 +209,7 @@ MasterDataSyncApi getSyncResponseApi( templateRepository, dynamicFieldRepository, locationRepository, blocklistedWordRepository, syncJobDefRepository, languageRepository, jobManagerService, - auditManagerService, masterDataService, packetService, globalParamDao, fileSignatureDao, preRegistrationDataSyncService + auditManagerService, masterDataService, packetService, globalParamDao, fileSignatureDao, preRegistrationDataSyncService, localConfigService ); } diff --git a/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java b/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java index 4504dcfdd..178799006 100644 --- a/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java +++ b/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java @@ -59,10 +59,12 @@ import io.mosip.registration.clientmanager.service.JobManagerServiceImpl; import io.mosip.registration.clientmanager.spi.AuditManagerService; import io.mosip.registration.clientmanager.spi.JobManagerService; +import io.mosip.registration.clientmanager.spi.LocalConfigService; import io.mosip.registration.clientmanager.spi.MasterDataService; import io.mosip.registration.clientmanager.spi.PacketService; import io.mosip.registration.clientmanager.spi.PreRegistrationDataSyncService; import io.mosip.registration.clientmanager.spi.SyncRestService; +import io.mosip.registration.clientmanager.util.CronExpressionParser; import io.mosip.registration.keymanager.spi.CertificateManagerService; import io.mosip.registration.keymanager.spi.ClientCryptoManagerService; import io.mosip.registration_client.utils.BatchJob; @@ -100,6 +102,7 @@ public class MasterDataSyncApi implements MasterDataSyncPigeon.SyncApi { GlobalParamDao globalParamDao; FileSignatureDao fileSignatureDao; PreRegistrationDataSyncService preRegistrationDataSyncService; + LocalConfigService localConfigService; Context context; private String regCenterId; @@ -120,7 +123,7 @@ public MasterDataSyncApi(ClientCryptoManagerService clientCryptoManagerService, AuditManagerService auditManagerService, MasterDataService masterDataService, PacketService packetService, - GlobalParamDao globalParamDao, FileSignatureDao fileSignatureDao, PreRegistrationDataSyncService preRegistrationDataSyncService) { + GlobalParamDao globalParamDao, FileSignatureDao fileSignatureDao, PreRegistrationDataSyncService preRegistrationDataSyncService, LocalConfigService localConfigService) { this.clientCryptoManagerService = clientCryptoManagerService; this.machineRepository = machineRepository; this.registrationCenterRepository = registrationCenterRepository; @@ -146,6 +149,7 @@ public MasterDataSyncApi(ClientCryptoManagerService clientCryptoManagerService, this.globalParamDao = globalParamDao; this.fileSignatureDao = fileSignatureDao; this.preRegistrationDataSyncService = preRegistrationDataSyncService; + this.localConfigService = localConfigService; } public void setCallbackActivity(MainActivity mainActivity, BatchJob batchJob) { @@ -432,6 +436,39 @@ public void getActiveSyncJobs(@NonNull MasterDataSyncPigeon.Result> result.success(value); } + @Override + public void getPermittedJobs(@NonNull MasterDataSyncPigeon.Result> result) { + try { + List permittedJobs = localConfigService.getPermittedJobs(); + result.success(permittedJobs != null ? permittedJobs : new ArrayList<>()); + } catch (Exception e) { + Log.e(TAG, "Failed to get permitted jobs", e); + result.error(e); + } + } + + @Override + public void isValidCronExpression(@NonNull String cronExpression, @NonNull MasterDataSyncPigeon.Result result) { + try { + boolean isValid = CronExpressionParser.isValidCronExpression(cronExpression); + result.success(isValid); + } catch (Exception e) { + Log.e(TAG, "Error validating cron expression", e); + result.success(false); + } + } + + @Override + public void modifyJobCronExpression(@NonNull String jobId, @NonNull String cronExpression, @NonNull MasterDataSyncPigeon.Result result) { + try { + localConfigService.modifyJob(jobId, cronExpression); + result.success(true); + } catch (Exception e) { + Log.e(TAG, "Failed to modify job cron expression", e); + result.error(e); + } + } + // Execute job based on API name public void executeJobByApiName(String jobApiName, Context context) { new Thread(() -> { diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/AppModule.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/AppModule.java index 6a05a0ff6..aa17ab068 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/AppModule.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/AppModule.java @@ -53,6 +53,7 @@ import io.mosip.registration.clientmanager.service.external.PreRegZipHandlingService; import io.mosip.registration.clientmanager.service.external.impl.PreRegZipHandlingServiceImpl; import io.mosip.registration.clientmanager.spi.AuditManagerService; +import io.mosip.registration.clientmanager.spi.LocalConfigService; import io.mosip.registration.clientmanager.spi.JobManagerService; import io.mosip.registration.clientmanager.spi.JobTransactionService; import io.mosip.registration.clientmanager.spi.LocationValidationService; @@ -259,8 +260,8 @@ DateUtil provideDateUtil() { @Provides @Singleton - JobManagerService provideJobManagerService(SyncJobDefRepository syncJobDefRepository, JobTransactionService jobTransactionService, DateUtil dateUtil) { - return new JobManagerServiceImpl(appContext, syncJobDefRepository, jobTransactionService, dateUtil); + JobManagerService provideJobManagerService(SyncJobDefRepository syncJobDefRepository, JobTransactionService jobTransactionService, DateUtil dateUtil, LocalConfigService localConfigService) { + return new JobManagerServiceImpl(appContext, syncJobDefRepository, jobTransactionService, dateUtil, localConfigService); } @Provides diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAO.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAO.java index 357960bb3..17cf60a3a 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAO.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAO.java @@ -26,5 +26,19 @@ public interface LocalConfigDAO { */ void modifyConfigurations(Map localPreferences); + /** + * Get value for a specific local preference by name + * @param name Preference name + * @return Preference value or null if not found + */ + String getValue(String name); + + /** + * Modify job cron expression + * @param name Job ID + * @param value Cron expression value + */ + void modifyJob(String name, String value); + void cleanUpLocalPreferences(); } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java index 04b328f41..59c6e8d30 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java @@ -77,6 +77,51 @@ public void modifyConfigurations(Map localPreferences) { } + @Override + public String getValue(String name) { + try { + LocalPreferences localPreference = localPreferencesRepository.findByIsDeletedFalseAndName(name); + if (localPreference != null && localPreference.getVal() != null) { + return localPreference.getVal(); + } + } catch (Exception e) { + Log.e(TAG, "Error getting value for: " + name, e); + } + return null; + } + + @Override + public void modifyJob(String name, String value) { + Log.i(TAG, "Modifying sync frequency for the job " + name); + + try { + // Check if existing preference exists + LocalPreferences localPreferences = localPreferencesRepository + .findByIsDeletedFalseAndName(name); + + if (localPreferences != null) { + // Soft delete existing record + updateLocalPreference(localPreferences, RegistrationConstants.PERMITTED_JOB_TYPE); + } + + // Create new record with updated cron expression + saveLocalPreference(name, value, RegistrationConstants.PERMITTED_JOB_TYPE); + } catch (Exception e) { + Log.e(TAG, "Error modifying job: " + name, e); + throw e; + } + } + + /** + * Update local preference (soft delete) + */ + private void updateLocalPreference(LocalPreferences localPreference, String configType) { + localPreference.setIsDeleted(true); + localPreference.setUpdBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); + localPreference.setUpdDtimes(System.currentTimeMillis()); + localPreferencesRepository.save(localPreference); + } + /** * Save local preference to database */ diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java index bad4880b2..959c40c16 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java @@ -25,6 +25,7 @@ import io.mosip.registration.clientmanager.repository.SyncJobDefRepository; import io.mosip.registration.clientmanager.spi.JobManagerService; import io.mosip.registration.clientmanager.spi.JobTransactionService; +import io.mosip.registration.clientmanager.spi.LocalConfigService; import io.mosip.registration.clientmanager.util.CronExpressionParser; import io.mosip.registration.clientmanager.util.DateUtil; @@ -44,13 +45,15 @@ public class JobManagerServiceImpl implements JobManagerService { JobTransactionService jobTransactionService; SyncJobDefRepository syncJobDefRepository; DateUtil dateUtil; + LocalConfigService localConfigService; - public JobManagerServiceImpl(Context context, SyncJobDefRepository syncJobDefRepository, JobTransactionService jobTransactionService, DateUtil dateUtil) { + public JobManagerServiceImpl(Context context, SyncJobDefRepository syncJobDefRepository, JobTransactionService jobTransactionService, DateUtil dateUtil, LocalConfigService localConfigService) { this.context = context; this.jobScheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE); this.syncJobDefRepository = syncJobDefRepository; this.jobTransactionService = jobTransactionService; this.dateUtil = dateUtil; + this.localConfigService = localConfigService; } /** @@ -167,7 +170,7 @@ public String getNextSyncTime(int jobId) { return "NA"; } - String cronExpression = jobDef.getSyncFreq(); + String cronExpression = getSyncFrequency(jobDef); // Try cron-based calculation first if (CronExpressionParser.isValidCronExpression(cronExpression)) { Instant nextExecution = CronExpressionParser.getNextExecutionTime(cronExpression); @@ -185,6 +188,21 @@ public String getNextSyncTime(int jobId) { return "NA"; } + /** + * Get sync frequency for a job, checking custom cron expression first, then default + * @param syncJob Job definition + * @return Cron expression (custom if exists, otherwise default) + */ + private String getSyncFrequency(SyncJobDef syncJob) { + if (localConfigService != null) { + String localPreference = localConfigService.getValue(syncJob.getId()); + if (localPreference != null && !localPreference.trim().isEmpty()) { + return localPreference; + } + } + return syncJob.getSyncFreq(); + } + @Override public int generateJobServiceId(String syncJobDefId) { try { diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java index 34df86013..183a6ff0a 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java @@ -34,4 +34,19 @@ public void modifyConfigurations(Map localPreferences) { public List getPermittedConfiguration() { return localConfigDAO.getPermittedConfigurations(RegistrationConstants.PERMITTED_CONFIG_TYPE); } + + @Override + public String getValue(String name) { + return localConfigDAO.getValue(name); + } + + @Override + public void modifyJob(String name, String value) { + localConfigDAO.modifyJob(name, value); + } + + @Override + public List getPermittedJobs() { + return localConfigDAO.getPermittedConfigurations(RegistrationConstants.PERMITTED_JOB_TYPE); + } } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/spi/LocalConfigService.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/spi/LocalConfigService.java index db3266aa3..ceaf39b06 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/spi/LocalConfigService.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/spi/LocalConfigService.java @@ -22,4 +22,24 @@ public interface LocalConfigService { * Get permitted configuration names */ List getPermittedConfiguration(); + + /** + * Get value for a specific local configuration by name + * @param name Configuration name + * @return Configuration value or null if not found + */ + String getValue(String name); + + /** + * Modify job cron expression + * @param name Job ID + * @param value Cron expression value + */ + void modifyJob(String name, String value); + + /** + * Get permitted job IDs + * @return List of permitted job IDs that can be edited + */ + List getPermittedJobs(); } diff --git a/lib/platform_android/sync_response_service_impl.dart b/lib/platform_android/sync_response_service_impl.dart index ea83a38c9..28721a032 100644 --- a/lib/platform_android/sync_response_service_impl.dart +++ b/lib/platform_android/sync_response_service_impl.dart @@ -271,6 +271,48 @@ class SyncResponseServiceImpl implements SyncResponseService { return "false"; } } + + @override + Future> getPermittedJobs() async { + try { + final permittedJobs = await SyncApi().getPermittedJobs(); + return permittedJobs; + } on PlatformException catch (e) { + debugPrint('getPermittedJobs PlatformException: ${e.message}'); + return []; + } catch (e) { + debugPrint('getPermittedJobs failed: $e'); + return []; + } + } + + @override + Future isValidCronExpression(String cronExpression) async { + try { + final isValid = await SyncApi().isValidCronExpression(cronExpression); + return isValid; + } on PlatformException catch (e) { + debugPrint('isValidCronExpression PlatformException: ${e.message}'); + return false; + } catch (e) { + debugPrint('isValidCronExpression failed: $e'); + return false; + } + } + + @override + Future modifyJobCronExpression(String jobId, String cronExpression) async { + try { + final success = await SyncApi().modifyJobCronExpression(jobId, cronExpression); + return success; + } on PlatformException catch (e) { + debugPrint('modifyJobCronExpression PlatformException: ${e.message}'); + return false; + } catch (e) { + debugPrint('modifyJobCronExpression failed: $e'); + return false; + } + } } SyncResponseService getSyncResponseServiceImpl() => SyncResponseServiceImpl(); diff --git a/lib/platform_spi/sync_response_service.dart b/lib/platform_spi/sync_response_service.dart index 01efd7bc4..8418fda75 100644 --- a/lib/platform_spi/sync_response_service.dart +++ b/lib/platform_spi/sync_response_service.dart @@ -32,5 +32,9 @@ abstract class SyncResponseService { Future getLastSyncTimeByJobId(String jobId); Future getNextSyncTimeByJobId(String jobId); + Future> getPermittedJobs(); + Future isValidCronExpression(String cronExpression); + Future modifyJobCronExpression(String jobId, String cronExpression); + factory SyncResponseService() => getSyncResponseServiceImpl(); } \ No newline at end of file diff --git a/lib/ui/settings/widgets/scheduled_jobs_settings.dart b/lib/ui/settings/widgets/scheduled_jobs_settings.dart index b8041bb39..90d622067 100644 --- a/lib/ui/settings/widgets/scheduled_jobs_settings.dart +++ b/lib/ui/settings/widgets/scheduled_jobs_settings.dart @@ -11,7 +11,7 @@ import '../../../provider/sync_provider.dart'; // Dart equivalent of the Java PACKET_JOBS constant const List PACKET_JOBS = ['RPS_J00006', 'RSJ_J00014', 'PUJ_J00017']; -class ScheduledJobsSettings extends StatelessWidget { +class ScheduledJobsSettings extends StatefulWidget { const ScheduledJobsSettings({ super.key, required this.jobJsonList, @@ -21,9 +21,39 @@ class ScheduledJobsSettings extends StatelessWidget { final List jobJsonList; final void Function(String jobId)? onRefreshJob; + @override + State createState() => _ScheduledJobsSettingsState(); +} + +class _ScheduledJobsSettingsState extends State { + List _permittedJobs = []; + bool _isLoadingPermittedJobs = true; + + @override + void initState() { + super.initState(); + _loadPermittedJobs(); + } + + Future _loadPermittedJobs() async { + try { + final service = SyncResponseService(); + final permittedJobs = await service.getPermittedJobs(); + setState(() { + _permittedJobs = permittedJobs; + _isLoadingPermittedJobs = false; + }); + } catch (e) { + debugPrint('Failed to load permitted jobs: $e'); + setState(() { + _isLoadingPermittedJobs = false; + }); + } + } + @override Widget build(BuildContext context) { - final jobs = jobJsonList + final jobs = widget.jobJsonList .whereType() .map((e) => _ScheduledJob.fromJson(json.decode(e) as Map)) .toList(); @@ -66,7 +96,11 @@ class ScheduledJobsSettings extends StatelessWidget { delegate: SliverChildBuilderDelegate( (context, index) { final job = jobs[index]; - return _JobCard(job: job, onRefresh: onRefreshJob); + return _JobCard( + job: job, + onRefresh: widget.onRefreshJob, + isPermitted: _permittedJobs.contains(job.id), + ); }, childCount: jobs.length, ), @@ -80,9 +114,10 @@ class ScheduledJobsSettings extends StatelessWidget { } class _JobCard extends StatefulWidget { - const _JobCard({required this.job, this.onRefresh}); + const _JobCard({required this.job, this.onRefresh, required this.isPermitted}); final _ScheduledJob job; final void Function(String jobId)? onRefresh; + final bool isPermitted; @override State<_JobCard> createState() => _JobCardState(); @@ -92,16 +127,27 @@ class _JobCardState extends State<_JobCard> { String? _lastSync; String? _nextSync; late SyncProvider syncProvider; + final TextEditingController _cronController = TextEditingController(); + final SyncResponseService _syncResponseService = SyncResponseService(); + bool _isEditingCron = false; + String? _cronError; @override void initState() { super.initState(); syncProvider = Provider.of(context, listen: false); + _cronController.text = widget.job.syncFreq ?? ''; _loadLastSyncTime(); // Fetch last sync when widget loads _loadNextSyncTime(); } + @override + void dispose() { + _cronController.dispose(); + super.dispose(); + } + Future _loadLastSyncTime() async { if (widget.job.id != null && widget.job.id!.isNotEmpty) { final value = await syncProvider.getLastSyncTimeByJobId(widget.job.id!); @@ -134,6 +180,65 @@ class _JobCardState extends State<_JobCard> { } } + Future _modifyCronExpression() async { + final cronExpression = _cronController.text.trim(); + + if (cronExpression.isEmpty) { + setState(() { + _cronError = 'Cron expression cannot be empty'; + }); + return; + } + + // Validate cron expression + final isValid = await _syncResponseService.isValidCronExpression(cronExpression); + if (!isValid) { + setState(() { + _cronError = 'Invalid cron expression'; + }); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Invalid cron expression')), + ); + } + return; + } + + setState(() { + _cronError = null; + }); + + // Save cron expression + try { + final success = await _syncResponseService.modifyJobCronExpression( + widget.job.id ?? '', + cronExpression, + ); + + if (success && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Cron expression saved successfully')), + ); + setState(() { + _isEditingCron = false; + }); + // Reload next sync time to reflect new schedule + await _loadNextSyncTime(); + } else if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Failed to save cron expression')), + ); + } + } catch (e) { + debugPrint('Error modifying cron expression: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e')), + ); + } + } + } + Future _triggerJobSync(BuildContext context, String? apiName, String? jobId) async { if (apiName == null || apiName.isEmpty) return; final service = SyncResponseService(); @@ -210,15 +315,75 @@ class _JobCardState extends State<_JobCard> { children: [ Text(job.name ?? job.apiName ?? 'Unknown Job', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), - const SizedBox(height: 8), + const SizedBox(height: 4), _kv('Next Run', _nextSync ?? '-'), _kv('Last Sync', _lastSync ?? '-'), - const SizedBox(height: 6), - _kv('Cron Expression', job.syncFreq ?? '-'), + const SizedBox(height: 4), + if (widget.isPermitted && _isEditingCron) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: _cronController, + decoration: InputDecoration( + labelText: 'Cron Expression', + errorText: _cronError, + border: const OutlineInputBorder(), + isDense: true, + contentPadding: const EdgeInsets.all(8), + ), + style: const TextStyle(fontSize: 12), + ), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + setState(() { + _isEditingCron = false; + _cronController.text = widget.job.syncFreq ?? ''; + _cronError = null; + }); + }, + child: const Text('Cancel'), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: _modifyCronExpression, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + child: const Text('Submit'), + ), + ], + ), + ], + ) + else + Row( + children: [ + Expanded( + child: _kv('Cron Expression', widget.job.syncFreq ?? '-'), + ), + if (widget.isPermitted) + IconButton( + icon: const Icon(Icons.edit, size: 16), + onPressed: () { + setState(() { + _isEditingCron = true; + }); + }, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + tooltip: 'Edit cron expression', + ), + ], + ), ], ), ), - if (!PACKET_JOBS.contains(job.id)) + if (!PACKET_JOBS.contains(job.id) && !_isEditingCron) SizedBox( width: 40, child: OutlinedButton( diff --git a/pigeon/master_data_sync.dart b/pigeon/master_data_sync.dart index 41cb9d275..90da6aa5d 100644 --- a/pigeon/master_data_sync.dart +++ b/pigeon/master_data_sync.dart @@ -63,4 +63,10 @@ abstract class SyncApi { String getNextSyncTimeByJobId(String jobId); @async List getActiveSyncJobs(); + @async + List getPermittedJobs(); + @async + bool isValidCronExpression(String cronExpression); + @async + bool modifyJobCronExpression(String jobId, String cronExpression); } From 302035f19e930042a63bbdc1b7808a6c452a0a6d Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Mon, 5 Jan 2026 21:52:33 +0530 Subject: [PATCH 02/12] RCF-1278: Editable Cron Job in Scheduled Job Settings Signed-off-by: sachin.sp --- .../registration_client/MainActivity.java | 20 +++ .../api_services/MasterDataSyncApi.java | 37 ++++ .../registration_client/utils/BatchJob.java | 22 ++- .../service/JobManagerServiceImpl.java | 4 +- .../sync_response_service_impl.dart | 14 ++ lib/platform_spi/sync_response_service.dart | 1 + .../widgets/scheduled_jobs_settings.dart | 165 ++++++++++-------- pigeon/master_data_sync.dart | 2 + 8 files changed, 189 insertions(+), 76 deletions(-) diff --git a/android/app/src/main/java/io/mosip/registration_client/MainActivity.java b/android/app/src/main/java/io/mosip/registration_client/MainActivity.java index 25598da76..b43343ae3 100644 --- a/android/app/src/main/java/io/mosip/registration_client/MainActivity.java +++ b/android/app/src/main/java/io/mosip/registration_client/MainActivity.java @@ -214,12 +214,27 @@ public void onReceive(Context context, Intent intent) { } }; + private BroadcastReceiver rescheduleReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String jobApiName = intent.getStringExtra(UploadBackgroundService.EXTRA_JOB_API_NAME); + if (jobApiName != null) { + Log.d(getClass().getSimpleName(), "Rescheduling job due to cron change: " + jobApiName); + createBackgroundTask(jobApiName); + } + } + }; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // createBackgroundTask("registrationPacketUploadJob"); IntentFilter intentFilterUpload = new IntentFilter("SYNC_JOB_TRIGGER"); registerReceiver(broadcastReceiver, intentFilterUpload); + + // Register receiver for rescheduling jobs when cron expression changes + IntentFilter rescheduleFilter = new IntentFilter("RESCHEDULE_JOB"); + registerReceiver(rescheduleReceiver, rescheduleFilter); } private void initializeAutoSync() { @@ -264,6 +279,11 @@ void scheduleAllActiveJobs() { protected void onDestroy() { super.onDestroy(); unregisterReceiver(broadcastReceiver); + try { + unregisterReceiver(rescheduleReceiver); + } catch (Exception e) { + // Receiver might not be registered, ignore + } } public void createBackgroundTask(String api){ diff --git a/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java b/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java index 178799006..57ce2b87f 100644 --- a/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java +++ b/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java @@ -462,6 +462,32 @@ public void isValidCronExpression(@NonNull String cronExpression, @NonNull Maste public void modifyJobCronExpression(@NonNull String jobId, @NonNull String cronExpression, @NonNull MasterDataSyncPigeon.Result result) { try { localConfigService.modifyJob(jobId, cronExpression); + + // Reschedule the job with new cron expression + List activeJobs = syncJobDefRepository.getActiveSyncJobs(); + SyncJobDef jobDef = null; + for (SyncJobDef job : activeJobs) { + if (job.getId().equals(jobId)) { + jobDef = job; + break; + } + } + + if (jobDef != null) { + // Refresh job status to apply new cron expression + // This will reschedule JobScheduler jobs with new cron + jobManagerService.refreshJobStatus(jobDef); + + // For AlarmManager-based jobs (like batch jobs), reschedule immediately + if (jobDef.getApiName() != null && activity != null) { + // Reschedule using MainActivity's createBackgroundTask via broadcast + Intent rescheduleIntent = new Intent("RESCHEDULE_JOB"); + rescheduleIntent.putExtra(UploadBackgroundService.EXTRA_JOB_API_NAME, jobDef.getApiName()); + context.sendBroadcast(rescheduleIntent); + Log.d(TAG, "Sent reschedule broadcast for job: " + jobDef.getApiName()); + } + } + result.success(true); } catch (Exception e) { Log.e(TAG, "Failed to modify job cron expression", e); @@ -469,6 +495,17 @@ public void modifyJobCronExpression(@NonNull String jobId, @NonNull String cronE } } + @Override + public void getValue(@NonNull String name, @NonNull MasterDataSyncPigeon.Result result) { + try { + String value = localConfigService.getValue(name); + result.success(value); + } catch (Exception e) { + Log.e(TAG, "Failed to get value for: " + name, e); + result.error(e); + } + } + // Execute job based on API name public void executeJobByApiName(String jobApiName, Context context) { new Thread(() -> { diff --git a/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java b/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java index 31d00adab..fba3dccb8 100644 --- a/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java +++ b/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java @@ -23,6 +23,7 @@ import io.mosip.registration.clientmanager.repository.SyncJobDefRepository; import io.mosip.registration.clientmanager.spi.AsyncPacketTaskCallBack; import io.mosip.registration.clientmanager.spi.AuditManagerService; +import io.mosip.registration.clientmanager.spi.LocalConfigService; import io.mosip.registration.clientmanager.spi.PacketService; import io.mosip.registration_client.MainActivity; import io.mosip.registration_client.R; @@ -33,16 +34,19 @@ public class BatchJob { AuditManagerService auditManagerService; GlobalParamDao globalParamDao; SyncJobDefRepository syncJobDefRepository; + LocalConfigService localConfigService; Activity activity; boolean syncAndUploadInProgressStatus = false; @Inject public BatchJob(PacketService packetService, AuditManagerService auditManagerService, - GlobalParamDao globalParamDao, SyncJobDefRepository syncJobDefRepository) { + GlobalParamDao globalParamDao, SyncJobDefRepository syncJobDefRepository, + LocalConfigService localConfigService) { this.packetService = packetService; this.auditManagerService = auditManagerService; this.globalParamDao = globalParamDao; this.syncJobDefRepository = syncJobDefRepository; + this.localConfigService = localConfigService; } public void setCallbackActivity(MainActivity mainActivity) { @@ -206,8 +210,20 @@ public long getIntervalMillis(String api) { List syncJobs = syncJobDefRepository.getAllSyncJobDefList(); for (SyncJobDef value : syncJobs) { if (Objects.equals(value.getApiName(), api)) { - Log.d(getClass().getSimpleName(), api + " Cron Expression : " + String.valueOf(value.getSyncFreq())); - cronExp = String.valueOf(value.getSyncFreq()); + // Check for custom cron expression first (matches desktop client logic) + if (localConfigService != null) { + String customCron = localConfigService.getValue(value.getId()); + if (customCron != null && !customCron.trim().isEmpty()) { + cronExp = customCron; // Use custom cron expression + Log.d(getClass().getSimpleName(), api + " Custom Cron Expression : " + cronExp); + } else { + cronExp = String.valueOf(value.getSyncFreq()); // Use default from DB + Log.d(getClass().getSimpleName(), api + " Default Cron Expression : " + cronExp); + } + } else { + cronExp = String.valueOf(value.getSyncFreq()); + Log.d(getClass().getSimpleName(), api + " Cron Expression : " + cronExp); + } break; } } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java index 959c40c16..dcdc9a932 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java @@ -84,8 +84,10 @@ public void refreshJobStatus(SyncJobDef jobDef) { return; } + // Use getSyncFrequency to check for custom cron expression first + String syncFreq = getSyncFrequency(jobDef); if (!isJobScheduled(jobId)) - scheduleJob(jobId, jobDef.getApiName(), jobDef.getSyncFreq()); + scheduleJob(jobId, jobDef.getApiName(), syncFreq); } /** diff --git a/lib/platform_android/sync_response_service_impl.dart b/lib/platform_android/sync_response_service_impl.dart index 28721a032..d33ab8227 100644 --- a/lib/platform_android/sync_response_service_impl.dart +++ b/lib/platform_android/sync_response_service_impl.dart @@ -313,6 +313,20 @@ class SyncResponseServiceImpl implements SyncResponseService { return false; } } + + @override + Future getValue(String name) async { + try { + final value = await SyncApi().getValue(name); + return value; + } on PlatformException catch (e) { + debugPrint('getValue PlatformException: ${e.message}'); + return null; + } catch (e) { + debugPrint('getValue failed: $e'); + return null; + } + } } SyncResponseService getSyncResponseServiceImpl() => SyncResponseServiceImpl(); diff --git a/lib/platform_spi/sync_response_service.dart b/lib/platform_spi/sync_response_service.dart index 8418fda75..872cab2f6 100644 --- a/lib/platform_spi/sync_response_service.dart +++ b/lib/platform_spi/sync_response_service.dart @@ -35,6 +35,7 @@ abstract class SyncResponseService { Future> getPermittedJobs(); Future isValidCronExpression(String cronExpression); Future modifyJobCronExpression(String jobId, String cronExpression); + Future getValue(String name); factory SyncResponseService() => getSyncResponseServiceImpl(); } \ No newline at end of file diff --git a/lib/ui/settings/widgets/scheduled_jobs_settings.dart b/lib/ui/settings/widgets/scheduled_jobs_settings.dart index 90d622067..9af3f856d 100644 --- a/lib/ui/settings/widgets/scheduled_jobs_settings.dart +++ b/lib/ui/settings/widgets/scheduled_jobs_settings.dart @@ -5,6 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:registration_client/platform_spi/sync_response_service.dart'; import 'package:registration_client/utils/sync_job_def.dart'; +import 'package:restart_app/restart_app.dart'; import '../../../provider/sync_provider.dart'; @@ -129,7 +130,6 @@ class _JobCardState extends State<_JobCard> { late SyncProvider syncProvider; final TextEditingController _cronController = TextEditingController(); final SyncResponseService _syncResponseService = SyncResponseService(); - bool _isEditingCron = false; String? _cronError; @@ -137,11 +137,25 @@ class _JobCardState extends State<_JobCard> { void initState() { super.initState(); syncProvider = Provider.of(context, listen: false); - _cronController.text = widget.job.syncFreq ?? ''; + _loadCronExpression(); // Load custom cron expression or default _loadLastSyncTime(); // Fetch last sync when widget loads _loadNextSyncTime(); } + Future _loadCronExpression() async { + if (widget.job.id != null && widget.job.id!.isNotEmpty) { + // Check for custom cron expression first (matches desktop client logic) + final customCron = await _syncResponseService.getValue(widget.job.id!); + if (customCron != null && customCron.trim().isNotEmpty) { + _cronController.text = customCron; // Use saved custom cron expression + } else { + _cronController.text = widget.job.syncFreq ?? ''; // Use default from DB + } + } else { + _cronController.text = widget.job.syncFreq ?? ''; + } + } + @override void dispose() { _cronController.dispose(); @@ -216,14 +230,26 @@ class _JobCardState extends State<_JobCard> { ); if (success && mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Cron expression saved successfully')), - ); + // Clear error setState(() { - _isEditingCron = false; + _cronError = null; }); - // Reload next sync time to reflect new schedule - await _loadNextSyncTime(); + + // Show success message + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Cron expression saved successfully. Restarting app...'), + duration: Duration(seconds: 2), + ), + ); + + // Wait a moment for the user to see the message, then restart the app + await Future.delayed(const Duration(seconds: 2)); + + // Restart the app to apply cron expression changes + if (mounted) { + Restart.restartApp(); + } } else if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Failed to save cron expression')), @@ -305,85 +331,77 @@ class _JobCardState extends State<_JobCard> { boxShadow: const [BoxShadow(color: Color(0x11000000), blurRadius: 4, offset: Offset(0, 2))], ), child: Padding( - padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.all(6.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ Text(job.name ?? job.apiName ?? 'Unknown Job', - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), - const SizedBox(height: 4), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), _kv('Next Run', _nextSync ?? '-'), _kv('Last Sync', _lastSync ?? '-'), - const SizedBox(height: 4), - if (widget.isPermitted && _isEditingCron) - Column( + if (widget.isPermitted) + Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextField( - controller: _cronController, - decoration: InputDecoration( - labelText: 'Cron Expression', - errorText: _cronError, - border: const OutlineInputBorder(), - isDense: true, - contentPadding: const EdgeInsets.all(8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 32, + child: TextField( + controller: _cronController, + decoration: InputDecoration( + hintText: 'Cron Expression', + errorText: null, + errorBorder: _cronError != null + ? const OutlineInputBorder( + borderSide: BorderSide(color: Colors.red, width: 1)) + : null, + border: const OutlineInputBorder(), + isDense: true, + contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + ), + style: const TextStyle(fontSize: 11), + ), + ), + if (_cronError != null) + Padding( + padding: const EdgeInsets.only(top: 1.0, left: 4.0), + child: Text( + _cronError!, + style: const TextStyle(fontSize: 9, color: Colors.red, height: 1), + ), + ), + ], ), - style: const TextStyle(fontSize: 12), ), - const SizedBox(height: 4), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () { - setState(() { - _isEditingCron = false; - _cronController.text = widget.job.syncFreq ?? ''; - _cronError = null; - }); - }, - child: const Text('Cancel'), - ), - const SizedBox(width: 8), - ElevatedButton( - onPressed: _modifyCronExpression, - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - ), - child: const Text('Submit'), + const SizedBox(width: 6), + SizedBox( + height: 32, + width: 65, + child: ElevatedButton( + onPressed: _modifyCronExpression, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.zero, ), - ], + child: const Text('Submit', style: TextStyle(fontSize: 11)), + ), ), ], ) else - Row( - children: [ - Expanded( - child: _kv('Cron Expression', widget.job.syncFreq ?? '-'), - ), - if (widget.isPermitted) - IconButton( - icon: const Icon(Icons.edit, size: 16), - onPressed: () { - setState(() { - _isEditingCron = true; - }); - }, - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - tooltip: 'Edit cron expression', - ), - ], - ), + _kv('Cron Expression', widget.job.syncFreq ?? '-'), ], ), ), - if (!PACKET_JOBS.contains(job.id) && !_isEditingCron) + if (!PACKET_JOBS.contains(job.id)) SizedBox( width: 40, child: OutlinedButton( @@ -402,13 +420,16 @@ class _JobCardState extends State<_JobCard> { ); } - Widget _kv(String k, String v) => Row( - children: [ - Text(k, style: const TextStyle(fontSize: 12, color: Colors.black54)), - const SizedBox(width: 8), - Flexible( - child: Text(v, style: const TextStyle(fontSize: 12, color: Colors.black87))), - ], + Widget _kv(String k, String v) => Padding( + padding: const EdgeInsets.only(top: 0.5), + child: Row( + children: [ + Text(k, style: const TextStyle(fontSize: 11, color: Colors.black54)), + const SizedBox(width: 6), + Flexible( + child: Text(v, style: const TextStyle(fontSize: 11, color: Colors.black87))), + ], + ), ); } diff --git a/pigeon/master_data_sync.dart b/pigeon/master_data_sync.dart index 90da6aa5d..315e1c47c 100644 --- a/pigeon/master_data_sync.dart +++ b/pigeon/master_data_sync.dart @@ -69,4 +69,6 @@ abstract class SyncApi { bool isValidCronExpression(String cronExpression); @async bool modifyJobCronExpression(String jobId, String cronExpression); + @async + String? getValue(String name); } From 89bd607385d42160024638398ebb9c3ec2b9a452 Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Tue, 6 Jan 2026 15:20:52 +0530 Subject: [PATCH 03/12] RCF-1278: Editable Cron Job in Scheduled Job Settings Signed-off-by: sachin.sp --- lib/provider/sync_provider.dart | 58 ++++++++++++ .../widgets/scheduled_jobs_settings.dart | 93 ++++++++++--------- 2 files changed, 109 insertions(+), 42 deletions(-) diff --git a/lib/provider/sync_provider.dart b/lib/provider/sync_provider.dart index 2f85874a5..f7b14ea80 100644 --- a/lib/provider/sync_provider.dart +++ b/lib/provider/sync_provider.dart @@ -5,6 +5,7 @@ * */ +import 'dart:async'; import 'dart:convert'; import 'dart:developer'; @@ -34,6 +35,9 @@ class SyncProvider with ChangeNotifier { bool isSyncInProgress = false; bool _isSyncAndUploadInProgress = false; + Timer? _jobStatusPollingTimer; + final Map _jobStatuses = {}; + String get lastSuccessfulSyncTime => _lastSuccessfulSyncTime; int get currentSyncProgress => _currentSyncProgress; String get currentProgressType => _currentProgressType; @@ -48,6 +52,8 @@ class SyncProvider with ChangeNotifier { bool get cacertsSyncSuccess => _cacertsSyncSuccess; bool get kernelCertsSyncSuccess => _kernelCertsSyncSuccess; + Map get jobStatuses => _jobStatuses; + set isSyncing(bool value) { _isSyncing = value; notifyListeners(); @@ -280,4 +286,56 @@ class SyncProvider with ChangeNotifier { return null; } } + + void startJobPolling() { + refreshJobStatuses(); // Initial fetch + _jobStatusPollingTimer?.cancel(); + _jobStatusPollingTimer = Timer.periodic(const Duration(seconds: 30), (_) { + refreshJobStatuses(); + }); + } + + void stopJobPolling() { + _jobStatusPollingTimer?.cancel(); + _jobStatusPollingTimer = null; + } + + Future refreshJobStatuses() async { + try { + final activeJobs = await syncResponseService.getActiveSyncJobs(); + for (final jobJson in activeJobs) { + if (jobJson == null) continue; + try { + final job = SyncJobDef.fromJson(json.decode(jobJson) as Map); + if (job.id != null) { + final lastSync = await getLastSyncTimeByJobId(job.id!); + final nextSync = await getNextSyncTimeByJobId(job.id!); + + _jobStatuses[job.id!] = JobStatus(id: job.id!, lastSyncTime: lastSync, nextSyncTime: nextSync); + } + } catch (e) { + log("Error parsing job during polling: $e"); + } + } + + await getLastSyncTime(); // Update global last sync time (fallback for Master Sync) + notifyListeners(); + } catch (e) { + log("Error refreshing job statuses: $e"); + } + } + + @override + void dispose() { + stopJobPolling(); + super.dispose(); + } +} + +class JobStatus { + final String id; + final String? lastSyncTime; + final String? nextSyncTime; + + JobStatus({required this.id, this.lastSyncTime, this.nextSyncTime}); } diff --git a/lib/ui/settings/widgets/scheduled_jobs_settings.dart b/lib/ui/settings/widgets/scheduled_jobs_settings.dart index 9af3f856d..5e8fb3016 100644 --- a/lib/ui/settings/widgets/scheduled_jobs_settings.dart +++ b/lib/ui/settings/widgets/scheduled_jobs_settings.dart @@ -33,6 +33,9 @@ class _ScheduledJobsSettingsState extends State { @override void initState() { super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + context.read().startJobPolling(); + }); _loadPermittedJobs(); } @@ -40,6 +43,7 @@ class _ScheduledJobsSettingsState extends State { try { final service = SyncResponseService(); final permittedJobs = await service.getPermittedJobs(); + if (!mounted) return; setState(() { _permittedJobs = permittedJobs; _isLoadingPermittedJobs = false; @@ -52,6 +56,23 @@ class _ScheduledJobsSettingsState extends State { } } + @override + void dispose() { + // Only stop polling if we are sure we started it and valid context access + // But since context might be invalid, we skip explicitly stopping here + // unless we store the provider reference. + // Actually, let's store it in initState or just accept it runs? + // Best practice: Access provider in didChangeDependencies or similar? + // For now, let's leave start in initState and we can add a deactivate hook. + super.dispose(); + } + + @override + void deactivate() { + context.read().stopJobPolling(); + super.deactivate(); + } + @override Widget build(BuildContext context) { final jobs = widget.jobJsonList @@ -59,12 +80,6 @@ class _ScheduledJobsSettingsState extends State { .map((e) => _ScheduledJob.fromJson(json.decode(e) as Map)) .toList(); - final bottomInset = MediaQuery.of(context).padding.bottom; - final bottomSpacer = bottomInset + kBottomNavigationBarHeight + 230; - final mediaSize = MediaQuery.of(context).size; - final bool isTablet = mediaSize.shortestSide <= 450; - final int crossAxisCount = isTablet ? 1 : 2; - final double childAspectRatio = MediaQuery.of(context).orientation == Orientation.landscape ? 5 : 3; return SafeArea( top: false, bottom: true, @@ -89,10 +104,10 @@ class _ScheduledJobsSettingsState extends State { padding: const EdgeInsets.symmetric(horizontal: 12.0), sliver: SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, + crossAxisCount: MediaQuery.of(context).size.shortestSide <= 450 ? 1 : 2, mainAxisSpacing: 8, crossAxisSpacing: 12, - childAspectRatio: childAspectRatio, + childAspectRatio: MediaQuery.of(context).orientation == Orientation.landscape ? 5 : 3, ), delegate: SliverChildBuilderDelegate( (context, index) { @@ -107,7 +122,7 @@ class _ScheduledJobsSettingsState extends State { ), ), ), - SliverToBoxAdapter(child: SizedBox(height: bottomSpacer)), + SliverToBoxAdapter(child: SizedBox(height: MediaQuery.of(context).padding.bottom + kBottomNavigationBarHeight + 230)), ], ), ); @@ -138,8 +153,6 @@ class _JobCardState extends State<_JobCard> { super.initState(); syncProvider = Provider.of(context, listen: false); _loadCronExpression(); // Load custom cron expression or default - _loadLastSyncTime(); // Fetch last sync when widget loads - _loadNextSyncTime(); } Future _loadCronExpression() async { @@ -162,35 +175,17 @@ class _JobCardState extends State<_JobCard> { super.dispose(); } - Future _loadLastSyncTime() async { - if (widget.job.id != null && widget.job.id!.isNotEmpty) { - final value = await syncProvider.getLastSyncTimeByJobId(widget.job.id!); - setState(() => _lastSync = value ?? '-'); - if (widget.job.apiName == "masterSyncJob" && _lastSync == "NA") { - _lastSync = formatDate(syncProvider.lastSuccessfulSyncTime); - setState(() {}); - } - } else { - setState(() => _lastSync = '-'); - } - } - String formatDate(String dateString) { - // Parse the input UTC date string - DateTime dateTime = DateTime.parse(dateString).toLocal(); // Convert to local time - - // Format the date - String formattedDate = DateFormat("yyyy-MMM-dd HH:mm:ss").format(dateTime); + try { + // Parse the input UTC date string + DateTime dateTime = DateTime.parse(dateString).toLocal(); // Convert to local time - return formattedDate; - } + // Format the date + String formattedDate = DateFormat("yyyy-MMM-dd HH:mm:ss").format(dateTime); - Future _loadNextSyncTime() async { - if (widget.job.id != null && widget.job.id!.isNotEmpty) { - final value = await syncProvider.getNextSyncTimeByJobId(widget.job.id!); - setState(() => _nextSync = value ?? '-'); - } else { - setState(() => _nextSync = '-'); + return formattedDate; + } catch(e) { + return dateString; } } @@ -309,9 +304,8 @@ class _JobCardState extends State<_JobCard> { return; } - // Refresh last and next sync time after successful sync - await _loadLastSyncTime(); - await _loadNextSyncTime(); + // Refresh last and next sync time after successful sync + await context.read().refreshJobStatuses(); } catch (e) { debugPrint('Sync failed for ${widget.job.id}: $e'); @@ -342,8 +336,23 @@ class _JobCardState extends State<_JobCard> { children: [ Text(job.name ?? job.apiName ?? 'Unknown Job', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), - _kv('Next Run', _nextSync ?? '-'), - _kv('Last Sync', _lastSync ?? '-'), + Consumer( + builder: (context, provider, child) { + final status = provider.jobStatuses[job.id]; + String lastSync = status?.lastSyncTime ?? '-'; + if (widget.job.apiName == "masterSyncJob" && lastSync == "NA") { + lastSync = formatDate(provider.lastSuccessfulSyncTime); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _kv('Next Run', status?.nextSyncTime ?? '-'), + _kv('Last Sync', lastSync), + ], + ); + }, + ), if (widget.isPermitted) Row( crossAxisAlignment: CrossAxisAlignment.start, From ee50c75f20960a3942874f94da680f3457dcabe0 Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Tue, 6 Jan 2026 16:58:25 +0530 Subject: [PATCH 04/12] RCF-1278: fixed coderabbit changes Signed-off-by: sachin.sp --- .../registration_client/utils/BatchJob.java | 2 +- .../clientmanager/config/RoomModule.java | 5 ++- .../clientmanager/dao/LocalConfigDAOImpl.java | 42 +++++++++++-------- .../service/LocalConfigServiceImpl.java | 19 +++++++++ .../widgets/scheduled_jobs_settings.dart | 18 +++++++- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java b/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java index fba3dccb8..47095af4d 100644 --- a/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java +++ b/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java @@ -210,7 +210,7 @@ public long getIntervalMillis(String api) { List syncJobs = syncJobDefRepository.getAllSyncJobDefList(); for (SyncJobDef value : syncJobs) { if (Objects.equals(value.getApiName(), api)) { - // Check for custom cron expression first (matches desktop client logic) + // Check for custom cron expression if (localConfigService != null) { String customCron = localConfigService.getValue(value.getId()); if (customCron != null && !customCron.trim().isEmpty()) { diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/RoomModule.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/RoomModule.java index c97410969..9b2a29576 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/RoomModule.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/RoomModule.java @@ -469,10 +469,11 @@ LocalPreferencesDao providesLocalPreferencesDao(ClientDatabase clientDatabase) { @Provides @Singleton - LocalConfigDAO provideLocalConfigDAO(PermittedLocalConfigDao permittedLocalConfigDao, LocalPreferencesDao localPreferencesDao) { + LocalConfigDAO provideLocalConfigDAO(PermittedLocalConfigDao permittedLocalConfigDao, LocalPreferencesDao localPreferencesDao, ClientDatabase clientDatabase) { return new LocalConfigDAOImpl( new PermittedLocalConfigRepository(permittedLocalConfigDao), - new LocalPreferencesRepository(localPreferencesDao) + new LocalPreferencesRepository(localPreferencesDao), + clientDatabase ); } } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java index 59c6e8d30..41da55dc9 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java @@ -10,6 +10,7 @@ import javax.inject.Inject; import javax.inject.Singleton; +import io.mosip.registration.clientmanager.config.ClientDatabase; import io.mosip.registration.clientmanager.constant.RegistrationConstants; import io.mosip.registration.clientmanager.entity.LocalPreferences; import io.mosip.registration.clientmanager.entity.PermittedLocalConfig; @@ -22,12 +23,15 @@ public class LocalConfigDAOImpl implements LocalConfigDAO { private static final String TAG = LocalConfigDAOImpl.class.getSimpleName(); private PermittedLocalConfigRepository permittedLocalConfigRepository; private LocalPreferencesRepository localPreferencesRepository; + private ClientDatabase clientDatabase; @Inject public LocalConfigDAOImpl(PermittedLocalConfigRepository permittedLocalConfigRepository, - LocalPreferencesRepository localPreferencesRepository) { + LocalPreferencesRepository localPreferencesRepository, + ClientDatabase clientDatabase) { this.permittedLocalConfigRepository = permittedLocalConfigRepository; this.localPreferencesRepository = localPreferencesRepository; + this.clientDatabase = clientDatabase; } @Override @@ -93,23 +97,27 @@ public String getValue(String name) { @Override public void modifyJob(String name, String value) { Log.i(TAG, "Modifying sync frequency for the job " + name); - - try { - // Check if existing preference exists - LocalPreferences localPreferences = localPreferencesRepository - .findByIsDeletedFalseAndName(name); - - if (localPreferences != null) { - // Soft delete existing record - updateLocalPreference(localPreferences, RegistrationConstants.PERMITTED_JOB_TYPE); + + // Use database transaction to ensure atomicity and thread safety of read-check-write operations + clientDatabase.runInTransaction(() -> { + try { + // Check if existing preference exists + LocalPreferences localPreferences = localPreferencesRepository + .findByIsDeletedFalseAndName(name); + + if (localPreferences != null) { + // Soft delete existing record + updateLocalPreference(localPreferences, RegistrationConstants.PERMITTED_JOB_TYPE); + } + + // Create new record with updated cron expression + saveLocalPreference(name, value, RegistrationConstants.PERMITTED_JOB_TYPE); + } catch (Exception e) { + Log.e(TAG, "Error modifying job: " + name, e); + // Re-throw to trigger transaction rollback + throw new RuntimeException("Failed to modify job: " + name, e); } - - // Create new record with updated cron expression - saveLocalPreference(name, value, RegistrationConstants.PERMITTED_JOB_TYPE); - } catch (Exception e) { - Log.e(TAG, "Error modifying job: " + name, e); - throw e; - } + }); } /** diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java index 183a6ff0a..bc56d0ce3 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java @@ -1,5 +1,7 @@ package io.mosip.registration.clientmanager.service; +import android.util.Log; + import java.util.List; import java.util.Map; @@ -9,10 +11,12 @@ import io.mosip.registration.clientmanager.constant.RegistrationConstants; import io.mosip.registration.clientmanager.dao.LocalConfigDAO; import io.mosip.registration.clientmanager.spi.LocalConfigService; +import io.mosip.registration.clientmanager.util.CronExpressionParser; @Singleton public class LocalConfigServiceImpl implements LocalConfigService { + private static final String TAG = LocalConfigServiceImpl.class.getSimpleName(); private LocalConfigDAO localConfigDAO; @Inject @@ -42,6 +46,21 @@ public String getValue(String name) { @Override public void modifyJob(String name, String value) { + // Validate cron expression before persisting to database + // This prevents invalid cron expressions from being stored, which could cause + // job scheduling failures or runtime errors when the cron is used + if (value == null || value.trim().isEmpty()) { + Log.e(TAG, "Cannot modify job " + name + ": cron expression is null or empty"); + throw new IllegalArgumentException("Cron expression cannot be null or empty"); + } + + if (!CronExpressionParser.isValidCronExpression(value)) { + Log.e(TAG, "Cannot modify job " + name + ": invalid cron expression: " + value); + throw new IllegalArgumentException("Invalid cron expression: " + value); + } + + // Delegate to DAO only after validation passes + // The DAO layer handles the transaction-safe persistence localConfigDAO.modifyJob(name, value); } diff --git a/lib/ui/settings/widgets/scheduled_jobs_settings.dart b/lib/ui/settings/widgets/scheduled_jobs_settings.dart index 5e8fb3016..d89e68d1c 100644 --- a/lib/ui/settings/widgets/scheduled_jobs_settings.dart +++ b/lib/ui/settings/widgets/scheduled_jobs_settings.dart @@ -157,7 +157,7 @@ class _JobCardState extends State<_JobCard> { Future _loadCronExpression() async { if (widget.job.id != null && widget.job.id!.isNotEmpty) { - // Check for custom cron expression first (matches desktop client logic) + // Check for custom cron expression final customCron = await _syncResponseService.getValue(widget.job.id!); if (customCron != null && customCron.trim().isNotEmpty) { _cronController.text = customCron; // Use saved custom cron expression @@ -217,10 +217,24 @@ class _JobCardState extends State<_JobCard> { _cronError = null; }); + // Validate job ID before proceeding + final jobId = widget.job.id; + if (jobId == null || jobId.isEmpty) { + setState(() { + _cronError = 'Job ID is required'; + }); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Cannot save cron expression: Job ID is missing')), + ); + } + return; + } + // Save cron expression try { final success = await _syncResponseService.modifyJobCronExpression( - widget.job.id ?? '', + jobId, cronExpression, ); From 9cf26456ad9ca5729bbd8ffd5030cf07c1dca17c Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Tue, 6 Jan 2026 17:18:33 +0530 Subject: [PATCH 05/12] RCF-1278: fixed coderabbit changes Signed-off-by: sachin.sp --- .../clientmanager/dao/LocalConfigDAOImpl.java | 2 - .../widgets/scheduled_jobs_settings.dart | 37 +++++++++---------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java index 41da55dc9..c9364dac9 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java @@ -96,8 +96,6 @@ public String getValue(String name) { @Override public void modifyJob(String name, String value) { - Log.i(TAG, "Modifying sync frequency for the job " + name); - // Use database transaction to ensure atomicity and thread safety of read-check-write operations clientDatabase.runInTransaction(() -> { try { diff --git a/lib/ui/settings/widgets/scheduled_jobs_settings.dart b/lib/ui/settings/widgets/scheduled_jobs_settings.dart index d89e68d1c..f0d1a6920 100644 --- a/lib/ui/settings/widgets/scheduled_jobs_settings.dart +++ b/lib/ui/settings/widgets/scheduled_jobs_settings.dart @@ -29,12 +29,14 @@ class ScheduledJobsSettings extends StatefulWidget { class _ScheduledJobsSettingsState extends State { List _permittedJobs = []; bool _isLoadingPermittedJobs = true; + SyncProvider? _syncProvider; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - context.read().startJobPolling(); + _syncProvider = context.read(); + _syncProvider?.startJobPolling(); }); _loadPermittedJobs(); } @@ -58,20 +60,9 @@ class _ScheduledJobsSettingsState extends State { @override void dispose() { - // Only stop polling if we are sure we started it and valid context access - // But since context might be invalid, we skip explicitly stopping here - // unless we store the provider reference. - // Actually, let's store it in initState or just accept it runs? - // Best practice: Access provider in didChangeDependencies or similar? - // For now, let's leave start in initState and we can add a deactivate hook. + _syncProvider?.stopJobPolling(); super.dispose(); } - - @override - void deactivate() { - context.read().stopJobPolling(); - super.deactivate(); - } @override Widget build(BuildContext context) { @@ -156,15 +147,21 @@ class _JobCardState extends State<_JobCard> { } Future _loadCronExpression() async { - if (widget.job.id != null && widget.job.id!.isNotEmpty) { - // Check for custom cron expression - final customCron = await _syncResponseService.getValue(widget.job.id!); - if (customCron != null && customCron.trim().isNotEmpty) { - _cronController.text = customCron; // Use saved custom cron expression + try { + if (widget.job.id != null && widget.job.id!.isNotEmpty) { + // Check for custom cron expression + final customCron = await _syncResponseService.getValue(widget.job.id!); + if (customCron != null && customCron.trim().isNotEmpty) { + _cronController.text = customCron; // Use saved custom cron expression + } else { + _cronController.text = widget.job.syncFreq ?? ''; // Use default from DB + } } else { - _cronController.text = widget.job.syncFreq ?? ''; // Use default from DB + _cronController.text = widget.job.syncFreq ?? ''; } - } else { + } catch (e) { + debugPrint('Failed to load cron expression: $e'); + // Fallback to default cron expression from job definition _cronController.text = widget.job.syncFreq ?? ''; } } From 5dce80a4cfbf46021e67cb7084a06639a4f01c86 Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Tue, 6 Jan 2026 17:31:26 +0530 Subject: [PATCH 06/12] RCF-1278: fixed coderabbit changes Signed-off-by: sachin.sp --- .../registration/clientmanager/dao/LocalConfigDAOImpl.java | 4 ++-- lib/ui/settings/widgets/scheduled_jobs_settings.dart | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java index c9364dac9..3c1ed3857 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java @@ -105,7 +105,7 @@ public void modifyJob(String name, String value) { if (localPreferences != null) { // Soft delete existing record - updateLocalPreference(localPreferences, RegistrationConstants.PERMITTED_JOB_TYPE); + updateLocalPreference(localPreferences); } // Create new record with updated cron expression @@ -121,7 +121,7 @@ public void modifyJob(String name, String value) { /** * Update local preference (soft delete) */ - private void updateLocalPreference(LocalPreferences localPreference, String configType) { + private void updateLocalPreference(LocalPreferences localPreference) { localPreference.setIsDeleted(true); localPreference.setUpdBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); localPreference.setUpdDtimes(System.currentTimeMillis()); diff --git a/lib/ui/settings/widgets/scheduled_jobs_settings.dart b/lib/ui/settings/widgets/scheduled_jobs_settings.dart index f0d1a6920..f62c3b445 100644 --- a/lib/ui/settings/widgets/scheduled_jobs_settings.dart +++ b/lib/ui/settings/widgets/scheduled_jobs_settings.dart @@ -316,6 +316,7 @@ class _JobCardState extends State<_JobCard> { } // Refresh last and next sync time after successful sync + if (!mounted) return; await context.read().refreshJobStatuses(); } catch (e) { From f3470c112e05fe1b85e5d9026d65282a71cbca8c Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Tue, 6 Jan 2026 17:45:59 +0530 Subject: [PATCH 07/12] RCF-1278: fixed coderabbit changes Signed-off-by: sachin.sp --- .../widgets/scheduled_jobs_settings.dart | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/ui/settings/widgets/scheduled_jobs_settings.dart b/lib/ui/settings/widgets/scheduled_jobs_settings.dart index f62c3b445..e1e135ebf 100644 --- a/lib/ui/settings/widgets/scheduled_jobs_settings.dart +++ b/lib/ui/settings/widgets/scheduled_jobs_settings.dart @@ -137,6 +137,7 @@ class _JobCardState extends State<_JobCard> { final TextEditingController _cronController = TextEditingController(); final SyncResponseService _syncResponseService = SyncResponseService(); String? _cronError; + bool _isSaving = false; @override @@ -187,11 +188,18 @@ class _JobCardState extends State<_JobCard> { } Future _modifyCronExpression() async { + if (_isSaving) return; + + setState(() { + _isSaving = true; + }); + final cronExpression = _cronController.text.trim(); if (cronExpression.isEmpty) { setState(() { _cronError = 'Cron expression cannot be empty'; + _isSaving = false; }); return; } @@ -201,6 +209,7 @@ class _JobCardState extends State<_JobCard> { if (!isValid) { setState(() { _cronError = 'Invalid cron expression'; + _isSaving = false; }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -219,6 +228,7 @@ class _JobCardState extends State<_JobCard> { if (jobId == null || jobId.isEmpty) { setState(() { _cronError = 'Job ID is required'; + _isSaving = false; }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -256,7 +266,11 @@ class _JobCardState extends State<_JobCard> { if (mounted) { Restart.restartApp(); } + // Note: No need to reset _isSaving here since app is restarting } else if (mounted) { + setState(() { + _isSaving = false; + }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Failed to save cron expression')), ); @@ -264,6 +278,9 @@ class _JobCardState extends State<_JobCard> { } catch (e) { debugPrint('Error modifying cron expression: $e'); if (mounted) { + setState(() { + _isSaving = false; + }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error: $e')), ); @@ -408,11 +425,20 @@ class _JobCardState extends State<_JobCard> { height: 32, width: 65, child: ElevatedButton( - onPressed: _modifyCronExpression, + onPressed: _isSaving ? null : _modifyCronExpression, style: ElevatedButton.styleFrom( padding: EdgeInsets.zero, ), - child: const Text('Submit', style: TextStyle(fontSize: 11)), + child: _isSaving + ? const SizedBox( + width: 12, + height: 12, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : const Text('Submit', style: TextStyle(fontSize: 11)), ), ), ], From 9792426bf4bd7b85f9c530669b87f9dcf1733686 Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Thu, 8 Jan 2026 18:14:20 +0530 Subject: [PATCH 08/12] review comment fixed Signed-off-by: sachin.sp --- .../api_services/MasterDataSyncApi.java | 19 ++++-------- .../registration_client/utils/BatchJob.java | 29 ++++++++----------- .../clientmanager/dao/LocalConfigDAOImpl.java | 23 +++++---------- .../clientmanager/dao/SyncJobDefDao.java | 12 +++++++- .../repository/SyncJobDefRepository.java | 20 +++++++++++++ 5 files changed, 56 insertions(+), 47 deletions(-) diff --git a/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java b/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java index 57ce2b87f..4954c1bd6 100644 --- a/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java +++ b/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java @@ -463,15 +463,8 @@ public void modifyJobCronExpression(@NonNull String jobId, @NonNull String cronE try { localConfigService.modifyJob(jobId, cronExpression); - // Reschedule the job with new cron expression - List activeJobs = syncJobDefRepository.getActiveSyncJobs(); - SyncJobDef jobDef = null; - for (SyncJobDef job : activeJobs) { - if (job.getId().equals(jobId)) { - jobDef = job; - break; - } - } + // Fetch specific sync job definition by jobId + SyncJobDef jobDef = syncJobDefRepository.getSyncJobDefById(jobId); if (jobDef != null) { // Refresh job status to apply new cron expression @@ -592,11 +585,9 @@ public void executeJobByApiName(String jobApiName, Context context) { private String getJobIdByApiName(String apiName) { try { - List jobs = syncJobDefRepository.getAllSyncJobDefList(); - for (SyncJobDef job : jobs) { - if (apiName.equals(job.getApiName())) { - return job.getId(); - } + SyncJobDef job = syncJobDefRepository.getSyncJobDefByApiName(apiName); + if (job != null) { + return job.getId(); } } catch (Exception e) { Log.e(getClass().getSimpleName(), "Error getting job ID for: " + apiName, e); diff --git a/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java b/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java index 47095af4d..88db2bc3a 100644 --- a/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java +++ b/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java @@ -207,24 +207,19 @@ public Integer getBatchSize() { public long getIntervalMillis(String api) { // Default everyday at Noon - 12pm String cronExp = ClientManagerConstant.DEFAULT_UPLOAD_CRON; - List syncJobs = syncJobDefRepository.getAllSyncJobDefList(); - for (SyncJobDef value : syncJobs) { - if (Objects.equals(value.getApiName(), api)) { - // Check for custom cron expression - if (localConfigService != null) { - String customCron = localConfigService.getValue(value.getId()); - if (customCron != null && !customCron.trim().isEmpty()) { - cronExp = customCron; // Use custom cron expression - Log.d(getClass().getSimpleName(), api + " Custom Cron Expression : " + cronExp); - } else { - cronExp = String.valueOf(value.getSyncFreq()); // Use default from DB - Log.d(getClass().getSimpleName(), api + " Default Cron Expression : " + cronExp); - } - } else { - cronExp = String.valueOf(value.getSyncFreq()); - Log.d(getClass().getSimpleName(), api + " Cron Expression : " + cronExp); + SyncJobDef syncJob = syncJobDefRepository.getSyncJobDefByApiName(api); + if (syncJob != null) { + // Use default from DB first + cronExp = String.valueOf(syncJob.getSyncFreq()); + Log.d(getClass().getSimpleName(), api + " Default Cron Expression : " + cronExp); + + // Check for custom cron expression and override if available + if (localConfigService != null) { + String customCron = localConfigService.getValue(syncJob.getId()); + if (customCron != null && !customCron.trim().isEmpty()) { + cronExp = customCron; // Use custom cron expression + Log.d(getClass().getSimpleName(), api + " Custom Cron Expression : " + cronExp); } - break; } } long nextExecution = CronParserUtil.getNextExecutionTimeInMillis(cronExp); diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java index 3c1ed3857..2e98d3dac 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java @@ -104,12 +104,15 @@ public void modifyJob(String name, String value) { .findByIsDeletedFalseAndName(name); if (localPreferences != null) { - // Soft delete existing record - updateLocalPreference(localPreferences); + // Update existing record + localPreferences.setVal(value); + localPreferences.setUpdBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); + localPreferences.setUpdDtimes(System.currentTimeMillis()); + localPreferencesRepository.save(localPreferences); + } else { + // Create new record if it doesn't exist + saveLocalPreference(name, value, RegistrationConstants.PERMITTED_JOB_TYPE); } - - // Create new record with updated cron expression - saveLocalPreference(name, value, RegistrationConstants.PERMITTED_JOB_TYPE); } catch (Exception e) { Log.e(TAG, "Error modifying job: " + name, e); // Re-throw to trigger transaction rollback @@ -118,16 +121,6 @@ public void modifyJob(String name, String value) { }); } - /** - * Update local preference (soft delete) - */ - private void updateLocalPreference(LocalPreferences localPreference) { - localPreference.setIsDeleted(true); - localPreference.setUpdBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); - localPreference.setUpdDtimes(System.currentTimeMillis()); - localPreferencesRepository.save(localPreference); - } - /** * Save local preference to database */ diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/SyncJobDefDao.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/SyncJobDefDao.java index 8e6b35310..08ede1eea 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/SyncJobDefDao.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/SyncJobDefDao.java @@ -28,11 +28,21 @@ public interface SyncJobDefDao { /** * To get a job in the List of {@link SyncJobDef} * + * @param jobId the job id * @return sync job */ - @Query("select * from sync_job_def where is_deleted == 0 or is_deleted is null and id=:jobId") + @Query("select * from sync_job_def where (is_deleted == 0 or is_deleted is null) and id=:jobId") SyncJobDef findOneById(String jobId); + /** + * To get a job by API name in the List of {@link SyncJobDef} + * + * @param apiName the API name + * @return sync job + */ + @Query("select * from sync_job_def where (is_deleted == 0 or is_deleted is null) and api_name=:apiName") + SyncJobDef findOneByApiName(String apiName); + /** * To get all the List of active {@link SyncJobDef} * diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/SyncJobDefRepository.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/SyncJobDefRepository.java index ce561fa2b..7e65b2b38 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/SyncJobDefRepository.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/SyncJobDefRepository.java @@ -36,4 +36,24 @@ public List getActiveSyncJobs() { List activeJobs = this.syncJobDefDao.findAllByActiveStatus(true); return activeJobs; } + + /** + * Get a sync job definition by job ID + * + * @param jobId the job ID + * @return sync job definition or null if not found + */ + public SyncJobDef getSyncJobDefById(String jobId) { + return this.syncJobDefDao.findOneById(jobId); + } + + /** + * Get a sync job definition by API name + * + * @param apiName the API name + * @return sync job definition or null if not found + */ + public SyncJobDef getSyncJobDefByApiName(String apiName) { + return this.syncJobDefDao.findOneByApiName(apiName); + } } From e0ace6aba2c8951e7b193526a3d75adaf86deee1 Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Fri, 9 Jan 2026 16:29:50 +0530 Subject: [PATCH 09/12] review comment fixed Signed-off-by: sachin.sp --- .../clientmanager/config/RoomModule.java | 5 +-- .../clientmanager/dao/LocalConfigDAOImpl.java | 44 +++++++------------ 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/RoomModule.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/RoomModule.java index 9b2a29576..c97410969 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/RoomModule.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/config/RoomModule.java @@ -469,11 +469,10 @@ LocalPreferencesDao providesLocalPreferencesDao(ClientDatabase clientDatabase) { @Provides @Singleton - LocalConfigDAO provideLocalConfigDAO(PermittedLocalConfigDao permittedLocalConfigDao, LocalPreferencesDao localPreferencesDao, ClientDatabase clientDatabase) { + LocalConfigDAO provideLocalConfigDAO(PermittedLocalConfigDao permittedLocalConfigDao, LocalPreferencesDao localPreferencesDao) { return new LocalConfigDAOImpl( new PermittedLocalConfigRepository(permittedLocalConfigDao), - new LocalPreferencesRepository(localPreferencesDao), - clientDatabase + new LocalPreferencesRepository(localPreferencesDao) ); } } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java index 2e98d3dac..77d7e25ab 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java @@ -10,7 +10,6 @@ import javax.inject.Inject; import javax.inject.Singleton; -import io.mosip.registration.clientmanager.config.ClientDatabase; import io.mosip.registration.clientmanager.constant.RegistrationConstants; import io.mosip.registration.clientmanager.entity.LocalPreferences; import io.mosip.registration.clientmanager.entity.PermittedLocalConfig; @@ -23,15 +22,12 @@ public class LocalConfigDAOImpl implements LocalConfigDAO { private static final String TAG = LocalConfigDAOImpl.class.getSimpleName(); private PermittedLocalConfigRepository permittedLocalConfigRepository; private LocalPreferencesRepository localPreferencesRepository; - private ClientDatabase clientDatabase; @Inject public LocalConfigDAOImpl(PermittedLocalConfigRepository permittedLocalConfigRepository, - LocalPreferencesRepository localPreferencesRepository, - ClientDatabase clientDatabase) { + LocalPreferencesRepository localPreferencesRepository) { this.permittedLocalConfigRepository = permittedLocalConfigRepository; this.localPreferencesRepository = localPreferencesRepository; - this.clientDatabase = clientDatabase; } @Override @@ -96,29 +92,23 @@ public String getValue(String name) { @Override public void modifyJob(String name, String value) { - // Use database transaction to ensure atomicity and thread safety of read-check-write operations - clientDatabase.runInTransaction(() -> { - try { - // Check if existing preference exists - LocalPreferences localPreferences = localPreferencesRepository - .findByIsDeletedFalseAndName(name); - - if (localPreferences != null) { - // Update existing record - localPreferences.setVal(value); - localPreferences.setUpdBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); - localPreferences.setUpdDtimes(System.currentTimeMillis()); - localPreferencesRepository.save(localPreferences); - } else { - // Create new record if it doesn't exist - saveLocalPreference(name, value, RegistrationConstants.PERMITTED_JOB_TYPE); - } - } catch (Exception e) { - Log.e(TAG, "Error modifying job: " + name, e); - // Re-throw to trigger transaction rollback - throw new RuntimeException("Failed to modify job: " + name, e); + try { + LocalPreferences localPreferences = localPreferencesRepository + .findByIsDeletedFalseAndName(name); + + if (localPreferences != null) { + // Update existing record + localPreferences.setVal(value); + localPreferences.setUpdBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); + localPreferences.setUpdDtimes(System.currentTimeMillis()); + localPreferencesRepository.save(localPreferences); + } else { + // Create new record if it doesn't exist + saveLocalPreference(name, value, RegistrationConstants.PERMITTED_JOB_TYPE); } - }); + } catch (Exception e) { + Log.e(TAG, "Error modifying job: " + name, e); + } } /** From 243b482d60fe9b480895e23ecbe08ef5064aa243 Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Fri, 9 Jan 2026 17:16:31 +0530 Subject: [PATCH 10/12] review comment fixed Signed-off-by: sachin.sp --- .../clientmanager/dao/LocalConfigDAOImpl.java | 74 ++++++++++--------- .../service/LocalConfigServiceImpl.java | 5 ++ 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java index 77d7e25ab..465e6f868 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java @@ -25,15 +25,15 @@ public class LocalConfigDAOImpl implements LocalConfigDAO { @Inject public LocalConfigDAOImpl(PermittedLocalConfigRepository permittedLocalConfigRepository, - LocalPreferencesRepository localPreferencesRepository) { + LocalPreferencesRepository localPreferencesRepository) { this.permittedLocalConfigRepository = permittedLocalConfigRepository; this.localPreferencesRepository = localPreferencesRepository; } @Override public List getPermittedConfigurations(String configType) { - List permittedConfigs = - permittedLocalConfigRepository.getPermittedConfigsByType(configType); + List permittedConfigs = permittedLocalConfigRepository + .getPermittedConfigsByType(configType); List permittedConfigurations = new ArrayList<>(); if (permittedConfigs != null && !permittedConfigs.isEmpty()) { @@ -51,32 +51,19 @@ public Map getLocalConfigurations() { @Override public void modifyConfigurations(Map localPreferences) { - + for (Map.Entry entry : localPreferences.entrySet()) { String name = entry.getKey(); String value = entry.getValue(); - + try { - LocalPreferences existingPreference = localPreferencesRepository.findByIsDeletedFalseAndName(name); - - if (existingPreference != null) { - // Update existing record - existingPreference.setVal(value); - existingPreference.setUpdBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); - existingPreference.setUpdDtimes(System.currentTimeMillis()); - localPreferencesRepository.save(existingPreference); - } else { - // Create new record if it doesn't exist - saveLocalPreference(name, value, RegistrationConstants.PERMITTED_CONFIG_TYPE); - } - + saveOrUpdateLocalPreference(name, value, RegistrationConstants.PERMITTED_CONFIG_TYPE); } catch (Exception e) { Log.e(TAG, "Error modifying configuration: " + name, e); } } } - @Override public String getValue(String name) { try { @@ -92,22 +79,36 @@ public String getValue(String name) { @Override public void modifyJob(String name, String value) { + if (name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException("Job name cannot be null or empty"); + } + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Job value cannot be null or empty"); + } + try { - LocalPreferences localPreferences = localPreferencesRepository - .findByIsDeletedFalseAndName(name); - - if (localPreferences != null) { - // Update existing record - localPreferences.setVal(value); - localPreferences.setUpdBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); - localPreferences.setUpdDtimes(System.currentTimeMillis()); - localPreferencesRepository.save(localPreferences); - } else { - // Create new record if it doesn't exist - saveLocalPreference(name, value, RegistrationConstants.PERMITTED_JOB_TYPE); - } + saveOrUpdateLocalPreference(name, value, RegistrationConstants.PERMITTED_JOB_TYPE); } catch (Exception e) { Log.e(TAG, "Error modifying job: " + name, e); + throw new RuntimeException("Failed to modify job: " + name, e); + } + } + + /** + * Save local preference to database + */ + private void saveOrUpdateLocalPreference(String name, String value, String configType) { + LocalPreferences existingPreference = localPreferencesRepository.findByIsDeletedFalseAndName(name); + + if (existingPreference != null) { + // Update existing record + existingPreference.setVal(value); + existingPreference.setUpdBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); + existingPreference.setUpdDtimes(System.currentTimeMillis()); + localPreferencesRepository.save(existingPreference); + } else { + // Create new record if it doesn't exist + saveLocalPreference(name, value, configType); } } @@ -122,7 +123,7 @@ private void saveLocalPreference(String name, String value, String configType) { localPreference.setCrBy(RegistrationConstants.JOB_TRIGGER_POINT_USER); localPreference.setCrDtime(System.currentTimeMillis()); localPreference.setIsDeleted(false); - + localPreferencesRepository.save(localPreference); } @@ -132,8 +133,8 @@ private void saveLocalPreference(String name, String value, String configType) { * Mark as deleted if key is deactivated in permitted configs. */ public void cleanUpLocalPreferences() { - List permittedConfigs = - permittedLocalConfigRepository.getPermittedConfigsByType(RegistrationConstants.PERMITTED_CONFIG_TYPE); + List permittedConfigs = permittedLocalConfigRepository + .getPermittedConfigsByType(RegistrationConstants.PERMITTED_CONFIG_TYPE); Map localConfigs = getLocalConfigurations(); @@ -144,7 +145,8 @@ public void cleanUpLocalPreferences() { for (String key : localConfigs.keySet()) { LocalPreferences pref = localPreferencesRepository.findByIsDeletedFalseAndName(key); - if (pref == null) continue; + if (pref == null) + continue; if (!permittedStatusMap.containsKey(key)) { localPreferencesRepository.delete(pref); diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java index bc56d0ce3..60ac76ffa 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java @@ -59,6 +59,11 @@ public void modifyJob(String name, String value) { throw new IllegalArgumentException("Invalid cron expression: " + value); } + if (!getPermittedJobs().contains(name)) { + Log.e(TAG, "Cannot modify job " + name + ": not a permitted job"); + throw new IllegalArgumentException("Job modification not permitted for: " + name); + } + // Delegate to DAO only after validation passes // The DAO layer handles the transaction-safe persistence localConfigDAO.modifyJob(name, value); From 6b8bdda1e418804c74aa9c369d0cfa77679e2938 Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Fri, 9 Jan 2026 17:41:39 +0530 Subject: [PATCH 11/12] review comment fixed Signed-off-by: sachin.sp --- .../clientmanager/dao/LocalConfigDAOImpl.java | 8 ++++++-- .../clientmanager/dao/LocalPreferencesDao.java | 3 +++ .../repository/LocalPreferencesRepository.java | 13 +++++++++++++ .../service/LocalConfigServiceImpl.java | 6 ++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java index 465e6f868..c1534a453 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java @@ -96,9 +96,11 @@ public void modifyJob(String name, String value) { /** * Save local preference to database + * Uses configType-aware lookup to prevent cross-contamination between JOB and CONFIGURATION preferences */ private void saveOrUpdateLocalPreference(String name, String value, String configType) { - LocalPreferences existingPreference = localPreferencesRepository.findByIsDeletedFalseAndName(name); + LocalPreferences existingPreference = localPreferencesRepository + .findByIsDeletedFalseAndNameAndConfigType(name, configType); if (existingPreference != null) { // Update existing record @@ -144,7 +146,9 @@ public void cleanUpLocalPreferences() { } for (String key : localConfigs.keySet()) { - LocalPreferences pref = localPreferencesRepository.findByIsDeletedFalseAndName(key); + // Use configType-aware lookup to ensure we only clean up CONFIGURATION type preferences + LocalPreferences pref = localPreferencesRepository + .findByIsDeletedFalseAndNameAndConfigType(key, RegistrationConstants.PERMITTED_CONFIG_TYPE); if (pref == null) continue; diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalPreferencesDao.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalPreferencesDao.java index f283f269d..ed2b5d350 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalPreferencesDao.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalPreferencesDao.java @@ -25,6 +25,9 @@ public interface LocalPreferencesDao { @Query("SELECT * FROM local_preferences WHERE is_deleted = 0 AND name = :name") LocalPreferences findByIsDeletedFalseAndName(String name); + @Query("SELECT * FROM local_preferences WHERE is_deleted = 0 AND name = :name AND config_type = :configType") + LocalPreferences findByIsDeletedFalseAndNameAndConfigType(String name, String configType); + @Query("DELETE FROM local_preferences WHERE name = :name") void deleteByName(String name); } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/LocalPreferencesRepository.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/LocalPreferencesRepository.java index 55f9431b4..4df98c260 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/LocalPreferencesRepository.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/LocalPreferencesRepository.java @@ -55,6 +55,19 @@ public LocalPreferences findByIsDeletedFalseAndName(String name) { } } + /** + * Find local preference by name and config type + * This prevents cross-contamination between JOB and CONFIGURATION preferences + */ + public LocalPreferences findByIsDeletedFalseAndNameAndConfigType(String name, String configType) { + try { + return localPreferencesDao.findByIsDeletedFalseAndNameAndConfigType(name, configType); + } catch (Exception e) { + Log.e(TAG, "Error finding local preference by name and config type: " + name + ", " + configType, e); + return null; + } + } + /** * Save local preference */ diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java index 60ac76ffa..b7a4bedb0 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java @@ -46,6 +46,12 @@ public String getValue(String name) { @Override public void modifyJob(String name, String value) { + // Validate job name is not null or empty + if (name == null || name.trim().isEmpty()) { + Log.e(TAG, "Cannot modify job: job name is null or empty"); + throw new IllegalArgumentException("Job name cannot be null or empty"); + } + // Validate cron expression before persisting to database // This prevents invalid cron expressions from being stored, which could cause // job scheduling failures or runtime errors when the cron is used From 6b3bdf790f95a36338b7b1b022fb866421e4107e Mon Sep 17 00:00:00 2001 From: "sachin.sp" Date: Fri, 9 Jan 2026 18:09:20 +0530 Subject: [PATCH 12/12] review comment fixed Signed-off-by: sachin.sp --- .../api_services/MasterDataSyncApi.java | 3 ++- .../io/mosip/registration_client/utils/BatchJob.java | 3 ++- .../registration/clientmanager/dao/LocalConfigDAO.java | 5 +++-- .../clientmanager/dao/LocalConfigDAOImpl.java | 7 ++++--- .../clientmanager/service/JobManagerServiceImpl.java | 3 ++- .../clientmanager/service/LocalConfigServiceImpl.java | 4 ++-- .../clientmanager/spi/LocalConfigService.java | 9 +++++---- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java b/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java index 4954c1bd6..551c86628 100644 --- a/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java +++ b/android/app/src/main/java/io/mosip/registration_client/api_services/MasterDataSyncApi.java @@ -491,7 +491,8 @@ public void modifyJobCronExpression(@NonNull String jobId, @NonNull String cronE @Override public void getValue(@NonNull String name, @NonNull MasterDataSyncPigeon.Result result) { try { - String value = localConfigService.getValue(name); + // getValue is used for retrieving job cron expressions, so use PERMITTED_JOB_TYPE + String value = localConfigService.getValue(name, RegistrationConstants.PERMITTED_JOB_TYPE); result.success(value); } catch (Exception e) { Log.e(TAG, "Failed to get value for: " + name, e); diff --git a/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java b/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java index 88db2bc3a..3c60a4c58 100644 --- a/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java +++ b/android/app/src/main/java/io/mosip/registration_client/utils/BatchJob.java @@ -16,6 +16,7 @@ import io.mosip.registration.clientmanager.constant.Components; import io.mosip.registration.clientmanager.constant.PacketClientStatus; import io.mosip.registration.clientmanager.constant.PacketTaskStatus; +import io.mosip.registration.clientmanager.constant.RegistrationConstants; import io.mosip.registration.clientmanager.dao.GlobalParamDao; import io.mosip.registration.clientmanager.entity.GlobalParam; import io.mosip.registration.clientmanager.entity.Registration; @@ -215,7 +216,7 @@ public long getIntervalMillis(String api) { // Check for custom cron expression and override if available if (localConfigService != null) { - String customCron = localConfigService.getValue(syncJob.getId()); + String customCron = localConfigService.getValue(syncJob.getId(), RegistrationConstants.PERMITTED_JOB_TYPE); if (customCron != null && !customCron.trim().isEmpty()) { cronExp = customCron; // Use custom cron expression Log.d(getClass().getSimpleName(), api + " Custom Cron Expression : " + cronExp); diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAO.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAO.java index 17cf60a3a..5f933d945 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAO.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAO.java @@ -27,11 +27,12 @@ public interface LocalConfigDAO { void modifyConfigurations(Map localPreferences); /** - * Get value for a specific local preference by name + * Get value for a specific local preference by name and config type * @param name Preference name + * @param configType Configuration type (PERMITTED_JOB_TYPE or PERMITTED_CONFIG_TYPE) * @return Preference value or null if not found */ - String getValue(String name); + String getValue(String name, String configType); /** * Modify job cron expression diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java index c1534a453..80de8ac93 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/dao/LocalConfigDAOImpl.java @@ -65,14 +65,15 @@ public void modifyConfigurations(Map localPreferences) { } @Override - public String getValue(String name) { + public String getValue(String name, String configType) { try { - LocalPreferences localPreference = localPreferencesRepository.findByIsDeletedFalseAndName(name); + LocalPreferences localPreference = localPreferencesRepository + .findByIsDeletedFalseAndNameAndConfigType(name, configType); if (localPreference != null && localPreference.getVal() != null) { return localPreference.getVal(); } } catch (Exception e) { - Log.e(TAG, "Error getting value for: " + name, e); + Log.e(TAG, "Error getting value for: " + name + ", configType: " + configType, e); } return null; } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java index dcdc9a932..8e0d6f059 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/JobManagerServiceImpl.java @@ -17,6 +17,7 @@ import java.util.concurrent.TimeUnit; import io.mosip.registration.clientmanager.R; +import io.mosip.registration.clientmanager.constant.RegistrationConstants; import io.mosip.registration.clientmanager.entity.SyncJobDef; import io.mosip.registration.clientmanager.jobs.ConfigDataSyncJob; import io.mosip.registration.clientmanager.jobs.DeleteAuditLogsJob; @@ -197,7 +198,7 @@ public String getNextSyncTime(int jobId) { */ private String getSyncFrequency(SyncJobDef syncJob) { if (localConfigService != null) { - String localPreference = localConfigService.getValue(syncJob.getId()); + String localPreference = localConfigService.getValue(syncJob.getId(), RegistrationConstants.PERMITTED_JOB_TYPE); if (localPreference != null && !localPreference.trim().isEmpty()) { return localPreference; } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java index b7a4bedb0..ba2a8b710 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/LocalConfigServiceImpl.java @@ -40,8 +40,8 @@ public List getPermittedConfiguration() { } @Override - public String getValue(String name) { - return localConfigDAO.getValue(name); + public String getValue(String name, String configType) { + return localConfigDAO.getValue(name, configType); } @Override diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/spi/LocalConfigService.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/spi/LocalConfigService.java index ceaf39b06..6c5ae4196 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/spi/LocalConfigService.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/spi/LocalConfigService.java @@ -24,11 +24,12 @@ public interface LocalConfigService { List getPermittedConfiguration(); /** - * Get value for a specific local configuration by name - * @param name Configuration name - * @return Configuration value or null if not found + * Get value for a specific local preference by name and config type + * @param name Preference name + * @param configType Configuration type (PERMITTED_JOB_TYPE or PERMITTED_CONFIG_TYPE) + * @return Preference value or null if not found */ - String getValue(String name); + String getValue(String name, String configType); /** * Modify job cron expression