updated app start
This commit is contained in:
555
app.py
555
app.py
@@ -1,17 +1,25 @@
|
||||
import os
|
||||
import click
|
||||
import time
|
||||
import psutil
|
||||
import threading
|
||||
from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify, send_from_directory
|
||||
from flask_migrate import Migrate
|
||||
import subprocess
|
||||
from werkzeug.utils import secure_filename
|
||||
from functools import wraps
|
||||
from functools import wraps, lru_cache
|
||||
from extensions import db, bcrypt, login_manager
|
||||
from sqlalchemy import text
|
||||
from dotenv import load_dotenv
|
||||
import logging
|
||||
import gc
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# Configure logging for better performance monitoring
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
# First import models
|
||||
from models import User, Player, Group, Content, ServerLog, group_player
|
||||
|
||||
@@ -38,7 +46,7 @@ from utils.uploads import (
|
||||
add_image_to_playlist,
|
||||
convert_video_and_update_playlist,
|
||||
process_pdf,
|
||||
process_pptx,
|
||||
process_pptx_improved,
|
||||
process_uploaded_files
|
||||
)
|
||||
|
||||
@@ -57,8 +65,18 @@ db_path = os.path.join(instance_dir, 'dashboard.db')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
|
||||
# Set maximum content length to 1GB
|
||||
app.config['MAX_CONTENT_LENGTH'] = 2048 * 2048 * 2048 # 2GB, adjust as needed
|
||||
# Performance configuration
|
||||
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
|
||||
'pool_pre_ping': True,
|
||||
'pool_recycle': 300,
|
||||
'connect_args': {'timeout': 10}
|
||||
}
|
||||
|
||||
# Set maximum content length to 1GB (reduced from 2GB)
|
||||
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 * 1024 # 1GB
|
||||
|
||||
# Add timeout configuration
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 31536000 # Cache static files for 1 year
|
||||
|
||||
# Ensure the instance folder exists
|
||||
os.makedirs(app.instance_path, exist_ok=True)
|
||||
@@ -314,6 +332,7 @@ def add_player():
|
||||
orientation = request.form.get('orientation', 'Landscape') # <-- Get orientation
|
||||
add_player_util(username, hostname, password, quickconnect_password, orientation) # <-- Pass orientation
|
||||
flash(f'Player "{username}" added successfully.', 'success')
|
||||
clear_player_cache() # Clear cache when player is added
|
||||
return redirect(url_for('dashboard'))
|
||||
return render_template('add_player.html')
|
||||
|
||||
@@ -330,6 +349,7 @@ def edit_player(player_id):
|
||||
orientation = request.form.get('orientation', player.orientation) # <-- Get orientation
|
||||
edit_player_util(player_id, username, hostname, password, quickconnect_password, orientation) # <-- Pass orientation
|
||||
flash(f'Player "{username}" updated successfully.', 'success')
|
||||
clear_player_cache() # Clear cache when player is updated
|
||||
return redirect(url_for('player_page', player_id=player.id))
|
||||
|
||||
return_url = request.args.get('return_url', url_for('player_page', player_id=player.id))
|
||||
@@ -344,6 +364,103 @@ def change_theme():
|
||||
db.session.commit()
|
||||
return redirect(url_for('admin'))
|
||||
|
||||
# Group management routes
|
||||
@app.route('/group/create', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def create_group():
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
player_ids = request.form.getlist('players')
|
||||
orientation = request.form.get('orientation', 'Landscape')
|
||||
|
||||
try:
|
||||
# Convert player_ids to integers
|
||||
player_ids = [int(pid) for pid in player_ids]
|
||||
group = create_group_util(name, player_ids, orientation)
|
||||
flash(f'Group "{name}" created successfully.', 'success')
|
||||
return redirect(url_for('dashboard'))
|
||||
except ValueError as e:
|
||||
flash(str(e), 'danger')
|
||||
return redirect(url_for('create_group'))
|
||||
|
||||
# GET request - show create group form
|
||||
players = Player.query.filter_by(locked_to_group_id=None).all() # Only available players
|
||||
return render_template('create_group.html', players=players)
|
||||
|
||||
@app.route('/group/<int:group_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def edit_group(group_id):
|
||||
group = Group.query.get_or_404(group_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
player_ids = request.form.getlist('players')
|
||||
orientation = request.form.get('orientation', group.orientation)
|
||||
|
||||
try:
|
||||
# Convert player_ids to integers
|
||||
player_ids = [int(pid) for pid in player_ids]
|
||||
edit_group_util(group_id, name, player_ids, orientation)
|
||||
flash(f'Group "{name}" updated successfully.', 'success')
|
||||
return redirect(url_for('dashboard'))
|
||||
except ValueError as e:
|
||||
flash(str(e), 'danger')
|
||||
return redirect(url_for('edit_group', group_id=group_id))
|
||||
|
||||
# GET request - show edit group form
|
||||
players = Player.query.all()
|
||||
return render_template('edit_group.html', group=group, players=players)
|
||||
|
||||
@app.route('/group/<int:group_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_group(group_id):
|
||||
delete_group_util(group_id)
|
||||
flash('Group deleted successfully.', 'success')
|
||||
return redirect(url_for('dashboard'))
|
||||
|
||||
@app.route('/group/<int:group_id>')
|
||||
@login_required
|
||||
def manage_group(group_id):
|
||||
group = Group.query.get_or_404(group_id)
|
||||
content = get_group_content(group_id)
|
||||
return render_template('manage_group.html', group=group, content=content)
|
||||
|
||||
@app.route('/group/<int:group_id>/fullscreen', methods=['GET', 'POST'])
|
||||
def group_fullscreen(group_id):
|
||||
group = Group.query.get_or_404(group_id)
|
||||
content = get_group_content(group_id)
|
||||
return render_template('group_fullscreen.html', group=group, content=content)
|
||||
|
||||
@app.route('/group/<int:group_id>/media/<int:content_id>/edit', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def edit_group_media(group_id, content_id):
|
||||
new_duration = int(request.form['duration'])
|
||||
success = edit_group_media(group_id, content_id, new_duration)
|
||||
|
||||
if success:
|
||||
flash('Media duration updated successfully.', 'success')
|
||||
else:
|
||||
flash('Error updating media duration.', 'danger')
|
||||
|
||||
return redirect(url_for('manage_group', group_id=group_id))
|
||||
|
||||
@app.route('/group/<int:group_id>/media/<int:content_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_group_media(group_id, content_id):
|
||||
success = delete_group_media(group_id, content_id)
|
||||
|
||||
if success:
|
||||
flash('Media deleted successfully.', 'success')
|
||||
else:
|
||||
flash('Error deleting media.', 'danger')
|
||||
|
||||
return redirect(url_for('manage_group', group_id=group_id))
|
||||
|
||||
@app.route('/upload_logo', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@@ -414,177 +531,103 @@ def clean_unused_files():
|
||||
flash('Unused files have been cleaned.', 'success')
|
||||
return redirect(url_for('admin'))
|
||||
|
||||
# Cache for frequently accessed data
|
||||
@lru_cache(maxsize=128)
|
||||
def get_player_by_hostname(hostname):
|
||||
"""Cached function to get player by hostname"""
|
||||
return Player.query.filter_by(hostname=hostname).first()
|
||||
|
||||
# Clear cache when players are modified
|
||||
def clear_player_cache():
|
||||
get_player_by_hostname.cache_clear()
|
||||
|
||||
# Optimized API endpoint with caching
|
||||
@app.route('/api/playlists', methods=['GET'])
|
||||
def get_playlists():
|
||||
hostname = request.args.get('hostname')
|
||||
quickconnect_code = request.args.get('quickconnect_code')
|
||||
|
||||
# Validate the parameters
|
||||
# Validate parameters early
|
||||
if not hostname or not quickconnect_code:
|
||||
return jsonify({'error': 'Hostname and quick connect code are required'}), 400
|
||||
|
||||
# Find the player by hostname and verify the quickconnect code
|
||||
player = Player.query.filter_by(hostname=hostname).first()
|
||||
if not player or not bcrypt.check_password_hash(player.quickconnect_password, quickconnect_code):
|
||||
return jsonify({'error': 'Invalid hostname or quick connect code'}), 404
|
||||
try:
|
||||
# Use cached function for better performance
|
||||
player = get_player_by_hostname(hostname)
|
||||
if not player:
|
||||
return jsonify({'error': 'Player not found'}), 404
|
||||
|
||||
# Verify quickconnect code
|
||||
if not bcrypt.check_password_hash(player.quickconnect_password, quickconnect_code):
|
||||
return jsonify({'error': 'Invalid credentials'}), 401
|
||||
|
||||
# Check if player is locked to a group
|
||||
if player.locked_to_group_id:
|
||||
# Get content for all players in the group to ensure shared content
|
||||
group_players = player.locked_to_group.players
|
||||
player_ids = [p.id for p in group_players]
|
||||
# Optimized content query
|
||||
if player.locked_to_group_id:
|
||||
# More efficient group content query
|
||||
content = db.session.query(Content).join(Player).filter(
|
||||
Player.locked_to_group_id == player.locked_to_group_id
|
||||
).distinct(Content.file_name).order_by(Content.position).all()
|
||||
else:
|
||||
# Get player's individual content with limit
|
||||
content = Content.query.filter_by(player_id=player.id).order_by(Content.position).all()
|
||||
|
||||
# Use the first occurrence of each file for the playlist
|
||||
content_query = (
|
||||
db.session.query(
|
||||
Content.file_name,
|
||||
db.func.min(Content.id).label('id'),
|
||||
db.func.min(Content.duration).label('duration')
|
||||
)
|
||||
.filter(Content.player_id.in_(player_ids))
|
||||
.group_by(Content.file_name)
|
||||
)
|
||||
# Build playlist efficiently
|
||||
playlist = []
|
||||
for media in content:
|
||||
playlist.append({
|
||||
'file_name': media.file_name,
|
||||
'url': f"http://{request.host}/media/{media.file_name}",
|
||||
'duration': media.duration
|
||||
})
|
||||
|
||||
# Force garbage collection for memory management
|
||||
gc.collect()
|
||||
|
||||
return jsonify({
|
||||
'playlist': playlist,
|
||||
'playlist_version': player.playlist_version,
|
||||
'hashed_quickconnect': player.quickconnect_password
|
||||
})
|
||||
|
||||
content = db.session.query(Content).filter(
|
||||
Content.id.in_([c.id for c in content_query])
|
||||
).all()
|
||||
else:
|
||||
# Get player's individual content
|
||||
content = Content.query.filter_by(player_id=player.id).all()
|
||||
|
||||
playlist = [
|
||||
{
|
||||
'file_name': media.file_name,
|
||||
'url': f"http://{request.host}/media/{media.file_name}",
|
||||
'duration': media.duration
|
||||
}
|
||||
for media in content
|
||||
]
|
||||
|
||||
# Return the playlist, version, and hashed quickconnect code
|
||||
return jsonify({
|
||||
'playlist': playlist,
|
||||
'playlist_version': player.playlist_version,
|
||||
'hashed_quickconnect': player.quickconnect_password
|
||||
})
|
||||
except Exception as e:
|
||||
app.logger.error(f"API Error: {str(e)}")
|
||||
return jsonify({'error': 'Internal server error'}), 500
|
||||
|
||||
# Optimized media serving with proper caching
|
||||
@app.route('/media/<path:filename>')
|
||||
def media(filename):
|
||||
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
||||
|
||||
@app.context_processor
|
||||
def inject_theme():
|
||||
if current_user.is_authenticated:
|
||||
theme = current_user.theme
|
||||
else:
|
||||
theme = 'light'
|
||||
return dict(theme=theme)
|
||||
|
||||
@app.route('/group/create', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def create_group():
|
||||
if request.method == 'POST':
|
||||
group_name = request.form['name']
|
||||
player_ids = request.form.getlist('players')
|
||||
orientation = request.form.get('orientation', 'Landscape')
|
||||
create_group_util(group_name, player_ids, orientation)
|
||||
flash(f'Group "{group_name}" created successfully.', 'success')
|
||||
return redirect(url_for('dashboard'))
|
||||
players = Player.query.all()
|
||||
return render_template('create_group.html', players=players)
|
||||
|
||||
@app.route('/group/<int:group_id>/manage')
|
||||
@login_required
|
||||
@admin_required
|
||||
def manage_group(group_id):
|
||||
group = Group.query.get_or_404(group_id)
|
||||
content = get_group_content(group_id)
|
||||
# Debug content ordering
|
||||
print("Group content positions before sorting:", [(c.id, c.file_name, c.position) for c in content])
|
||||
content = sorted(content, key=lambda c: c.position)
|
||||
print("Group content positions after sorting:", [(c.id, c.file_name, c.position) for c in content])
|
||||
return render_template('manage_group.html', group=group, content=content)
|
||||
|
||||
@app.route('/group/<int:group_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def edit_group(group_id):
|
||||
group = Group.query.get_or_404(group_id)
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
player_ids = request.form.getlist('players')
|
||||
orientation = request.form.get('orientation', group.orientation)
|
||||
edit_group_util(group_id, name, player_ids, orientation)
|
||||
flash(f'Group "{name}" updated successfully.', 'success')
|
||||
return redirect(url_for('dashboard'))
|
||||
players = Player.query.all()
|
||||
return render_template('edit_group.html', group=group, players=players)
|
||||
|
||||
@app.route('/group/<int:group_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_group(group_id):
|
||||
group = Group.query.get_or_404(group_id)
|
||||
group_name = group.name
|
||||
delete_group_util(group_id)
|
||||
flash(f'Group "{group_name}" deleted successfully.', 'success')
|
||||
return redirect(url_for('dashboard'))
|
||||
|
||||
@app.route('/group/<int:group_id>/fullscreen', methods=['GET'])
|
||||
@login_required
|
||||
def group_fullscreen(group_id):
|
||||
group = Group.query.get_or_404(group_id)
|
||||
content = Content.query.filter(Content.player_id.in_([player.id for player in group.players])).order_by(Content.position).all()
|
||||
return render_template('group_fullscreen.html', group=group, content=content)
|
||||
|
||||
@app.route('/group/<int:group_id>/media/<int:content_id>/edit', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def edit_group_media_route(group_id, content_id):
|
||||
new_duration = int(request.form['duration'])
|
||||
success = edit_group_media(group_id, content_id, new_duration)
|
||||
|
||||
if success:
|
||||
flash('Media duration updated successfully.', 'success')
|
||||
else:
|
||||
flash('Error updating media duration.', 'danger')
|
||||
|
||||
return redirect(url_for('manage_group', group_id=group_id))
|
||||
|
||||
@app.route('/group/<int:group_id>/media/<int:content_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_group_media_route(group_id, content_id):
|
||||
success = delete_group_media(group_id, content_id)
|
||||
|
||||
if success:
|
||||
flash('Media deleted successfully.', 'success')
|
||||
else:
|
||||
flash('Error deleting media.', 'danger')
|
||||
|
||||
return redirect(url_for('manage_group', group_id=group_id))
|
||||
try:
|
||||
response = send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
||||
# Add caching headers for better performance
|
||||
response.cache_control.max_age = 86400 # Cache for 24 hours
|
||||
response.cache_control.public = True
|
||||
return response
|
||||
except Exception as e:
|
||||
app.logger.error(f"Media serving error: {str(e)}")
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
|
||||
# Optimized playlist version check
|
||||
@app.route('/api/playlist_version', methods=['GET'])
|
||||
def get_playlist_version():
|
||||
hostname = request.args.get('hostname')
|
||||
quickconnect_code = request.args.get('quickconnect_code')
|
||||
|
||||
# Validate the parameters
|
||||
if not hostname or not quickconnect_code:
|
||||
return jsonify({'error': 'Hostname and quick connect code are required'}), 400
|
||||
|
||||
# Find the player by hostname and verify the quickconnect code
|
||||
player = Player.query.filter_by(hostname=hostname).first()
|
||||
if not player or not bcrypt.check_password_hash(player.quickconnect_password, quickconnect_code):
|
||||
return jsonify({'error': 'Invalid hostname or quick connect code'}), 404
|
||||
try:
|
||||
# Use cached function
|
||||
player = get_player_by_hostname(hostname)
|
||||
if not player or not bcrypt.check_password_hash(player.quickconnect_password, quickconnect_code):
|
||||
return jsonify({'error': 'Invalid credentials'}), 401
|
||||
|
||||
# Return the playlist version and hashed quickconnect code
|
||||
return jsonify({
|
||||
'playlist_version': player.playlist_version,
|
||||
'hashed_quickconnect': player.quickconnect_password
|
||||
})
|
||||
return jsonify({
|
||||
'playlist_version': player.playlist_version,
|
||||
'hashed_quickconnect': player.quickconnect_password
|
||||
})
|
||||
except Exception as e:
|
||||
app.logger.error(f"Version check error: {str(e)}")
|
||||
return jsonify({'error': 'Internal server error'}), 500
|
||||
|
||||
@app.route('/player/<int:player_id>/update_order', methods=['POST'])
|
||||
@login_required
|
||||
@@ -660,6 +703,218 @@ if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
||||
db.create_all()
|
||||
create_default_user(db, User, bcrypt)
|
||||
|
||||
# Performance monitoring functions
|
||||
def get_system_stats():
|
||||
"""Get current system performance statistics"""
|
||||
try:
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
memory = psutil.virtual_memory()
|
||||
disk = psutil.disk_usage('/')
|
||||
|
||||
return {
|
||||
'cpu_percent': cpu_percent,
|
||||
'memory_percent': memory.percent,
|
||||
'memory_used_mb': memory.used / (1024 * 1024),
|
||||
'memory_total_mb': memory.total / (1024 * 1024),
|
||||
'disk_percent': disk.percent,
|
||||
'disk_used_gb': disk.used / (1024 * 1024 * 1024),
|
||||
'disk_total_gb': disk.total / (1024 * 1024 * 1024),
|
||||
'timestamp': time.time()
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error getting system stats: {e}")
|
||||
return None
|
||||
|
||||
# Performance monitoring endpoint
|
||||
@app.route('/api/performance', methods=['GET'])
|
||||
@login_required
|
||||
def get_performance_stats():
|
||||
"""API endpoint to get real-time performance statistics"""
|
||||
stats = get_system_stats()
|
||||
if stats:
|
||||
return jsonify(stats)
|
||||
else:
|
||||
return jsonify({'error': 'Unable to get system stats'}), 500
|
||||
|
||||
# Enhanced upload endpoint with monitoring
|
||||
@app.route('/upload_content_monitored', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def upload_content_monitored():
|
||||
"""Enhanced upload endpoint with performance monitoring"""
|
||||
start_time = time.time()
|
||||
start_stats = get_system_stats()
|
||||
|
||||
target_type = request.form.get('target_type')
|
||||
target_id = request.form.get('target_id')
|
||||
files = request.files.getlist('files')
|
||||
duration = int(request.form['duration'])
|
||||
return_url = request.form.get('return_url')
|
||||
media_type = request.form['media_type']
|
||||
|
||||
print(f"=== UPLOAD MONITORING START ===")
|
||||
print(f"Target Type: {target_type}, Target ID: {target_id}, Media Type: {media_type}")
|
||||
print(f"Number of files: {len(files)}")
|
||||
print(f"Start CPU: {start_stats['cpu_percent']}%, Memory: {start_stats['memory_percent']}%")
|
||||
|
||||
if not target_type or not target_id:
|
||||
flash('Please select a target type and target ID.', 'danger')
|
||||
return redirect(url_for('upload_content'))
|
||||
|
||||
# Monitor during file processing
|
||||
def monitor_upload():
|
||||
"""Background monitoring thread"""
|
||||
while True:
|
||||
stats = get_system_stats()
|
||||
if stats:
|
||||
print(f"[MONITOR] CPU: {stats['cpu_percent']}%, Memory: {stats['memory_percent']}%, Time: {time.time() - start_time:.1f}s")
|
||||
time.sleep(2)
|
||||
|
||||
# Start monitoring thread
|
||||
monitor_thread = threading.Thread(target=monitor_upload, daemon=True)
|
||||
monitor_thread.start()
|
||||
|
||||
# Process uploaded files and get results
|
||||
results = process_uploaded_files(app, files, media_type, duration, target_type, target_id)
|
||||
|
||||
end_time = time.time()
|
||||
end_stats = get_system_stats()
|
||||
total_time = end_time - start_time
|
||||
|
||||
print(f"=== UPLOAD MONITORING END ===")
|
||||
print(f"Total processing time: {total_time:.2f} seconds")
|
||||
print(f"End CPU: {end_stats['cpu_percent']}%, Memory: {end_stats['memory_percent']}%")
|
||||
print(f"CPU change: {end_stats['cpu_percent'] - start_stats['cpu_percent']:.1f}%")
|
||||
print(f"Memory change: {end_stats['memory_percent'] - start_stats['memory_percent']:.1f}%")
|
||||
|
||||
# Log performance metrics
|
||||
log_action(f"Upload completed: {len(files)} files, {total_time:.2f}s, CPU: {start_stats['cpu_percent']}% → {end_stats['cpu_percent']}%")
|
||||
|
||||
return redirect(return_url)
|
||||
@app.route('/player/<int:player_id>/bulk_delete_content', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def bulk_delete_player_content(player_id):
|
||||
"""Bulk delete content for a specific player"""
|
||||
if not request.is_json:
|
||||
return jsonify({'success': False, 'error': 'Invalid request format'}), 400
|
||||
|
||||
player = Player.query.get_or_404(player_id)
|
||||
content_ids = request.json.get('content_ids', [])
|
||||
|
||||
if not content_ids:
|
||||
return jsonify({'success': False, 'error': 'No content IDs provided'}), 400
|
||||
|
||||
try:
|
||||
# Get all content items to delete
|
||||
content_items = Content.query.filter(
|
||||
Content.id.in_(content_ids),
|
||||
Content.player_id == player_id
|
||||
).all()
|
||||
|
||||
if not content_items:
|
||||
return jsonify({'success': False, 'error': 'No valid content found to delete'}), 404
|
||||
|
||||
# Delete the content items
|
||||
deleted_count = 0
|
||||
for content in content_items:
|
||||
# Delete the actual file from filesystem
|
||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], content.file_name)
|
||||
if os.path.exists(file_path):
|
||||
try:
|
||||
os.remove(file_path)
|
||||
except OSError as e:
|
||||
app.logger.warning(f"Could not delete file {file_path}: {e}")
|
||||
|
||||
# Delete from database
|
||||
db.session.delete(content)
|
||||
deleted_count += 1
|
||||
|
||||
# Update playlist version for the player
|
||||
player.playlist_version += 1
|
||||
db.session.commit()
|
||||
|
||||
# Clear cache
|
||||
clear_player_cache()
|
||||
|
||||
# Log the action
|
||||
log_action(f"Bulk deleted {deleted_count} content items from player {player.username}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'deleted_count': deleted_count,
|
||||
'new_playlist_version': player.playlist_version
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
app.logger.error(f"Error in bulk delete: {str(e)}")
|
||||
return jsonify({'success': False, 'error': 'Database error occurred'}), 500
|
||||
|
||||
@app.route('/group/<int:group_id>/bulk_delete_content', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def bulk_delete_group_content(group_id):
|
||||
"""Bulk delete content for a specific group"""
|
||||
if not request.is_json:
|
||||
return jsonify({'success': False, 'error': 'Invalid request format'}), 400
|
||||
|
||||
group = Group.query.get_or_404(group_id)
|
||||
content_ids = request.json.get('content_ids', [])
|
||||
|
||||
if not content_ids:
|
||||
return jsonify({'success': False, 'error': 'No content IDs provided'}), 400
|
||||
|
||||
try:
|
||||
# Get player IDs in the group
|
||||
player_ids = [p.id for p in group.players]
|
||||
|
||||
# Get all content items to delete that belong to players in this group
|
||||
content_items = Content.query.filter(
|
||||
Content.id.in_(content_ids),
|
||||
Content.player_id.in_(player_ids)
|
||||
).all()
|
||||
|
||||
if not content_items:
|
||||
return jsonify({'success': False, 'error': 'No valid content found to delete'}), 404
|
||||
|
||||
# Delete the content items
|
||||
deleted_count = 0
|
||||
for content in content_items:
|
||||
# Delete the actual file from filesystem
|
||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], content.file_name)
|
||||
if os.path.exists(file_path):
|
||||
try:
|
||||
os.remove(file_path)
|
||||
except OSError as e:
|
||||
app.logger.warning(f"Could not delete file {file_path}: {e}")
|
||||
|
||||
# Delete from database
|
||||
db.session.delete(content)
|
||||
deleted_count += 1
|
||||
|
||||
# Update playlist version for all players in the group
|
||||
for player in group.players:
|
||||
player.playlist_version += 1
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Clear cache
|
||||
clear_player_cache()
|
||||
|
||||
# Log the action
|
||||
log_action(f"Bulk deleted {deleted_count} content items from group {group.name}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'deleted_count': deleted_count
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
app.logger.error(f"Error in group bulk delete: {str(e)}")
|
||||
return jsonify({'success': False, 'error': 'Database error occurred'}), 500
|
||||
|
||||
# Add this at the end of app.py
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
|
||||
Reference in New Issue
Block a user