364 lines
12 KiB
Python
364 lines
12 KiB
Python
"""Configuration management blueprint."""
|
|
from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify
|
|
from ..db import db
|
|
# Import the new model
|
|
from ..models import VolumeConfig, ScheduleConfig, ActivityLog, DownloadPathConfig
|
|
from ..defaults import MAX_VOLUME
|
|
import os # Import os for path validation
|
|
|
|
bp = Blueprint("config", __name__, url_prefix="/config")
|
|
|
|
|
|
# Helper functions for configuration updates
|
|
def _update_volume(new_volume):
|
|
"""
|
|
Helper function to update volume configuration.
|
|
|
|
Args:
|
|
new_volume (float): The new volume value
|
|
|
|
Returns:
|
|
tuple: (success, message, volume_config)
|
|
"""
|
|
try:
|
|
new_volume = float(new_volume)
|
|
if new_volume <= 0 or new_volume > MAX_VOLUME:
|
|
return False, f"Volume must be between 1 and {MAX_VOLUME}", None
|
|
|
|
volume_config = VolumeConfig.query.first()
|
|
if not volume_config:
|
|
volume_config = VolumeConfig(volume=new_volume)
|
|
db.session.add(volume_config)
|
|
else:
|
|
old_value = volume_config.volume
|
|
volume_config.volume = new_volume
|
|
ActivityLog.log_config_change(
|
|
config_key="scraper_volume",
|
|
old_value=old_value,
|
|
new_value=new_volume,
|
|
description="Updated scraper volume"
|
|
)
|
|
|
|
db.session.commit()
|
|
return True, "Volume updated successfully!", volume_config
|
|
|
|
except (ValueError, TypeError) as e:
|
|
db.session.rollback()
|
|
return False, f"Error updating volume: {str(e)}", None
|
|
|
|
|
|
# Add helper for download path
|
|
def _update_download_path(new_path):
|
|
"""
|
|
Helper function to update download path configuration.
|
|
|
|
Args:
|
|
new_path (str): The new download path
|
|
|
|
Returns:
|
|
tuple: (success, message, download_path_config)
|
|
"""
|
|
try:
|
|
# Basic validation: check if it's a non-empty string
|
|
if not new_path or not isinstance(new_path, str):
|
|
return False, "Download path cannot be empty.", None
|
|
|
|
# --- Add more validation like checking if path exists or is writable ---
|
|
# Check if the path exists and is a directory
|
|
if not os.path.isdir(new_path):
|
|
# Try to create it if it doesn't exist
|
|
try:
|
|
os.makedirs(new_path, exist_ok=True)
|
|
ActivityLog.log_system_activity(
|
|
action="create_directory",
|
|
status="info",
|
|
description=f"Created download directory: {new_path}"
|
|
)
|
|
except OSError as e:
|
|
ActivityLog.log_system_activity(
|
|
action="create_directory",
|
|
status="error",
|
|
description=f"Failed to create download directory: {new_path}, Error: {str(e)}"
|
|
)
|
|
return False, f"Path '{new_path}' is not a valid directory and could not be created: {e}", None
|
|
|
|
# Check if the path is writable
|
|
if not os.access(new_path, os.W_OK):
|
|
ActivityLog.log_system_activity(
|
|
action="check_directory_permissions",
|
|
status="error",
|
|
description=f"Download path '{new_path}' is not writable."
|
|
)
|
|
return False, f"Path '{new_path}' exists but is not writable by the application.", None
|
|
# --- End of validation ---
|
|
|
|
config = DownloadPathConfig.query.first()
|
|
if not config:
|
|
config = DownloadPathConfig(path=new_path)
|
|
db.session.add(config)
|
|
else:
|
|
old_value = config.path
|
|
config.path = new_path
|
|
ActivityLog.log_config_change(
|
|
config_key="download_path",
|
|
old_value=old_value,
|
|
new_value=new_path,
|
|
description="Updated download path"
|
|
)
|
|
|
|
db.session.commit()
|
|
return True, "Download path updated successfully!", config
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
return False, f"Error updating download path: {str(e)}", None
|
|
|
|
|
|
def _update_schedule(schedule_data):
|
|
"""
|
|
Helper function to update schedule configuration.
|
|
|
|
Args:
|
|
schedule_data (dict): Dictionary with hour:weight pairs
|
|
|
|
Returns:
|
|
tuple: (success, message)
|
|
"""
|
|
try:
|
|
# Validate all entries first
|
|
for hour_str, weight in schedule_data.items():
|
|
try:
|
|
hour = int(hour_str)
|
|
weight = float(weight)
|
|
|
|
if hour < 0 or hour > 23:
|
|
return False, f"Hour value must be between 0 and 23, got {hour}"
|
|
|
|
if weight < 0.1 or weight > 5:
|
|
return False, f"Weight for hour {hour} must be between 0.1 and 5, got {weight}"
|
|
except ValueError:
|
|
return False, f"Invalid data format for hour {hour_str}"
|
|
|
|
# Update schedule after validation
|
|
for hour_str, weight in schedule_data.items():
|
|
hour = int(hour_str)
|
|
weight = float(weight)
|
|
|
|
config = ScheduleConfig.query.get(hour)
|
|
if not config:
|
|
config = ScheduleConfig(hour=hour, weight=weight)
|
|
db.session.add(config)
|
|
else:
|
|
old_value = config.weight
|
|
config.weight = weight
|
|
ActivityLog.log_config_change(
|
|
config_key=f"schedule_hour_{hour}",
|
|
old_value=old_value,
|
|
new_value=weight,
|
|
description=f"Updated schedule weight for hour {hour}"
|
|
)
|
|
|
|
db.session.commit()
|
|
return True, "Schedule updated successfully!"
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
return False, f"Error updating schedule: {str(e)}"
|
|
|
|
|
|
@bp.route("/")
|
|
@bp.route("/general")
|
|
def general():
|
|
"""Show general configuration page."""
|
|
volume_config = VolumeConfig.query.first()
|
|
if not volume_config:
|
|
volume_config = VolumeConfig(volume=100) # Default value
|
|
db.session.add(volume_config)
|
|
db.session.commit()
|
|
|
|
# Fetch download path config
|
|
download_path_config = DownloadPathConfig.query.first()
|
|
if not download_path_config:
|
|
download_path_config = DownloadPathConfig() # Use default from model
|
|
db.session.add(download_path_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
|
|
max_volume=MAX_VOLUME,
|
|
app_title="Configuration"
|
|
)
|
|
|
|
|
|
@bp.route("/schedule")
|
|
def schedule():
|
|
"""Show schedule configuration page."""
|
|
# Ensure we have schedule config for all hours
|
|
existing_hours = {record.hour: record for record in ScheduleConfig.query.all()}
|
|
schedule_config = {}
|
|
|
|
for hour in range(24):
|
|
if hour in existing_hours:
|
|
schedule_config[hour] = existing_hours[hour].weight
|
|
else:
|
|
# Create default schedule entry (weight 1.0)
|
|
new_config = ScheduleConfig(hour=hour, weight=1.0)
|
|
db.session.add(new_config)
|
|
schedule_config[hour] = 1.0
|
|
|
|
if len(existing_hours) < 24:
|
|
db.session.commit()
|
|
|
|
volume_config = VolumeConfig.query.first()
|
|
if not volume_config:
|
|
volume_config = VolumeConfig(volume=100) # Default value
|
|
db.session.add(volume_config)
|
|
db.session.commit()
|
|
|
|
return render_template(
|
|
"config/index.html.jinja",
|
|
active_tab="schedule",
|
|
schedule=schedule_config,
|
|
volume=volume_config.volume,
|
|
max_volume=MAX_VOLUME,
|
|
app_title="Configuration"
|
|
)
|
|
|
|
|
|
# Remove old update_volume route
|
|
# @bp.route("/update/volume", methods=["POST"])
|
|
# def update_volume(): ...
|
|
|
|
# Add new route to handle general settings form
|
|
@bp.route("/update/general", methods=["POST"])
|
|
def update_general():
|
|
"""Update general configuration (Volume and Download Path)."""
|
|
volume_success, volume_message = True, ""
|
|
path_success, path_message = True, ""
|
|
|
|
# Update Volume
|
|
new_volume = request.form.get("total_volume")
|
|
if new_volume is not None:
|
|
volume_success, volume_message, _ = _update_volume(new_volume)
|
|
if volume_success:
|
|
flash(volume_message, "success")
|
|
else:
|
|
flash(volume_message, "error")
|
|
|
|
# Update Download Path
|
|
new_path = request.form.get("download_path")
|
|
if new_path is not None:
|
|
path_success, path_message, _ = _update_download_path(new_path)
|
|
if path_success:
|
|
flash(path_message, "success")
|
|
else:
|
|
flash(path_message, "error")
|
|
|
|
return redirect(url_for("config.general"))
|
|
|
|
|
|
@bp.route("/update/schedule", methods=["POST"])
|
|
def update_schedule():
|
|
"""Update schedule configuration."""
|
|
schedule_data = {}
|
|
for hour in range(24):
|
|
key = f"hour_{hour}"
|
|
if key not in request.form:
|
|
flash(f"Missing data for hour {hour}", "error")
|
|
return redirect(url_for("config.schedule"))
|
|
schedule_data[str(hour)] = request.form.get(key, 0)
|
|
|
|
success, message = _update_schedule(schedule_data)
|
|
|
|
if success:
|
|
flash(message, "success")
|
|
else:
|
|
flash(message, "error")
|
|
|
|
return redirect(url_for("config.schedule"))
|
|
|
|
|
|
@bp.route("/api/schedule/stats")
|
|
def schedule_stats():
|
|
"""Get statistics about the current schedule configuration."""
|
|
volume_config = VolumeConfig.query.first()
|
|
if not volume_config:
|
|
return jsonify({"error": "No volume configuration found"})
|
|
|
|
total_volume = volume_config.volume
|
|
schedule_configs = ScheduleConfig.query.all()
|
|
|
|
if not schedule_configs:
|
|
return jsonify({"error": "No schedule configuration found"})
|
|
|
|
# Calculate total weight
|
|
total_weight = sum(config.weight for config in schedule_configs)
|
|
|
|
# Calculate papers per hour
|
|
papers_per_hour = {}
|
|
hourly_weights = {}
|
|
for config in schedule_configs:
|
|
weight_ratio = config.weight / total_weight if total_weight > 0 else 0
|
|
papers = weight_ratio * total_volume
|
|
papers_per_hour[config.hour] = papers
|
|
hourly_weights[config.hour] = config.weight
|
|
|
|
return jsonify({
|
|
"total_volume": total_volume,
|
|
"total_weight": total_weight,
|
|
"papers_per_hour": papers_per_hour,
|
|
"hourly_weights": hourly_weights
|
|
})
|
|
|
|
|
|
@bp.route("/api/update_config", methods=["POST"])
|
|
def api_update_config():
|
|
"""API endpoint to update configuration."""
|
|
data = request.json
|
|
response = {"success": True, "updates": []}
|
|
|
|
try:
|
|
# Update volume if provided
|
|
if "volume" in data:
|
|
success, message, _ = _update_volume(data["volume"])
|
|
response["updates"].append({
|
|
"type": "volume",
|
|
"success": success,
|
|
"message": message
|
|
})
|
|
if not success:
|
|
response["success"] = False
|
|
|
|
# Update download path if provided
|
|
if "download_path" in data:
|
|
success, message, _ = _update_download_path(data["download_path"])
|
|
response["updates"].append({
|
|
"type": "download_path",
|
|
"success": success,
|
|
"message": message
|
|
})
|
|
if not success:
|
|
response["success"] = False
|
|
|
|
# Update schedule if provided
|
|
if "schedule" in data:
|
|
success, message = _update_schedule(data["schedule"])
|
|
response["updates"].append({
|
|
"type": "schedule",
|
|
"success": success,
|
|
"message": message
|
|
})
|
|
if not success:
|
|
response["success"] = False
|
|
|
|
return jsonify(response)
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
return jsonify({
|
|
"success": False,
|
|
"message": f"Unexpected error: {str(e)}"
|
|
}) |