diff --git a/.gitignore b/.gitignore index d76b9fb..011d974 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env env/* .git +db/* diff --git a/Dockerfile b/Dockerfile index 7d9a4b6..f7a6c43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,12 @@ FROM python:3.10-slim ENV LISTEN_ADDRESS="0.0.0.0" \ LISTEN_PORT=8080 +RUN apt-get update && apt-get install -y \ + build-essential \ + libpq-dev \ + gcc \ + && rm -rf /var/lib/apt/lists/* + # Set the working directory in the container WORKDIR /app diff --git a/VERSION b/VERSION index 4e379d2..6e8bf73 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.2 +0.1.0 diff --git a/docker-compose.yml b/docker-compose.yml index 65adb63..fb6b876 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,19 @@ services: networks: - mpv-youtube-queue-server restart: unless-stopped + db: + image: lscr.io/linuxserver/mariadb:latest + container_name: mpv-youtube-queue-db + networks: + - mpv-youtube-queue-server + volumes: + - ./db:/config + environment: + - MYSQL_DATABASE=mpv + - MYSQL_USER=mpvuser + - MYSQL_PASSWORD=SecretPassword + ports: + - 3306:3306 networks: mpv-youtube-queue-server: external: true diff --git a/env.example b/env.example index d09d758..96ce909 100644 --- a/env.example +++ b/env.example @@ -1,12 +1,11 @@ LISTEN_ADDRESS=0.0.0.0 # Lisen on all interfaces LISTEN_PORT=8080 # Internal port number MPV_SOCKET=/tmp/mpvsocket # Path to mpv socket - -# MySQL connection info -MYSQL_HOST=localhost -MYSQL_USER=mpvuser -MYSQL_PASSWORD=SecretPassword -MYSQL_DATABASE=mpv -MYSQL_PORT=3306 - LOGLEVEL=info + +# Options: +# mysql+pymysql://:@[:]/ - works with MySQL and Mariadb +# postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...] +# sqlite:///path +# oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=[&key=value&key=value...]] +DATABASE_URL=mysql+pymysql://user:password@localhost:3306/mpv diff --git a/mpv-youtube-queue-server.service b/mpv-youtube-queue-server.service index 4df11ad..0a6105e 100644 --- a/mpv-youtube-queue-server.service +++ b/mpv-youtube-queue-server.service @@ -10,10 +10,7 @@ Restart=on-failure Environment="MPV_SOCKET=/tmp/mpvsocket" Environment="LISTEN_ADDRESS=0.0.0.0" Environment="LISTEN_PORT=42069" -Environment="MYSQL_HOST=http://localhost" -Environment="MYSQL_USER=mpvuser" -Environment="MYSQL_PASSWORD=SecretPassword" -Environment="MYSQL_PORT=3306" +Environment="DATABASE_URL=mysql+mysqldb://user:password@localhost:3306/mpv" [Install] WantedBy=multi-user.target diff --git a/requirements.txt b/requirements.txt index 3f0fa14..be80582 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,18 @@ blinker==1.8.2 +cffi==1.17.1 click==8.1.7 +cryptography==43.0.1 Flask==3.0.3 +greenlet==3.0.3 gunicorn==23.0.0 itsdangerous==2.2.0 Jinja2==3.1.4 MarkupSafe==2.1.5 -mysql-connector-python==9.0.0 +oracledb==2.4.1 packaging==24.1 +psycopg2-binary==2.9.9 +pycparser==2.22 +PyMySQL==1.1.1 +SQLAlchemy==2.0.34 +typing_extensions==4.12.2 Werkzeug==3.0.4 diff --git a/server.py b/server.py index 0c32f9e..94512db 100755 --- a/server.py +++ b/server.py @@ -3,22 +3,17 @@ import logging import os import socket import time -import urllib.parse -from datetime import date -import mysql.connector from flask import Flask, jsonify, request -from mysql.connector import Error +from sqlalchemy import Column, DateTime, Integer, String, create_engine, exc +from sqlalchemy.orm import declarative_base, sessionmaker +from sqlalchemy.sql import func # Set up basic logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) -# Ensure Flask doesn't duplicate log messages -log = logging.getLogger("werkzeug") -log.setLevel(logging.ERROR) - # Flask app app = Flask(__name__) @@ -29,16 +24,11 @@ app.logger.setLevel(logging.getLogger().level) SOCKET_RETRY_DELAY = 5 # Time in seconds between retries to connect to the socket MAX_RETRIES = 10 # Maximum number of retries to connect to the socket -# Configuration +# Configuration from environment variables +LISTEN_ADDRESS = os.getenv("LISTEN_ADDRESS", "0.0.0.0") +LISTEN_PORT = int(os.getenv("LISTEN_PORT", "8080")) +DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./mpv.db") MPV_SOCKET: str = os.getenv("MPV_SOCKET", "/tmp/mpvsocket") -HOST_NAME: str = os.getenv("HOST_NAME", "0.0.0.0") -PORT_NUMBER: int = int(os.getenv("PORT_NUMBER", "8080")) -# MySQL Configuration -MYSQL_HOST: str = os.getenv("MYSQL_HOST", "localhost") -MYSQL_DATABASE: str = os.getenv("MYSQL_DATABASE", "your_database") -MYSQL_USER: str = os.getenv("MYSQL_USER", "your_username") -MYSQL_PASSWORD: str = os.getenv("MYSQL_PASSWORD", "your_password") -MYSQL_PORT: int = int(os.getenv("MYSQL_PORT", "3306")) LOGLEVEL = os.getenv("LOGLEVEL", "INFO").strip().upper() if LOGLEVEL == "DEBUG": @@ -50,53 +40,29 @@ elif LOGLEVEL == "ERROR": else: logging.getLogger().setLevel(logging.INFO) - -def get_mysql_connection(): - """Get a MySQL database connection.""" - try: - logging.debug( - f"Connection information: {MYSQL_HOST}, {MYSQL_USER}, {MYSQL_PORT}" - ) - connection = mysql.connector.connect( - host=MYSQL_HOST, - user=MYSQL_USER, - password=MYSQL_PASSWORD, - port=MYSQL_PORT, - ) - if connection.is_connected(): - logging.info("Connected to MySQL database") - return connection - except Error as e: - logging.error(f"Error while connecting to MySQL: {e}") - return None +# Set up SQLAlchemy +Base = declarative_base() +engine = create_engine(DATABASE_URL) +Session = sessionmaker(bind=engine) +session = Session() -def ensure_watch_history_table_exists(): - """Ensure the watch_history table exists in the mpv schema, otherwise create it.""" - connection = get_mysql_connection() - if connection: - try: - cursor = connection.cursor() - cursor.execute("CREATE DATABASE IF NOT EXISTS mpv") - cursor.execute( - """ - CREATE TABLE IF NOT EXISTS mpv.watch_history ( - whid INT AUTO_INCREMENT PRIMARY KEY, - video_url VARCHAR(255) NOT NULL, - video_name VARCHAR(255) NOT NULL, - channel_url VARCHAR(255) NOT NULL, - channel_name VARCHAR(255) NOT NULL, - watch_date DATE NOT NULL - ) - """ - ) - connection.commit() - logging.info("Ensured watch_history table exists") - except Error as e: - logging.error(f"Failed to ensure watch_history table exists: {e}") - finally: - cursor.close() - connection.close() +class WatchHistory(Base): + __tablename__ = "watch_history" + + whid = Column(Integer, primary_key=True, autoincrement=True) + video_url = Column(String(255), nullable=False) + video_name = Column(String(255), nullable=False) + channel_url = Column(String(255), nullable=False) + channel_name = Column(String(255), nullable=False) + watch_date = Column(DateTime, nullable=False, server_default=func.now()) + created_by = Column( + String(100), nullable=False, server_default="mpv-youtube-queue-server" + ) + + +# Ensure tables exist +Base.metadata.create_all(engine) def send_to_mpv(command): @@ -129,45 +95,27 @@ def add_video(): video_name: str = data.get("video_name") channel_url: str = data.get("channel_url") channel_name: str = data.get("channel_name") - watch_date: date = date.today().strftime("%Y-%m-%d") - if video_url and video_name and channel_url and channel_name and watch_date: + if video_url and video_name and channel_url and channel_name: logging.debug(f"Received data: {data}") - logging.debug(f"Watch date: {watch_date}") - # Insert the data into the MySQL database - connection = get_mysql_connection() - if connection: - try: - query = """ - INSERT INTO mpv.watch_history (video_url, video_name, channel_url, channel_name, watch_date) - VALUES (%s, %s, %s, %s, %s) - """ - cursor = connection.cursor() - cursor.execute( - query, - ( - video_url, - video_name, - channel_url, - channel_name, - watch_date, - ), - ) - connection.commit() - logging.info("Data inserted into MySQL database") - return ( - jsonify(message="Data added to mpv queue and database"), - 200, - ) - except Error as e: - logging.error(f"Failed to insert data into MySQL database: {e}") - return jsonify(message="Failed to add data to database"), 500 - finally: - cursor.close() - connection.close() - else: - return jsonify(message="Failed to connect to MySQL database"), 500 + try: + new_entry = WatchHistory( + video_url=video_url, + video_name=video_name, + channel_url=channel_url, + channel_name=channel_name, + ) + session.add(new_entry) + session.commit() + logging.debug( + f"{video_name} by {channel_name} inserted into the database successfully" + ) + return jsonify(message="Data added to mpv queue and database"), 200 + except exc.SQLAlchemyError as e: + session.rollback() + logging.error(f"Failed to insert data into database: {e}") + return jsonify(message="Failed to add data to database"), 500 else: logging.error("Missing required data fields") return jsonify(message="Missing required data fields"), 400 @@ -197,10 +145,9 @@ def handle_request(): if __name__ == "__main__": - logging.info(f"Starting server on {HOST_NAME}:{PORT_NUMBER}...") - ensure_watch_history_table_exists() + logging.info(f"Starting server on {LISTEN_ADDRESS}:{LISTEN_PORT}...") try: - app.run(host=HOST_NAME, port=PORT_NUMBER) + app.run(host=LISTEN_ADDRESS, port=LISTEN_PORT) except Exception as e: logging.exception(f"Error occurred: {e}") except KeyboardInterrupt: