@@ -202,6 +202,44 @@ def add_repository():
202202 schedule_type = request .form ['schedule_type' ]
203203 retention_count = int (request .form ['retention_count' ])
204204
205+ # Handle custom schedule fields
206+ custom_interval = None
207+ custom_unit = None
208+ custom_hour = 2
209+ custom_minute = 0
210+
211+ if schedule_type == 'custom' :
212+ custom_interval = int (request .form .get ('custom_interval' , 1 ))
213+ custom_unit = request .form .get ('custom_unit' , 'days' )
214+ custom_time = request .form .get ('custom_time' , '02:00' )
215+
216+ # Validate custom schedule parameters
217+ if custom_unit == 'days' and (custom_interval < 1 or custom_interval > 365 ):
218+ flash ('Custom interval for days must be between 1 and 365' , 'error' )
219+ return render_template ('add_repository.html' )
220+ elif custom_unit == 'weeks' and (custom_interval < 1 or custom_interval > 52 ):
221+ flash ('Custom interval for weeks must be between 1 and 52' , 'error' )
222+ return render_template ('add_repository.html' )
223+ elif custom_unit == 'months' and (custom_interval < 1 or custom_interval > 12 ):
224+ flash ('Custom interval for months must be between 1 and 12' , 'error' )
225+ return render_template ('add_repository.html' )
226+
227+ try :
228+ time_parts = custom_time .split (':' )
229+ custom_hour = int (time_parts [0 ])
230+ custom_minute = int (time_parts [1 ])
231+
232+ if custom_hour < 0 or custom_hour > 23 :
233+ flash ('Hour must be between 0 and 23' , 'error' )
234+ return render_template ('add_repository.html' )
235+ if custom_minute < 0 or custom_minute > 59 :
236+ flash ('Minute must be between 0 and 59' , 'error' )
237+ return render_template ('add_repository.html' )
238+
239+ except (IndexError , ValueError ):
240+ flash ('Invalid time format. Please use HH:MM format' , 'error' )
241+ return render_template ('add_repository.html' )
242+
205243 # Extract repo name from URL
206244 repo_name = repo_url .split ('/' )[- 1 ].replace ('.git' , '' )
207245
@@ -213,6 +251,10 @@ def add_repository():
213251 backup_format = backup_format ,
214252 schedule_type = schedule_type ,
215253 retention_count = retention_count ,
254+ custom_interval = custom_interval ,
255+ custom_unit = custom_unit ,
256+ custom_hour = custom_hour ,
257+ custom_minute = custom_minute ,
216258 is_active = True
217259 )
218260
@@ -239,10 +281,56 @@ def edit_repository(repo_id):
239281 repository .retention_count = int (request .form ['retention_count' ])
240282 repository .is_active = 'is_active' in request .form
241283
284+ # Handle custom schedule fields
285+ if repository .schedule_type == 'custom' :
286+ custom_interval = int (request .form .get ('custom_interval' , 1 ))
287+ custom_unit = request .form .get ('custom_unit' , 'days' )
288+ custom_time = request .form .get ('custom_time' , '02:00' )
289+
290+ # Validate custom schedule parameters
291+ if custom_unit == 'days' and (custom_interval < 1 or custom_interval > 365 ):
292+ flash ('Custom interval for days must be between 1 and 365' , 'error' )
293+ return render_template ('edit_repository.html' , repository = repository )
294+ elif custom_unit == 'weeks' and (custom_interval < 1 or custom_interval > 52 ):
295+ flash ('Custom interval for weeks must be between 1 and 52' , 'error' )
296+ return render_template ('edit_repository.html' , repository = repository )
297+ elif custom_unit == 'months' and (custom_interval < 1 or custom_interval > 12 ):
298+ flash ('Custom interval for months must be between 1 and 12' , 'error' )
299+ return render_template ('edit_repository.html' , repository = repository )
300+
301+ repository .custom_interval = custom_interval
302+ repository .custom_unit = custom_unit
303+
304+ try :
305+ time_parts = custom_time .split (':' )
306+ repository .custom_hour = int (time_parts [0 ])
307+ repository .custom_minute = int (time_parts [1 ])
308+
309+ if repository .custom_hour < 0 or repository .custom_hour > 23 :
310+ flash ('Hour must be between 0 and 23' , 'error' )
311+ return render_template ('edit_repository.html' , repository = repository )
312+ if repository .custom_minute < 0 or repository .custom_minute > 59 :
313+ flash ('Minute must be between 0 and 59' , 'error' )
314+ return render_template ('edit_repository.html' , repository = repository )
315+
316+ except (IndexError , ValueError ):
317+ flash ('Invalid time format. Please use HH:MM format' , 'error' )
318+ return render_template ('edit_repository.html' , repository = repository )
319+ else :
320+ # Reset custom fields when not using custom schedule
321+ repository .custom_interval = None
322+ repository .custom_unit = None
323+ repository .custom_hour = 2
324+ repository .custom_minute = 0
325+
242326 db .session .commit ()
243327
244328 # Reschedule the backup job
245- scheduler .remove_job (f'backup_{ repo_id } ' , jobstore = None )
329+ try :
330+ scheduler .remove_job (f'backup_{ repo_id } ' , jobstore = None )
331+ except :
332+ pass
333+
246334 if repository .is_active :
247335 schedule_backup_job (repository )
248336
@@ -315,6 +403,61 @@ def schedule_backup_job(repository):
315403 trigger = CronTrigger (day_of_week = 0 , hour = 2 , minute = 0 ) # Sunday 2 AM
316404 elif repository .schedule_type == 'monthly' :
317405 trigger = CronTrigger (day = 1 , hour = 2 , minute = 0 ) # 1st of month 2 AM
406+ elif repository .schedule_type == 'custom' :
407+ # Handle custom schedule
408+ hour = repository .custom_hour or 2
409+ minute = repository .custom_minute or 0
410+ interval = repository .custom_interval or 1
411+ unit = repository .custom_unit or 'days'
412+
413+ if unit == 'days' :
414+ # For daily intervals, use interval_trigger if more than 1 day
415+ if interval == 1 :
416+ trigger = CronTrigger (hour = hour , minute = minute ) # Daily
417+ else :
418+ # Use interval trigger for multi-day schedules
419+ from apscheduler .triggers .interval import IntervalTrigger
420+ from datetime import datetime , time
421+ # Calculate next run time at the specified hour/minute
422+ now = datetime .now ()
423+ start_date = now .replace (hour = hour , minute = minute , second = 0 , microsecond = 0 )
424+ if start_date <= now :
425+ start_date = start_date .replace (day = start_date .day + 1 )
426+ trigger = IntervalTrigger (days = interval , start_date = start_date )
427+ elif unit == 'weeks' :
428+ # For weekly intervals
429+ if interval == 1 :
430+ trigger = CronTrigger (day_of_week = 0 , hour = hour , minute = minute ) # Every Sunday
431+ else :
432+ from apscheduler .triggers .interval import IntervalTrigger
433+ from datetime import datetime
434+ now = datetime .now ()
435+ start_date = now .replace (hour = hour , minute = minute , second = 0 , microsecond = 0 )
436+ # Find next Sunday
437+ days_until_sunday = (6 - now .weekday ()) % 7
438+ if days_until_sunday == 0 and start_date <= now :
439+ days_until_sunday = 7
440+ start_date = start_date .replace (day = start_date .day + days_until_sunday )
441+ trigger = IntervalTrigger (weeks = interval , start_date = start_date )
442+ elif unit == 'months' :
443+ # For monthly intervals
444+ if interval == 1 :
445+ trigger = CronTrigger (day = 1 , hour = hour , minute = minute ) # 1st of every month
446+ else :
447+ from apscheduler .triggers .interval import IntervalTrigger
448+ from datetime import datetime
449+ now = datetime .now ()
450+ start_date = now .replace (day = 1 , hour = hour , minute = minute , second = 0 , microsecond = 0 )
451+ if start_date <= now :
452+ # Move to next month
453+ if start_date .month == 12 :
454+ start_date = start_date .replace (year = start_date .year + 1 , month = 1 )
455+ else :
456+ start_date = start_date .replace (month = start_date .month + 1 )
457+ # Note: Using weeks approximation for months since APScheduler doesn't have months interval
458+ trigger = IntervalTrigger (weeks = interval * 4 , start_date = start_date )
459+ else :
460+ return # Invalid unit
318461 else :
319462 return # Manual only
320463
0 commit comments