166 lines
5.8 KiB
Python
166 lines
5.8 KiB
Python
from logging.handlers import RotatingFileHandler
|
||
import os
|
||
import time
|
||
from flask_socketio import SocketIO
|
||
|
||
import sys
|
||
from flask import Flask, has_request_context
|
||
from flask_sqlalchemy import SQLAlchemy
|
||
from flask_migrate import Migrate
|
||
from psycopg2 import OperationalError
|
||
import spotipy
|
||
from spotipy.oauth2 import SpotifyClientCredentials
|
||
from celery import Celery
|
||
from celery.schedules import crontab
|
||
from sqlalchemy import create_engine
|
||
from config import Config
|
||
from jellyfin.client import JellyfinClient
|
||
import logging
|
||
from spotdl.utils.config import DEFAULT_CONFIG
|
||
from flask_caching import Cache
|
||
from .version import __version__
|
||
|
||
|
||
def check_db_connection(db_uri, retries=5, delay=5):
|
||
"""
|
||
Check if the database is reachable.
|
||
|
||
Args:
|
||
db_uri (str): The database URI.
|
||
retries (int): Number of retry attempts.
|
||
delay (int): Delay between retries in seconds.
|
||
|
||
Raises:
|
||
SystemExit: If the database is not reachable after retries.
|
||
"""
|
||
for attempt in range(1, retries + 1):
|
||
try:
|
||
engine = create_engine(db_uri)
|
||
connection = engine.connect()
|
||
connection.close()
|
||
app.logger.info("Successfully connected to the database.")
|
||
return
|
||
except OperationalError as e:
|
||
app.logger.error(f"Database connection failed on attempt {attempt}/{retries}: {e}")
|
||
if attempt < retries:
|
||
app.logger.info(f"Retrying in {delay} seconds...")
|
||
time.sleep(delay)
|
||
else:
|
||
app.logger.critical("Could not connect to the database. Exiting application.")
|
||
sys.exit(1)
|
||
|
||
# Celery setup
|
||
def make_celery(app):
|
||
celery = Celery(
|
||
app.import_name,
|
||
backend=app.config['result_backend'],
|
||
broker=app.config['CELERY_BROKER_URL'],
|
||
include=['app.tasks']
|
||
)
|
||
celery.conf.update(app.config)
|
||
# Configure Celery Beat schedule
|
||
celery.conf.beat_schedule = {
|
||
'download-missing-tracks-schedule': {
|
||
'task': 'app.tasks.download_missing_tracks',
|
||
'schedule': crontab(minute='30'),
|
||
},
|
||
'check-playlist-updates-schedule': {
|
||
'task': 'app.tasks.check_for_playlist_updates',
|
||
'schedule': crontab(minute='25'),
|
||
},
|
||
'update_all_playlists_track_status-schedule': {
|
||
'task': 'app.tasks.update_all_playlists_track_status',
|
||
'schedule': crontab(minute='*/5'),
|
||
|
||
},
|
||
'update_jellyfin_id_for_downloaded_tracks-schedule': {
|
||
'task': 'app.tasks.update_jellyfin_id_for_downloaded_tracks',
|
||
'schedule': crontab(minute='*/10'),
|
||
|
||
}
|
||
}
|
||
|
||
celery.conf.timezone = 'UTC'
|
||
return celery
|
||
|
||
# Why this ? Because we are using the same admin login for web, worker and beat we need to distinguish the device_id´s
|
||
device_id = f'JellyPlist_{'_'.join(sys.argv)}'
|
||
|
||
# Initialize Flask app
|
||
app = Flask(__name__, template_folder="../templates", static_folder='../static')
|
||
# log_file = 'app.log'
|
||
# handler = RotatingFileHandler(log_file, maxBytes=100000, backupCount=3)
|
||
# handler.setLevel(logging.DEBUG)
|
||
# handler.setFormatter(log_formatter)
|
||
# stream_handler = logging.StreamHandler(sys.stdout)
|
||
# stream_handler.setLevel(logging.DEBUG)
|
||
# stream_handler.setFormatter(log_formatter)
|
||
|
||
|
||
# # app.logger.addHandler(handler)
|
||
# app.logger.addHandler(stream_handler)
|
||
|
||
|
||
app.config.from_object(Config)
|
||
for handler in app.logger.handlers:
|
||
app.logger.removeHandler(handler)
|
||
|
||
log_level = getattr(logging, app.config['LOG_LEVEL'], logging.INFO) # Default to DEBUG if invalid
|
||
app.logger.setLevel(log_level)
|
||
|
||
FORMAT = "[%(asctime)s][%(filename)18s:%(lineno)4s - %(funcName)20s() ] %(levelname)7s - %(message)s"
|
||
logging.basicConfig(format=FORMAT)
|
||
|
||
Config.validate_env_vars()
|
||
cache = Cache(app)
|
||
|
||
|
||
# Spotify, Jellyfin, and Spotdl setup
|
||
app.logger.info(f"setting up spotipy")
|
||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials(
|
||
client_id=app.config['SPOTIFY_CLIENT_ID'],
|
||
client_secret=app.config['SPOTIFY_CLIENT_SECRET']
|
||
))
|
||
|
||
app.logger.info(f"setting up jellyfin client, BaseUrl = {app.config['JELLYFIN_SERVER_URL']}, timeout = {app.config['JELLYFIN_REQUEST_TIMEOUT']}")
|
||
|
||
jellyfin = JellyfinClient(app.config['JELLYFIN_SERVER_URL'], app.config['JELLYFIN_REQUEST_TIMEOUT'])
|
||
jellyfin_admin_token, jellyfin_admin_id, jellyfin_admin_name, jellyfin_admin_is_admin = jellyfin.login_with_password(
|
||
app.config['JELLYFIN_ADMIN_USER'],
|
||
app.config['JELLYFIN_ADMIN_PASSWORD'], device_id= device_id
|
||
)
|
||
|
||
# SQLAlchemy and Migrate setup
|
||
app.logger.info(f"connecting to db: {app.config['JELLYPLIST_DB_HOST']}:{app.config['JELLYPLIST_DB_PORT']}")
|
||
db_uri = f'postgresql://{app.config["JELLYPLIST_DB_USER"]}:{app.config["JELLYPLIST_DB_PASSWORD"]}@{app.config["JELLYPLIST_DB_HOST"]}:{app.config['JELLYPLIST_DB_PORT']}/jellyplist'
|
||
check_db_connection(db_uri=db_uri,retries=5,delay=2)
|
||
app.config['SQLALCHEMY_DATABASE_URI'] = db_uri
|
||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||
db = SQLAlchemy(app)
|
||
app.logger.info(f"applying db migrations")
|
||
migrate = Migrate(app, db)
|
||
|
||
# Celery Configuration (Updated)
|
||
app.config.update(
|
||
CELERY_BROKER_URL=app.config['REDIS_URL'],
|
||
result_backend=app.config['REDIS_URL']
|
||
)
|
||
|
||
def read_dev_build_file(file_path="/jellyplist/DEV_BUILD"):
|
||
if os.path.exists(file_path):
|
||
with open(file_path, "r") as file:
|
||
content = file.read().strip()
|
||
return f"-{content}"
|
||
else:
|
||
return ''
|
||
app.logger.info(f"initializing celery")
|
||
celery = make_celery(app)
|
||
socketio = SocketIO(app, message_queue=app.config['REDIS_URL'], async_mode='eventlet')
|
||
celery.set_default()
|
||
|
||
app.logger.info(f'Jellyplist {__version__}{read_dev_build_file()} started')
|
||
app.logger.debug(f"Debug logging active")
|
||
from app import routes
|
||
from app import jellyfin_routes, tasks
|
||
if "worker" in sys.argv:
|
||
tasks.release_lock("download_missing_tracks_lock") |