Updates
This commit is contained in:
parent
e86ab53de5
commit
095bf52a2f
29 changed files with 2494 additions and 758 deletions
|
@ -206,10 +206,11 @@ def download_episode_api(episode_id):
|
|||
"""
|
||||
Download an episode.
|
||||
"""
|
||||
# Verify episode exists
|
||||
episode = Episode.query.get_or_404(episode_id)
|
||||
|
||||
try:
|
||||
download_path = download_episode(episode)
|
||||
download_path = download_episode(episode_id)
|
||||
return jsonify({
|
||||
'message': 'Episode downloaded successfully',
|
||||
'path': download_path
|
||||
|
|
98
app/web/routes/debug.py
Normal file
98
app/web/routes/debug.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
"""
|
||||
Debug routes for troubleshooting Podcastrr issues.
|
||||
"""
|
||||
from flask import Blueprint, jsonify, render_template, request
|
||||
from app.models.podcast import Podcast
|
||||
from app.services.podcast_updater import PodcastUpdater
|
||||
import logging
|
||||
|
||||
debug_bp = Blueprint('debug', __name__, url_prefix='/debug')
|
||||
|
||||
|
||||
@debug_bp.route('/test-feed')
|
||||
def test_feed():
|
||||
"""
|
||||
Test RSS feed parsing for debugging.
|
||||
"""
|
||||
feed_url = request.args.get('url')
|
||||
if not feed_url:
|
||||
return jsonify({'error': 'No feed URL provided'}), 400
|
||||
|
||||
try:
|
||||
updater = PodcastUpdater()
|
||||
# Create a temporary podcast object for testing
|
||||
temp_podcast = type('obj', (object,), {
|
||||
'id': 0,
|
||||
'feed_url': feed_url,
|
||||
'title': 'Test Podcast'
|
||||
})
|
||||
|
||||
# Try to fetch episodes
|
||||
logging.info(f"Testing feed URL: {feed_url}")
|
||||
episodes = updater.fetch_episodes(temp_podcast)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'feed_url': feed_url,
|
||||
'episodes_found': len(episodes),
|
||||
'episodes': [
|
||||
{
|
||||
'title': ep.get('title', 'No title'),
|
||||
'description': ep.get('description', 'No description')[:100] + '...' if ep.get(
|
||||
'description') else 'No description',
|
||||
'pub_date': str(ep.get('pub_date', 'No date')),
|
||||
'audio_url': ep.get('audio_url', 'No audio URL')
|
||||
}
|
||||
for ep in episodes[:5] # Show first 5 episodes
|
||||
]
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error testing feed {feed_url}: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'feed_url': feed_url
|
||||
}), 500
|
||||
|
||||
|
||||
@debug_bp.route('/podcast-info/<int:podcast_id>')
|
||||
def podcast_info(podcast_id):
|
||||
"""
|
||||
Get detailed information about a podcast for debugging.
|
||||
"""
|
||||
try:
|
||||
podcast = Podcast.query.get_or_404(podcast_id)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'podcast': {
|
||||
'id': podcast.id,
|
||||
'title': podcast.title,
|
||||
'author': podcast.author,
|
||||
'feed_url': podcast.feed_url,
|
||||
'image_url': podcast.image_url,
|
||||
'description': podcast.description[:200] + '...' if podcast.description else None,
|
||||
'last_updated': str(podcast.last_updated) if podcast.last_updated else None,
|
||||
'episode_count': len(podcast.episodes) if hasattr(podcast, 'episodes') else 0
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error getting podcast info for ID {podcast_id}: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@debug_bp.route('/logs')
|
||||
def view_logs():
|
||||
"""
|
||||
View recent application logs.
|
||||
"""
|
||||
try:
|
||||
# This is a simple log viewer - in production you'd want proper log management
|
||||
return render_template('debug/logs.html')
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
|
@ -13,7 +13,7 @@ def index():
|
|||
"""
|
||||
# Get recent podcasts
|
||||
recent_podcasts = Podcast.query.order_by(Podcast.last_updated.desc()).limit(5).all()
|
||||
|
||||
|
||||
return render_template('index.html',
|
||||
title='Home',
|
||||
recent_podcasts=recent_podcasts)
|
||||
|
@ -32,7 +32,7 @@ def dashboard():
|
|||
"""
|
||||
# Get statistics
|
||||
total_podcasts = Podcast.query.count()
|
||||
|
||||
|
||||
return render_template('dashboard.html',
|
||||
title='Dashboard',
|
||||
total_podcasts=total_podcasts)
|
||||
total_podcasts=total_podcasts)
|
||||
|
|
|
@ -31,7 +31,9 @@ def search():
|
|||
if request.method == 'POST':
|
||||
query = request.form.get('query', '')
|
||||
if query:
|
||||
logger.info(f"Searching for podcasts with query: {query}")
|
||||
results = search_podcasts(query)
|
||||
logger.info(f"Found {len(results)} results")
|
||||
|
||||
return render_template('podcasts/search.html',
|
||||
title='Search Podcasts',
|
||||
|
@ -42,33 +44,61 @@ def add(podcast_id):
|
|||
"""
|
||||
Add a podcast to track.
|
||||
"""
|
||||
logger.info(f"Adding podcast with ID: {podcast_id}")
|
||||
|
||||
# Check if podcast already exists
|
||||
existing = Podcast.query.filter_by(external_id=podcast_id).first()
|
||||
|
||||
if existing:
|
||||
flash('Podcast is already being tracked.', 'info')
|
||||
return redirect(url_for('podcasts.index'))
|
||||
|
||||
# Get podcast details from service
|
||||
podcast_data = search_podcasts(podcast_id=podcast_id)
|
||||
|
||||
if not podcast_data:
|
||||
flash('Failed to get podcast details.', 'error')
|
||||
return redirect(url_for('podcasts.search'))
|
||||
|
||||
logger.info(f"Got podcast data: {podcast_data['title']}")
|
||||
logger.info(f"Feed URL: {podcast_data.get('feed_url', 'No feed URL')}")
|
||||
|
||||
# Create podcast record
|
||||
podcast = Podcast(
|
||||
title=podcast_data['title'],
|
||||
author=podcast_data['author'],
|
||||
description=podcast_data['description'],
|
||||
image_url=podcast_data['image_url'],
|
||||
feed_url=podcast_data['feed_url'],
|
||||
external_id=podcast_id
|
||||
)
|
||||
|
||||
db.session.add(podcast)
|
||||
db.session.commit()
|
||||
|
||||
logger.info(f"Podcast saved with ID: {podcast.id}")
|
||||
|
||||
# Fetch episodes immediately after adding
|
||||
if podcast_data.get('feed_url'):
|
||||
try:
|
||||
from app.services.podcast_updater import update_podcast
|
||||
logger.info(f"Fetching episodes for newly added podcast: {podcast.title}")
|
||||
stats = update_podcast(podcast.id)
|
||||
logger.info(f"Update stats: {stats}")
|
||||
|
||||
if stats and stats.get('new_episodes', 0) > 0:
|
||||
flash(f'Podcast added successfully! Found {stats["new_episodes"]} episodes.', 'success')
|
||||
else:
|
||||
flash('Podcast added successfully! No episodes found yet. The feed might be empty or inaccessible.', 'info')
|
||||
logger.warning(f"No episodes found for podcast: {podcast.title}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching episodes for new podcast: {str(e)}", exc_info=True)
|
||||
flash(f'Podcast added successfully, but failed to fetch episodes: {str(e)}', 'error')
|
||||
else:
|
||||
# Get podcast details from service
|
||||
podcast_data = search_podcasts(podcast_id=podcast_id)
|
||||
flash('Podcast added successfully, but no RSS feed URL available.', 'info')
|
||||
logger.warning(f"No feed URL available for podcast: {podcast.title}")
|
||||
|
||||
if podcast_data:
|
||||
podcast = Podcast(
|
||||
title=podcast_data['title'],
|
||||
author=podcast_data['author'],
|
||||
description=podcast_data['description'],
|
||||
image_url=podcast_data['image_url'],
|
||||
feed_url=podcast_data['feed_url'],
|
||||
external_id=podcast_id
|
||||
)
|
||||
|
||||
db.session.add(podcast)
|
||||
db.session.commit()
|
||||
|
||||
flash('Podcast added successfully!', 'success')
|
||||
else:
|
||||
flash('Failed to add podcast.', 'error')
|
||||
|
||||
return redirect(url_for('podcasts.index'))
|
||||
return redirect(url_for('podcasts.view', podcast_id=podcast.id))
|
||||
|
||||
@podcasts_bp.route('/<int:podcast_id>')
|
||||
def view(podcast_id):
|
||||
|
@ -78,6 +108,8 @@ def view(podcast_id):
|
|||
podcast = Podcast.query.get_or_404(podcast_id)
|
||||
episodes = Episode.query.filter_by(podcast_id=podcast_id).order_by(Episode.published_date.desc()).all()
|
||||
|
||||
logger.info(f"Viewing podcast: {podcast.title} with {len(episodes)} episodes")
|
||||
|
||||
return render_template('podcasts/view.html',
|
||||
title=podcast.title,
|
||||
podcast=podcast,
|
||||
|
@ -89,6 +121,7 @@ def delete(podcast_id):
|
|||
Delete a podcast from tracking.
|
||||
"""
|
||||
podcast = Podcast.query.get_or_404(podcast_id)
|
||||
podcast_title = podcast.title
|
||||
|
||||
# Delete associated episodes
|
||||
Episode.query.filter_by(podcast_id=podcast_id).delete()
|
||||
|
@ -96,45 +129,126 @@ def delete(podcast_id):
|
|||
db.session.delete(podcast)
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Podcast "{podcast.title}" has been deleted.', 'success')
|
||||
logger.info(f"Deleted podcast: {podcast_title}")
|
||||
flash(f'Podcast "{podcast_title}" has been deleted.', 'success')
|
||||
return redirect(url_for('podcasts.index'))
|
||||
|
||||
@podcasts_bp.route('/download/<int:episode_id>')
|
||||
def download(episode_id):
|
||||
"""
|
||||
Download an episode.
|
||||
Download an episode in the background.
|
||||
"""
|
||||
from app.services.task_manager import task_manager
|
||||
|
||||
episode = Episode.query.get_or_404(episode_id)
|
||||
episode_title = episode.title
|
||||
podcast_id = episode.podcast_id
|
||||
|
||||
try:
|
||||
download_path = download_episode(episode)
|
||||
flash(f'Episode downloaded to {download_path}', 'success')
|
||||
except Exception as e:
|
||||
flash(f'Download failed: {str(e)}', 'error')
|
||||
# Create a background task for the download
|
||||
task_id = task_manager.create_task(
|
||||
'download',
|
||||
f"Downloading episode: {episode_title}",
|
||||
download_episode,
|
||||
episode_id
|
||||
)
|
||||
|
||||
return redirect(url_for('podcasts.view', podcast_id=episode.podcast_id))
|
||||
flash(f'Download started in the background. Check the status in the tasks panel.', 'info')
|
||||
return redirect(url_for('podcasts.view', podcast_id=podcast_id))
|
||||
|
||||
@podcasts_bp.route('/update/<int:podcast_id>', methods=['POST'])
|
||||
def update(podcast_id):
|
||||
"""
|
||||
Manually update a podcast to fetch new episodes.
|
||||
Manually update a podcast to fetch new episodes in the background.
|
||||
"""
|
||||
from app.services.task_manager import task_manager
|
||||
from app.services.podcast_updater import update_podcast
|
||||
|
||||
podcast = Podcast.query.get_or_404(podcast_id)
|
||||
|
||||
logger.info(f"Starting background update for podcast: {podcast.title} (ID: {podcast.id})")
|
||||
|
||||
# Create a background task for the update
|
||||
task_id = task_manager.create_task(
|
||||
'update',
|
||||
f"Updating podcast: {podcast.title}",
|
||||
update_podcast,
|
||||
podcast_id
|
||||
)
|
||||
|
||||
flash(f'Update started in the background. Check the status in the tasks panel.', 'info')
|
||||
return redirect(url_for('podcasts.view', podcast_id=podcast_id))
|
||||
|
||||
@podcasts_bp.route('/verify/<int:podcast_id>', methods=['POST'])
|
||||
def verify(podcast_id):
|
||||
"""
|
||||
Verify that downloaded episodes still exist on disk.
|
||||
"""
|
||||
from app.services.task_manager import task_manager
|
||||
from app.services.podcast_downloader import verify_downloaded_episodes
|
||||
|
||||
podcast = Podcast.query.get_or_404(podcast_id)
|
||||
|
||||
# Create a background task for verification
|
||||
task_id = task_manager.create_task(
|
||||
'verify',
|
||||
f"Verifying episodes for podcast: {podcast.title}",
|
||||
verify_downloaded_episodes,
|
||||
podcast_id
|
||||
)
|
||||
|
||||
flash(f'Verification started in the background. Check the status in the tasks panel.', 'info')
|
||||
return redirect(url_for('podcasts.view', podcast_id=podcast_id))
|
||||
|
||||
@podcasts_bp.route('/rename/<int:episode_id>', methods=['POST'])
|
||||
def rename_episode(episode_id):
|
||||
"""
|
||||
Rename a downloaded episode file.
|
||||
"""
|
||||
from app.services.task_manager import task_manager
|
||||
from app.services.podcast_downloader import rename_episode as rename_episode_func
|
||||
|
||||
episode = Episode.query.get_or_404(episode_id)
|
||||
episode_title = episode.title
|
||||
podcast_id = episode.podcast_id
|
||||
|
||||
# Check if episode is downloaded
|
||||
if not episode.downloaded or not episode.file_path:
|
||||
flash('Episode is not downloaded.', 'error')
|
||||
return redirect(url_for('podcasts.view', podcast_id=podcast_id))
|
||||
|
||||
# Create a background task for renaming
|
||||
task_id = task_manager.create_task(
|
||||
'rename',
|
||||
f"Renaming episode: {episode_title}",
|
||||
rename_episode_func,
|
||||
episode_id
|
||||
)
|
||||
|
||||
flash(f'Renaming started in the background. Check the status in the tasks panel.', 'info')
|
||||
return redirect(url_for('podcasts.view', podcast_id=podcast_id))
|
||||
|
||||
@podcasts_bp.route('/update_naming_format/<int:podcast_id>', methods=['POST'])
|
||||
def update_naming_format(podcast_id):
|
||||
"""
|
||||
Update the naming format for a podcast.
|
||||
"""
|
||||
podcast = Podcast.query.get_or_404(podcast_id)
|
||||
|
||||
try:
|
||||
from app.services.podcast_updater import update_podcast
|
||||
# Get the naming format from the form
|
||||
naming_format = request.form.get('naming_format')
|
||||
|
||||
logger.info(f"Manually updating podcast: {podcast.title} (ID: {podcast.id})")
|
||||
stats = update_podcast(podcast_id)
|
||||
# If custom format is selected, use the custom format
|
||||
if naming_format == 'custom':
|
||||
naming_format = request.form.get('custom_format')
|
||||
|
||||
if stats['new_episodes'] > 0:
|
||||
flash(f"Found {stats['new_episodes']} new episodes!", 'success')
|
||||
else:
|
||||
flash("No new episodes found.", 'info')
|
||||
# Update the podcast's naming format
|
||||
podcast.naming_format = naming_format
|
||||
db.session.commit()
|
||||
|
||||
logger.info(f"Manual update completed: {stats}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating podcast: {str(e)}")
|
||||
flash(f"Error updating podcast: {str(e)}", 'error')
|
||||
# Flash a message to the user
|
||||
if naming_format:
|
||||
flash(f'Naming format updated for {podcast.title}.', 'success')
|
||||
else:
|
||||
flash(f'Naming format reset to global settings for {podcast.title}.', 'success')
|
||||
|
||||
return redirect(url_for('podcasts.view', podcast_id=podcast_id))
|
||||
|
|
|
@ -15,7 +15,7 @@ def index():
|
|||
"""
|
||||
# Get current settings
|
||||
settings = Settings.query.first()
|
||||
|
||||
|
||||
# If no settings exist, create default settings
|
||||
if not settings:
|
||||
settings = Settings(
|
||||
|
@ -27,7 +27,7 @@ def index():
|
|||
)
|
||||
db.session.add(settings)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
# Update settings
|
||||
download_path = request.form.get('download_path')
|
||||
|
@ -35,7 +35,7 @@ def index():
|
|||
auto_download = 'auto_download' in request.form
|
||||
max_downloads = int(request.form.get('max_downloads', 5))
|
||||
delete_after_days = int(request.form.get('delete_after_days', 30))
|
||||
|
||||
|
||||
# Validate download path
|
||||
if not os.path.exists(download_path):
|
||||
try:
|
||||
|
@ -45,22 +45,22 @@ def index():
|
|||
return render_template('settings/index.html',
|
||||
title='Settings',
|
||||
settings=settings)
|
||||
|
||||
|
||||
# Update settings
|
||||
settings.download_path = download_path
|
||||
settings.naming_format = naming_format
|
||||
settings.auto_download = auto_download
|
||||
settings.max_downloads = max_downloads
|
||||
settings.delete_after_days = delete_after_days
|
||||
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
||||
# Update application config
|
||||
current_app.config['DOWNLOAD_PATH'] = download_path
|
||||
|
||||
|
||||
flash('Settings updated successfully!', 'success')
|
||||
return redirect(url_for('settings.index'))
|
||||
|
||||
|
||||
return render_template('settings/index.html',
|
||||
title='Settings',
|
||||
settings=settings)
|
||||
|
@ -71,7 +71,7 @@ def naming_preview():
|
|||
Preview the naming format.
|
||||
"""
|
||||
naming_format = request.form.get('naming_format', '')
|
||||
|
||||
|
||||
# Example data for preview
|
||||
example_data = {
|
||||
'podcast_title': 'Example Podcast',
|
||||
|
@ -79,10 +79,10 @@ def naming_preview():
|
|||
'published_date': '2023-01-01',
|
||||
'episode_number': '1'
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
# Format the example data with the naming format
|
||||
preview = naming_format.format(**example_data)
|
||||
return {'preview': preview}
|
||||
except Exception as e:
|
||||
return {'error': str(e)}
|
||||
return {'error': str(e)}
|
||||
|
|
51
app/web/routes/tasks.py
Normal file
51
app/web/routes/tasks.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
"""
|
||||
Task-related routes for the Podcastrr application.
|
||||
"""
|
||||
import logging
|
||||
from flask import Blueprint, jsonify, request, current_app
|
||||
from app.services.task_manager import task_manager
|
||||
|
||||
# Set up logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
tasks_bp = Blueprint('tasks', __name__)
|
||||
|
||||
@tasks_bp.route('/api/tasks', methods=['GET'])
|
||||
def get_tasks():
|
||||
"""
|
||||
Get all tasks or filter by status.
|
||||
"""
|
||||
status = request.args.get('status')
|
||||
tasks = task_manager.get_all_tasks()
|
||||
|
||||
if status:
|
||||
tasks = [task for task in tasks if task.status.value == status]
|
||||
|
||||
return jsonify({
|
||||
'tasks': [task.to_dict() for task in tasks]
|
||||
})
|
||||
|
||||
@tasks_bp.route('/api/tasks/<task_id>', methods=['GET'])
|
||||
def get_task(task_id):
|
||||
"""
|
||||
Get a specific task by ID.
|
||||
"""
|
||||
task = task_manager.get_task(task_id)
|
||||
|
||||
if not task:
|
||||
return jsonify({'error': 'Task not found'}), 404
|
||||
|
||||
return jsonify(task.to_dict())
|
||||
|
||||
@tasks_bp.route('/api/tasks/clean', methods=['POST'])
|
||||
def clean_tasks():
|
||||
"""
|
||||
Clean up old completed or failed tasks.
|
||||
"""
|
||||
max_age = request.json.get('max_age_seconds', 3600) if request.json else 3600
|
||||
count = task_manager.clean_old_tasks(max_age)
|
||||
|
||||
return jsonify({
|
||||
'message': f'Cleaned up {count} old tasks',
|
||||
'count': count
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue