@@ -554,7 +554,7 @@ def set_cwa_settings():
554554 boolean_settings = []
555555 string_settings = []
556556 list_settings = []
557- integer_settings = ['ingest_timeout_minutes' , 'auto_send_delay_minutes' ] # Special handling for integer settings
557+ integer_settings = ['ingest_timeout_minutes' , 'auto_send_delay_minutes' , 'duplicate_scan_hour' , 'duplicate_scan_chunk_size' ] # Special handling for integer settings
558558 json_settings = ['metadata_provider_hierarchy' , 'metadata_providers_enabled' , 'duplicate_format_priority' ] # Special handling for JSON settings
559559
560560 for setting in cwa_default_settings :
@@ -567,6 +567,10 @@ def set_cwa_settings():
567567 else :
568568 list_settings .append (setting )
569569
570+ # Ensure cron expression is treated as a string even if default is empty
571+ if 'duplicate_scan_cron' not in string_settings :
572+ string_settings .append ('duplicate_scan_cron' )
573+
570574 for format in ignorable_formats :
571575 string_settings .append (f"ignore_ingest_{ format } " )
572576 string_settings .append (f"ignore_convert_{ format } " )
@@ -631,6 +635,10 @@ def set_cwa_settings():
631635 int_value = max (5 , min (120 , int_value )) # Clamp between 5 and 120 minutes
632636 elif setting == 'auto_send_delay_minutes' :
633637 int_value = max (1 , min (60 , int_value )) # Clamp between 1 and 60 minutes
638+ elif setting == 'duplicate_scan_hour' :
639+ int_value = max (0 , min (23 , int_value ))
640+ elif setting == 'duplicate_scan_chunk_size' :
641+ int_value = max (500 , min (50000 , int_value ))
634642 result [setting ] = int_value
635643 except (ValueError , TypeError ):
636644 # Use current value if conversion fails
@@ -689,6 +697,17 @@ def set_cwa_settings():
689697 else :
690698 result [setting ] = cwa_db .cwa_settings .get (setting , '[]' )
691699
700+ # Validate cron expression if provided
701+ cron_expr = result .get ('duplicate_scan_cron' , '' )
702+ if cron_expr :
703+ try :
704+ from apscheduler .triggers .cron import CronTrigger
705+ CronTrigger .from_crontab (cron_expr )
706+ except Exception :
707+ # Revert to previous value and notify user
708+ result ['duplicate_scan_cron' ] = cwa_db .cwa_settings .get ('duplicate_scan_cron' , '' )
709+ flash (_ ("Invalid cron expression for duplicate scans. Changes were not saved." ), category = "error" )
710+
692711 # DEBUGGING
693712 # with open("/config/post_request" ,"w") as f:
694713 # for key in result.keys():
@@ -713,9 +732,33 @@ def set_cwa_settings():
713732 cwa_db = CWA_DB ()
714733 cwa_settings = cwa_db .get_cwa_settings ()
715734
735+ next_scan_run = get_next_duplicate_scan_run (cwa_settings )
736+
716737 return render_title_template ("cwa_settings.html" , title = _ ("Calibre-Web Automated User Settings" ), page = "cwa-settings" ,
717738 cwa_settings = cwa_settings , ignorable_formats = ignorable_formats , target_formats = target_formats ,
718- automerge_options = automerge_options , autoingest_options = autoingest_options )
739+ automerge_options = automerge_options , autoingest_options = autoingest_options ,
740+ next_duplicate_scan_run = next_scan_run )
741+
742+
743+ def get_next_duplicate_scan_run (settings ):
744+ """Compute next scheduled duplicate scan run time based on settings."""
745+ try :
746+ enabled = bool (settings .get ('duplicate_scan_enabled' , 0 ))
747+ cron_expr = (settings .get ('duplicate_scan_cron' ) or '' ).strip ()
748+
749+ if not enabled :
750+ return None
751+
752+ if not cron_expr :
753+ return None
754+
755+ from apscheduler .triggers .cron import CronTrigger
756+ now = datetime .now ().astimezone ()
757+ trigger = CronTrigger .from_crontab (cron_expr , timezone = now .tzinfo )
758+ next_run = trigger .get_next_fire_time (None , now )
759+ return next_run .isoformat () if next_run else None
760+ except Exception :
761+ return None
719762
720763##————————————————————————————————————————————————————————————————————————————##
721764## ##
0 commit comments