From 1c0eb91333e39bdca2913b8ad406a1107534473e Mon Sep 17 00:00:00 2001 From: ksyasuda Date: Fri, 6 Sep 2024 02:14:12 -0700 Subject: [PATCH 1/3] update to use sqlalchemy - change from mysql-connector-python to sqlalchemy - allow for connection to sqlite3, mysql, mariadb, postgres, and oracle databases - change `DATE` columns to `DATETIME` type - add WHO columns - install database connectors and required packages --- .gitignore | 1 + Dockerfile | 6 ++ VERSION | 2 +- docker-compose.yml | 13 +++ env.example | 15 ++-- mpv-youtube-queue-server.service | 5 +- requirements.txt | 10 ++- server.py | 148 ++++++++++--------------------- 8 files changed, 85 insertions(+), 115 deletions(-) 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..e6b7217 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==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..f9d66d4 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,30 @@ 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()) + creation_date = Column(Date, nullable=False, server_default=func.current_date()) + 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 +96,25 @@ 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.info("Data inserted into database") + 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 +144,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: -- 2.45.2 From badcee8d7c63732976f9ff300e2e542549fa8a23 Mon Sep 17 00:00:00 2001 From: sudacode Date: Fri, 6 Sep 2024 02:15:33 -0700 Subject: [PATCH 2/3] update requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e6b7217..be80582 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ Jinja2==3.1.4 MarkupSafe==2.1.5 oracledb==2.4.1 packaging==24.1 -psycopg2==2.9.9 +psycopg2-binary==2.9.9 pycparser==2.22 PyMySQL==1.1.1 SQLAlchemy==2.0.34 -- 2.45.2 From 1847403254bf1d8b5202948c97d07d1e3f1d7aec Mon Sep 17 00:00:00 2001 From: sudacode Date: Fri, 6 Sep 2024 02:55:10 -0700 Subject: [PATCH 3/3] remove creation_date column --- server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index f9d66d4..94512db 100755 --- a/server.py +++ b/server.py @@ -56,7 +56,6 @@ class WatchHistory(Base): channel_url = Column(String(255), nullable=False) channel_name = Column(String(255), nullable=False) watch_date = Column(DateTime, nullable=False, server_default=func.now()) - creation_date = Column(Date, nullable=False, server_default=func.current_date()) created_by = Column( String(100), nullable=False, server_default="mpv-youtube-queue-server" ) @@ -109,7 +108,9 @@ def add_video(): ) session.add(new_entry) session.commit() - logging.info("Data inserted into database") + 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() -- 2.45.2