Compare commits

..

3 Commits

Author SHA1 Message Date
1847403254
remove creation_date column 2024-09-06 02:55:10 -07:00
badcee8d7c
update requirements 2024-09-06 02:15:33 -07:00
ksyasuda
1c0eb91333
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
2024-09-06 02:14:12 -07:00
8 changed files with 86 additions and 115 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.env .env
env/* env/*
.git .git
db/*

View File

@ -5,6 +5,12 @@ FROM python:3.10-slim
ENV LISTEN_ADDRESS="0.0.0.0" \ ENV LISTEN_ADDRESS="0.0.0.0" \
LISTEN_PORT=8080 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 # Set the working directory in the container
WORKDIR /app WORKDIR /app

View File

@ -1 +1 @@
0.0.2 0.1.0

View File

@ -13,6 +13,19 @@ services:
networks: networks:
- mpv-youtube-queue-server - mpv-youtube-queue-server
restart: unless-stopped 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: networks:
mpv-youtube-queue-server: mpv-youtube-queue-server:
external: true external: true

View File

@ -1,12 +1,11 @@
LISTEN_ADDRESS=0.0.0.0 # Lisen on all interfaces LISTEN_ADDRESS=0.0.0.0 # Lisen on all interfaces
LISTEN_PORT=8080 # Internal port number LISTEN_PORT=8080 # Internal port number
MPV_SOCKET=/tmp/mpvsocket # Path to mpv socket 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 LOGLEVEL=info
# Options:
# mysql+pymysql://<user>:<password>@<host>[:<port>]/<dbname> - 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=<service>[&key=value&key=value...]]
DATABASE_URL=mysql+pymysql://user:password@localhost:3306/mpv

View File

@ -10,10 +10,7 @@ Restart=on-failure
Environment="MPV_SOCKET=/tmp/mpvsocket" Environment="MPV_SOCKET=/tmp/mpvsocket"
Environment="LISTEN_ADDRESS=0.0.0.0" Environment="LISTEN_ADDRESS=0.0.0.0"
Environment="LISTEN_PORT=42069" Environment="LISTEN_PORT=42069"
Environment="MYSQL_HOST=http://localhost" Environment="DATABASE_URL=mysql+mysqldb://user:password@localhost:3306/mpv"
Environment="MYSQL_USER=mpvuser"
Environment="MYSQL_PASSWORD=SecretPassword"
Environment="MYSQL_PORT=3306"
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@ -1,10 +1,18 @@
blinker==1.8.2 blinker==1.8.2
cffi==1.17.1
click==8.1.7 click==8.1.7
cryptography==43.0.1
Flask==3.0.3 Flask==3.0.3
greenlet==3.0.3
gunicorn==23.0.0 gunicorn==23.0.0
itsdangerous==2.2.0 itsdangerous==2.2.0
Jinja2==3.1.4 Jinja2==3.1.4
MarkupSafe==2.1.5 MarkupSafe==2.1.5
mysql-connector-python==9.0.0 oracledb==2.4.1
packaging==24.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 Werkzeug==3.0.4

149
server.py
View File

@ -3,22 +3,17 @@ import logging
import os import os
import socket import socket
import time import time
import urllib.parse
from datetime import date
import mysql.connector
from flask import Flask, jsonify, request 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 # Set up basic logging
logging.basicConfig( logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" 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 # Flask app
app = Flask(__name__) 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 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 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") 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() LOGLEVEL = os.getenv("LOGLEVEL", "INFO").strip().upper()
if LOGLEVEL == "DEBUG": if LOGLEVEL == "DEBUG":
@ -50,53 +40,29 @@ elif LOGLEVEL == "ERROR":
else: else:
logging.getLogger().setLevel(logging.INFO) logging.getLogger().setLevel(logging.INFO)
# Set up SQLAlchemy
def get_mysql_connection(): Base = declarative_base()
"""Get a MySQL database connection.""" engine = create_engine(DATABASE_URL)
try: Session = sessionmaker(bind=engine)
logging.debug( session = Session()
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
def ensure_watch_history_table_exists(): class WatchHistory(Base):
"""Ensure the watch_history table exists in the mpv schema, otherwise create it.""" __tablename__ = "watch_history"
connection = get_mysql_connection()
if connection: whid = Column(Integer, primary_key=True, autoincrement=True)
try: video_url = Column(String(255), nullable=False)
cursor = connection.cursor() video_name = Column(String(255), nullable=False)
cursor.execute("CREATE DATABASE IF NOT EXISTS mpv") channel_url = Column(String(255), nullable=False)
cursor.execute( channel_name = Column(String(255), nullable=False)
""" watch_date = Column(DateTime, nullable=False, server_default=func.now())
CREATE TABLE IF NOT EXISTS mpv.watch_history ( created_by = Column(
whid INT AUTO_INCREMENT PRIMARY KEY, String(100), nullable=False, server_default="mpv-youtube-queue-server"
video_url VARCHAR(255) NOT NULL, )
video_name VARCHAR(255) NOT NULL,
channel_url VARCHAR(255) NOT NULL,
channel_name VARCHAR(255) NOT NULL, # Ensure tables exist
watch_date DATE NOT NULL Base.metadata.create_all(engine)
)
"""
)
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()
def send_to_mpv(command): def send_to_mpv(command):
@ -129,45 +95,27 @@ def add_video():
video_name: str = data.get("video_name") video_name: str = data.get("video_name")
channel_url: str = data.get("channel_url") channel_url: str = data.get("channel_url")
channel_name: str = data.get("channel_name") 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"Received data: {data}")
logging.debug(f"Watch date: {watch_date}")
# Insert the data into the MySQL database try:
connection = get_mysql_connection() new_entry = WatchHistory(
if connection: video_url=video_url,
try: video_name=video_name,
query = """ channel_url=channel_url,
INSERT INTO mpv.watch_history (video_url, video_name, channel_url, channel_name, watch_date) channel_name=channel_name,
VALUES (%s, %s, %s, %s, %s) )
""" session.add(new_entry)
cursor = connection.cursor() session.commit()
cursor.execute( logging.debug(
query, f"{video_name} by {channel_name} inserted into the database successfully"
( )
video_url, return jsonify(message="Data added to mpv queue and database"), 200
video_name, except exc.SQLAlchemyError as e:
channel_url, session.rollback()
channel_name, logging.error(f"Failed to insert data into database: {e}")
watch_date, return jsonify(message="Failed to add data to database"), 500
),
)
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
else: else:
logging.error("Missing required data fields") logging.error("Missing required data fields")
return jsonify(message="Missing required data fields"), 400 return jsonify(message="Missing required data fields"), 400
@ -197,10 +145,9 @@ def handle_request():
if __name__ == "__main__": if __name__ == "__main__":
logging.info(f"Starting server on {HOST_NAME}:{PORT_NUMBER}...") logging.info(f"Starting server on {LISTEN_ADDRESS}:{LISTEN_PORT}...")
ensure_watch_history_table_exists()
try: try:
app.run(host=HOST_NAME, port=PORT_NUMBER) app.run(host=LISTEN_ADDRESS, port=LISTEN_PORT)
except Exception as e: except Exception as e:
logging.exception(f"Error occurred: {e}") logging.exception(f"Error occurred: {e}")
except KeyboardInterrupt: except KeyboardInterrupt: