#!/usr/bin/python3 import os import sqlalchemy from datetime import datetime from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import ( Column, DateTime, Float, ForeignKey, Integer, String ) from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm import relationship, sessionmaker from config import Config from log import DEBUG, ERROR, INFO INFO("Starting") # Create session at the global level as per # https://docs.sqlalchemy.org/en/13/orm/session_basics.html # Set up database connection INFO("Connect to database") engine = sqlalchemy.create_engine(f"{Config.MYSQL_CONNECT}?charset=utf8", encoding='utf-8', echo=Config.DISPLAY_SQL) Base = declarative_base() Base.metadata.create_all(engine) # Create a Session Session = sessionmaker(bind=engine) session = Session() # Database classes class Settings(Base): __tablename__ = 'settings' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(32), nullable=False, unique=True) f_datetime = Column(DateTime, default=None, nullable=True) f_int = Column(Integer, default=None, nullable=True) f_string = Column(String(128), default=None, nullable=True) @classmethod def get_int(cls, name): try: int_setting = session.query(cls).filter( cls.name == name).one() except NoResultFound: int_setting = Settings() int_setting.name = name int_setting.f_int = None session.add(int_setting) session.commit() return int_setting def update(self, data): for key, value in data.items(): assert hasattr(self, key) setattr(self, key, value) session.commit() class PlaylistTracks(Base): __tablename__ = 'playlisttracks' Base.metadata, id = Column(Integer, primary_key=True, autoincrement=True) playlist_id = Column(Integer, ForeignKey('playlists.id'), primary_key=True) track_id = Column(Integer, ForeignKey('tracks.id'), primary_key=True) sort = Column(Integer, nullable=False) tracks = relationship("Tracks", back_populates="playlists") playlists = relationship("Playlists", back_populates="tracks") class Playlists(Base): """ Usage: pl = session.query(Playlists).filter(Playlists.id == 1).one() pl pl.tracks [<__main__.PlaylistTracks at 0x7fcd20181c18>, <__main__.PlaylistTracks at 0x7fcd20181c88>, <__main__.PlaylistTracks at 0x7fcd20181be0>, <__main__.PlaylistTracks at 0x7fcd20181c50>] [a.tracks for a in pl.tracks] [ glue.track_id = tr.id pl.tracks.append(glue) session.commit() [a.tracks for a in pl.tracks] [") # Currently we only support one playlist, so make that obvious from # function name @classmethod def get_only_playlist(cls): return session.query(Playlists).filter(Playlists.id == 1).one() def add_track(self, track): self.tracks.append(track) def get_tracks(self): return [a.tracks for a in self.tracks] class Tracks(Base): __tablename__ = 'tracks' id = Column(Integer, primary_key=True, autoincrement=True) title = Column(String(256), index=True) artist = Column(String(256), index=True) duration = Column(Integer, index=True) start_gap = Column(Integer, index=False) fade_at = Column(Integer, index=False) silence_at = Column(Integer, index=False) path = Column(String(2048), index=False, nullable=False) mtime = Column(Float, index=True) lastplayed = Column(DateTime, index=True, default=None) playlists = relationship("PlaylistTracks", back_populates="tracks") playdates = relationship("Playdates", back_populates="tracks") def __repr__(self): return ( f"" ) @classmethod def get_or_create(cls, path): DEBUG(f"get_or_create(cls, {path})") try: track = session.query(cls).filter(cls.path == path).one() except NoResultFound: track = Tracks() track.path = path session.add(track) return track @staticmethod def get_path(id): try: path = session.query(Tracks.path).filter(Tracks.id == id).one()[0] return os.path.join(Config.ROOT, path) except NoResultFound: print(f"Can't find track id {id}") return None @staticmethod def get_track(id): try: DEBUG(f"get_track({id})") track = session.query(Tracks).filter(Tracks.id == id).one() return track except NoResultFound: ERROR(f"get_track({id}): not found") return None @staticmethod def search_titles(text): return ( session.query(Tracks) .filter(Tracks.title.ilike(f"%{text}%")) .order_by(Tracks.title) ).all() @staticmethod def track_from_id(id): return session.query(Tracks).filter( Tracks.id == id).one() class Playdates(Base): __tablename__ = 'playdates' id = Column(Integer, primary_key=True, autoincrement=True) lastplayed = Column(DateTime, index=True, default=None) track_id = Column(Integer, ForeignKey('tracks.id')) tracks = relationship("Tracks", back_populates="playdates") @staticmethod def add_playdate(track): DEBUG(f"add_playdate({track})") pd = Playdates() pd.lastplayed = datetime.now() pd.tracks.append(track) session.add(pd) session.commit()