Docker and more calendar work
This commit is contained in:
parent
4527504c80
commit
f7a919ebf2
22 changed files with 2036 additions and 79 deletions
287
app/web/routes/calendar.py
Normal file
287
app/web/routes/calendar.py
Normal file
|
@ -0,0 +1,287 @@
|
|||
"""
|
||||
Calendar routes for the Podcastrr application.
|
||||
"""
|
||||
from flask import Blueprint, render_template, request, jsonify, current_app, url_for
|
||||
from app.models.podcast import Podcast, Episode
|
||||
from app.models.settings import Settings
|
||||
from app.models.database import db
|
||||
from datetime import datetime, timedelta
|
||||
import calendar
|
||||
import json
|
||||
|
||||
calendar_bp = Blueprint('calendar', __name__)
|
||||
|
||||
@calendar_bp.route('/')
|
||||
def index():
|
||||
"""
|
||||
Display the calendar view.
|
||||
"""
|
||||
# Get current settings
|
||||
settings = Settings.query.first()
|
||||
|
||||
# If no settings exist, create default settings
|
||||
if not settings:
|
||||
settings = Settings(
|
||||
download_path=current_app.config['DOWNLOAD_PATH'],
|
||||
naming_format="{podcast_title}/{episode_title}",
|
||||
auto_download=False,
|
||||
max_downloads=5,
|
||||
delete_after_days=30,
|
||||
calendar_first_day="Monday",
|
||||
calendar_show_monitored_only=False
|
||||
)
|
||||
db.session.add(settings)
|
||||
db.session.commit()
|
||||
|
||||
# Get view type (month, week, day, agenda)
|
||||
view_type = request.args.get('view', 'month')
|
||||
|
||||
# Get current date or use the one from the query parameters
|
||||
today = datetime.today()
|
||||
year = int(request.args.get('year', today.year))
|
||||
month = int(request.args.get('month', today.month))
|
||||
day = int(request.args.get('day', today.day))
|
||||
|
||||
# Create a date object for the selected date
|
||||
selected_date = datetime(year, month, day)
|
||||
|
||||
# Get the first day of the month
|
||||
first_day = datetime(year, month, 1)
|
||||
|
||||
# Get the last day of the month
|
||||
if month == 12:
|
||||
last_day = datetime(year + 1, 1, 1) - timedelta(days=1)
|
||||
else:
|
||||
last_day = datetime(year, month + 1, 1) - timedelta(days=1)
|
||||
|
||||
# Get all days in the month
|
||||
days_in_month = []
|
||||
current_day = first_day
|
||||
while current_day <= last_day:
|
||||
days_in_month.append(current_day)
|
||||
current_day += timedelta(days=1)
|
||||
|
||||
# For week view, get the start and end of the week
|
||||
if view_type == 'week':
|
||||
# Determine the first day of the week based on settings
|
||||
first_day_of_week = 0 if settings.calendar_first_day == 'Sunday' else 1
|
||||
# Calculate the start of the week
|
||||
start_of_week = selected_date - timedelta(days=(selected_date.weekday() - first_day_of_week) % 7)
|
||||
# Calculate the end of the week
|
||||
end_of_week = start_of_week + timedelta(days=6)
|
||||
elif view_type == 'day':
|
||||
# For day view, just use the selected date
|
||||
start_of_week = selected_date
|
||||
end_of_week = selected_date
|
||||
else:
|
||||
# For month view, use the first and last day of the month
|
||||
start_of_week = first_day
|
||||
end_of_week = last_day
|
||||
|
||||
# Get episodes for the selected view (month, week, or day)
|
||||
query = Episode.query.filter(
|
||||
Episode.published_date >= start_of_week,
|
||||
Episode.published_date <= end_of_week
|
||||
).order_by(Episode.published_date)
|
||||
|
||||
# Apply filter for monitored podcasts only if setting is enabled
|
||||
if settings.calendar_show_monitored_only:
|
||||
query = query.join(Podcast).filter(Podcast.auto_download == True)
|
||||
|
||||
episodes = query.all()
|
||||
|
||||
# Group episodes by day
|
||||
episodes_by_day = {}
|
||||
|
||||
# Determine which days to include based on the view type
|
||||
if view_type == 'week':
|
||||
# For week view, include all days of the week
|
||||
days_to_include = []
|
||||
current_day = start_of_week
|
||||
while current_day <= end_of_week:
|
||||
days_to_include.append(current_day)
|
||||
current_day += timedelta(days=1)
|
||||
elif view_type == 'day':
|
||||
# For day view, just include the selected date
|
||||
days_to_include = [selected_date]
|
||||
else:
|
||||
# For month view, include all days in the month
|
||||
days_to_include = days_in_month
|
||||
|
||||
# Initialize the episodes_by_day dictionary
|
||||
for day in days_to_include:
|
||||
day_str = day.strftime('%Y-%m-%d')
|
||||
episodes_by_day[day_str] = []
|
||||
|
||||
for episode in episodes:
|
||||
day_str = episode.published_date.strftime('%Y-%m-%d')
|
||||
if day_str in episodes_by_day:
|
||||
# Get podcast info
|
||||
podcast = Podcast.query.get(episode.podcast_id)
|
||||
|
||||
# Determine status color
|
||||
status_class = 'status-unmonitored'
|
||||
if podcast and podcast.auto_download:
|
||||
if episode.downloaded:
|
||||
status_class = 'status-downloaded'
|
||||
elif episode.download_error:
|
||||
status_class = 'status-error'
|
||||
else:
|
||||
status_class = 'status-downloading'
|
||||
|
||||
# Format air time
|
||||
air_time = episode.published_date.strftime('%I:%M%p').lower()
|
||||
|
||||
# Calculate end time (using duration if available, otherwise add 30 minutes)
|
||||
if episode.duration:
|
||||
end_time = (episode.published_date + timedelta(seconds=episode.duration)).strftime('%I:%M%p').lower()
|
||||
else:
|
||||
end_time = (episode.published_date + timedelta(minutes=30)).strftime('%I:%M%p').lower()
|
||||
|
||||
# Format episode info
|
||||
episode_info = {
|
||||
'id': episode.id,
|
||||
'podcast_id': episode.podcast_id,
|
||||
'podcast_title': podcast.title if podcast else 'Unknown',
|
||||
'title': episode.title,
|
||||
'season': episode.season,
|
||||
'episode_number': episode.episode_number,
|
||||
'air_time': f"{air_time} - {end_time}",
|
||||
'status_class': status_class,
|
||||
'url': url_for('podcasts.view', podcast_id=episode.podcast_id)
|
||||
}
|
||||
|
||||
episodes_by_day[day_str].append(episode_info)
|
||||
|
||||
return render_template('calendar/index.html',
|
||||
title='Calendar',
|
||||
settings=settings,
|
||||
view_type=view_type,
|
||||
selected_date=selected_date,
|
||||
days_in_month=days_in_month,
|
||||
episodes_by_day=episodes_by_day,
|
||||
start_of_week=start_of_week,
|
||||
end_of_week=end_of_week,
|
||||
days_to_include=days_to_include,
|
||||
today=today,
|
||||
first_day=first_day)
|
||||
|
||||
@calendar_bp.route('/events')
|
||||
def events():
|
||||
"""
|
||||
Get events for the calendar as JSON.
|
||||
"""
|
||||
# Get current settings
|
||||
settings = Settings.query.first()
|
||||
|
||||
# Get date range from query parameters
|
||||
start_date_str = request.args.get('start', '')
|
||||
end_date_str = request.args.get('end', '')
|
||||
|
||||
try:
|
||||
start_date = datetime.fromisoformat(start_date_str.replace('Z', '+00:00'))
|
||||
end_date = datetime.fromisoformat(end_date_str.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
# If dates are invalid, use current month
|
||||
today = datetime.today()
|
||||
start_date = datetime(today.year, today.month, 1)
|
||||
end_date = datetime(today.year, today.month + 1 if today.month < 12 else 1, 1) - timedelta(days=1)
|
||||
|
||||
# Query episodes within the date range
|
||||
query = Episode.query.filter(
|
||||
Episode.published_date >= start_date,
|
||||
Episode.published_date <= end_date
|
||||
)
|
||||
|
||||
# Apply filter for monitored podcasts only if setting is enabled
|
||||
if settings.calendar_show_monitored_only:
|
||||
query = query.join(Podcast).filter(Podcast.auto_download == True)
|
||||
|
||||
episodes = query.all()
|
||||
|
||||
# Convert episodes to calendar events
|
||||
events = []
|
||||
for episode in episodes:
|
||||
# Determine color based on status
|
||||
color = '#999999' # Default gray for unmonitored
|
||||
|
||||
# Check if podcast is monitored
|
||||
podcast = Podcast.query.get(episode.podcast_id)
|
||||
if podcast and podcast.auto_download:
|
||||
if episode.downloaded:
|
||||
color = '#28a745' # Green for downloaded
|
||||
elif episode.download_error:
|
||||
color = '#dc3545' # Red for error/missing
|
||||
else:
|
||||
color = '#6f42c1' # Purple for downloading/pending
|
||||
|
||||
events.append({
|
||||
'id': episode.id,
|
||||
'title': episode.title,
|
||||
'start': episode.published_date.isoformat(),
|
||||
'url': url_for('podcasts.view', podcast_id=episode.podcast_id),
|
||||
'color': color,
|
||||
'description': f"Podcast: {podcast.title if podcast else 'Unknown'}"
|
||||
})
|
||||
|
||||
return jsonify(events)
|
||||
|
||||
@calendar_bp.route('/ical')
|
||||
def ical():
|
||||
"""
|
||||
Generate iCal feed for podcast episodes.
|
||||
"""
|
||||
# Get current settings
|
||||
settings = Settings.query.first()
|
||||
|
||||
# Query episodes (limit to recent and upcoming)
|
||||
start_date = datetime.today() - timedelta(days=30) # Past 30 days
|
||||
end_date = datetime.today() + timedelta(days=30) # Next 30 days
|
||||
|
||||
query = Episode.query.filter(
|
||||
Episode.published_date >= start_date,
|
||||
Episode.published_date <= end_date
|
||||
)
|
||||
|
||||
# Apply filter for monitored podcasts only if setting is enabled
|
||||
if settings.calendar_show_monitored_only:
|
||||
query = query.join(Podcast).filter(Podcast.auto_download == True)
|
||||
|
||||
episodes = query.all()
|
||||
|
||||
# Generate iCal content
|
||||
ical_content = [
|
||||
"BEGIN:VCALENDAR",
|
||||
"VERSION:2.0",
|
||||
"PRODID:-//Podcastrr//EN",
|
||||
"CALSCALE:GREGORIAN",
|
||||
"METHOD:PUBLISH",
|
||||
f"X-WR-CALNAME:Podcastrr Episodes",
|
||||
f"X-WR-CALDESC:Podcast episodes from Podcastrr"
|
||||
]
|
||||
|
||||
for episode in episodes:
|
||||
podcast = Podcast.query.get(episode.podcast_id)
|
||||
pub_date = episode.published_date
|
||||
|
||||
# Format date for iCal
|
||||
dt_stamp = datetime.now().strftime("%Y%m%dT%H%M%SZ")
|
||||
dt_start = pub_date.strftime("%Y%m%dT%H%M%SZ")
|
||||
|
||||
ical_content.extend([
|
||||
"BEGIN:VEVENT",
|
||||
f"UID:{episode.id}@podcastrr",
|
||||
f"DTSTAMP:{dt_stamp}",
|
||||
f"DTSTART:{dt_start}",
|
||||
f"SUMMARY:{episode.title}",
|
||||
f"DESCRIPTION:{podcast.title if podcast else 'Unknown podcast'}: {episode.description[:100] if episode.description else ''}...",
|
||||
"END:VEVENT"
|
||||
])
|
||||
|
||||
ical_content.append("END:VCALENDAR")
|
||||
|
||||
response = "\r\n".join(ical_content)
|
||||
return response, 200, {
|
||||
'Content-Type': 'text/calendar; charset=utf-8',
|
||||
'Content-Disposition': 'attachment; filename=podcastrr.ics'
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue