From 8f064cda34462ade13456b90a768e988fcdf46f4 Mon Sep 17 00:00:00 2001 From: Michael Beck Date: Fri, 13 Jun 2025 11:47:41 +0200 Subject: [PATCH] adds timezone config option --- scipaperloader/blueprints/config.py | 70 ++++++++++++++++++- scipaperloader/models.py | 41 +++++++++++ scipaperloader/scheduler.py | 6 +- .../templates/config/general.html.jinja | 37 ++++++++++ 4 files changed, 151 insertions(+), 3 deletions(-) diff --git a/scipaperloader/blueprints/config.py b/scipaperloader/blueprints/config.py index 8fdf4a8..d477640 100644 --- a/scipaperloader/blueprints/config.py +++ b/scipaperloader/blueprints/config.py @@ -2,7 +2,7 @@ from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify, current_app from ..db import db # Import the new model -from ..models import VolumeConfig, ScheduleConfig, ActivityLog, DownloadPathConfig, PaperMetadata +from ..models import VolumeConfig, ScheduleConfig, ActivityLog, DownloadPathConfig, PaperMetadata, TimezoneConfig from ..defaults import MAX_VOLUME import os # Import os for path validation import sys @@ -129,6 +129,54 @@ def _update_download_path(new_path): return False, f"Error updating download path: {str(e)}", None +def _update_timezone(new_timezone): + """ + Helper function to update timezone configuration. + + Args: + new_timezone (str): The new timezone + + Returns: + tuple: (success, message, timezone_config) + """ + try: + # Basic validation: check if it's a non-empty string + if not new_timezone or not isinstance(new_timezone, str): + return False, "Timezone cannot be empty.", None + + # Validate timezone using pytz + try: + import pytz + pytz.timezone(new_timezone) # This will raise an exception if invalid + except ImportError: + # If pytz is not available, do basic validation + if '/' not in new_timezone: + return False, "Invalid timezone format. Use format like 'Europe/Berlin'.", None + except pytz.exceptions.UnknownTimeZoneError: + return False, f"Unknown timezone: {new_timezone}. Use format like 'Europe/Berlin'.", None + + config = TimezoneConfig.query.first() + if not config: + config = TimezoneConfig(timezone=new_timezone) + db.session.add(config) + else: + old_value = config.timezone + config.timezone = new_timezone + ActivityLog.log_config_change( + config_key="scheduler_timezone", + old_value=old_value, + new_value=new_timezone, + description="Updated scheduler timezone" + ) + + db.session.commit() + return True, "Timezone updated successfully!", config + + except Exception as e: + db.session.rollback() + return False, f"Error updating timezone: {str(e)}", None + + def _update_schedule(schedule_data): """ Helper function to update schedule configuration. @@ -211,11 +259,19 @@ def general(): db.session.add(download_path_config) db.session.commit() + # Fetch timezone config + timezone_config = TimezoneConfig.query.first() + if not timezone_config: + timezone_config = TimezoneConfig() # Use default from model + db.session.add(timezone_config) + db.session.commit() + return render_template( "config/index.html.jinja", active_tab="general", volume_config=volume_config, download_path_config=download_path_config, # Pass to template + timezone_config=timezone_config, # Pass to template max_volume=MAX_VOLUME, app_title="Configuration" ) @@ -369,9 +425,10 @@ def generate_test_papers(): @bp.route("/update/general", methods=["POST"]) def update_general(): - """Update general configuration (Volume and Download Path).""" + """Update general configuration (Volume, Download Path, and Timezone).""" volume_success, volume_message = True, "" path_success, path_message = True, "" + timezone_success, timezone_message = True, "" # Update Volume new_volume = request.form.get("total_volume") @@ -391,6 +448,15 @@ def update_general(): else: flash(path_message, "error") + # Update Timezone + new_timezone = request.form.get("timezone") + if new_timezone is not None: + timezone_success, timezone_message, _ = _update_timezone(new_timezone) + if timezone_success: + flash(timezone_message, "success") + else: + flash(timezone_message, "error") + return redirect(url_for("config.general")) diff --git a/scipaperloader/models.py b/scipaperloader/models.py index 3387bd5..c0ed2c5 100644 --- a/scipaperloader/models.py +++ b/scipaperloader/models.py @@ -343,6 +343,41 @@ class ScraperModuleConfig(db.Model): db.session.commit() return config + +class TimezoneConfig(db.Model): + """Model to store the configured timezone for the scheduler.""" + id = db.Column(db.Integer, primary_key=True) + timezone = db.Column(db.String(50), default="Europe/Berlin") + + @classmethod + def get_current_timezone(cls): + """Get the currently configured timezone.""" + config = cls.query.first() + if not config: + config = cls(timezone="Europe/Berlin") + db.session.add(config) + db.session.commit() + return config.timezone + + @classmethod + def set_timezone(cls, timezone_name): + """Set the timezone configuration.""" + config = cls.query.first() + if not config: + config = cls(timezone=timezone_name) + db.session.add(config) + else: + old_value = config.timezone + config.timezone = timezone_name + ActivityLog.log_config_change( + config_key="scheduler_timezone", + old_value=old_value, + new_value=timezone_name, + description="Updated scheduler timezone configuration" + ) + db.session.commit() + return config + def init_schedule_config(): """Initialize ScheduleConfig with default values if empty""" if ScheduleConfig.query.count() == 0: @@ -380,3 +415,9 @@ def init_schedule_config(): default_path = DownloadPathConfig(path="/path/to/dummy/papers") db.session.add(default_path) db.session.commit() + + # Initialize TimezoneConfig if it doesn't exist + if TimezoneConfig.query.count() == 0: + default_timezone = TimezoneConfig(timezone="Europe/Berlin") + db.session.add(default_timezone) + db.session.commit() diff --git a/scipaperloader/scheduler.py b/scipaperloader/scheduler.py index 17995d0..f053f8d 100644 --- a/scipaperloader/scheduler.py +++ b/scipaperloader/scheduler.py @@ -293,12 +293,16 @@ class ScraperScheduler: 'misfire_grace_time': 30 # 30 seconds grace period for missed jobs } + # Get timezone from database configuration + from .models import TimezoneConfig + configured_timezone = TimezoneConfig.get_current_timezone() + # Create the scheduler _scheduler = BackgroundScheduler( jobstores=jobstores, executors=executors, job_defaults=job_defaults, - timezone=None # Use system timezone instead of UTC + timezone=configured_timezone # Use configurable timezone from database ) # Add event listeners diff --git a/scipaperloader/templates/config/general.html.jinja b/scipaperloader/templates/config/general.html.jinja index 6300e03..9ca7f79 100644 --- a/scipaperloader/templates/config/general.html.jinja +++ b/scipaperloader/templates/config/general.html.jinja @@ -38,6 +38,43 @@ +
+
Scheduler Timezone
+

Configure the timezone for the APScheduler to use for job + scheduling.

+
+ + +
Current: {{ timezone_config.timezone }}
+
+
+
System Settings

Configure general system behavior.