diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | bower.json | 6 | ||||
| -rw-r--r-- | common/utils.py | 2 | ||||
| -rw-r--r-- | db/db_manager.py | 143 | ||||
| -rw-r--r-- | mach2.py | 160 | ||||
| -rw-r--r-- | models/album.py | 113 | ||||
| -rw-r--r-- | models/artist.py | 124 | ||||
| -rw-r--r-- | models/base.py | 10 | ||||
| -rw-r--r-- | models/track.py | 456 | ||||
| -rw-r--r-- | requirements.txt | 13 | ||||
| -rw-r--r-- | tests/common/utils_test.py | 14 | ||||
| -rw-r--r-- | tests/conftest.py | 44 | ||||
| -rw-r--r-- | tests/db/db_manager_test.py | 53 | ||||
| -rw-r--r-- | tests/mach2_test.py | 52 | ||||
| -rw-r--r-- | tests/models/album_test.py | 63 | ||||
| -rw-r--r-- | tests/models/artist_test.py | 67 | ||||
| -rw-r--r-- | tests/models/track_test.py | 106 | ||||
| -rw-r--r-- | tests/test.db | bin | 0 -> 19456 bytes | |||
| -rw-r--r-- | tests/test.ogg | bin | 0 -> 3929 bytes | |||
| -rw-r--r-- | tests/testapp.db | bin | 0 -> 4096 bytes | 
20 files changed, 985 insertions, 444 deletions
@@ -17,4 +17,5 @@ node_modules  library.db  cscope.*  static/scripts/libs/ -.jshintrc
\ No newline at end of file +.jshintrc +.cache @@ -1,6 +1,6 @@  {    "name": "mach2", -  "version": "0.0.1", +  "version": "0.1.0",    "homepage": "https://github.com/michael-ball/mach2",    "authors": [      "Michaël Ball" @@ -17,7 +17,7 @@      "tests"    ],    "dependencies": { -    "angular": "1.4.8", +    "angular": "~1.5.0",      "bootstrap": "3.3.5",      "moment": "~2.10.6",      "angular-moment": "~0.10.3", @@ -27,7 +27,7 @@      "moment-timezone": "~0.4.1"    },    "resolutions": { -    "angular": "1.3.10" +    "angular": "~1.5.0"    },    "exportsOverride": {      "bootstrap": { diff --git a/common/utils.py b/common/utils.py index a7489f9..efac527 100644 --- a/common/utils.py +++ b/common/utils.py @@ -53,7 +53,7 @@ def update_clause_from_dict(data):      try:          for key in data.keys(): -            update_items.append("%s = :%s", (key, key)) +            update_items.append("%s = :%s" % (key, key))          if len(update_items) > 1:              update_clause = ", ".join(update_items) diff --git a/db/db_manager.py b/db/db_manager.py index f9b10c3..ad2fd14 100644 --- a/db/db_manager.py +++ b/db/db_manager.py @@ -4,6 +4,40 @@ import sqlite3  class DbManager: + +    create_album_table = "CREATE TABLE IF NOT EXISTS album (id INTEGER "\ +        "PRIMARY KEY, name TEXT, date TEXT, musicbrainz_albumid TEXT)" +    create_album_artist_table = "CREATE TABLE IF NOT EXISTS album_artist ("\ +        "album_id INTEGER, artist_id INTEGER, CONSTRAINT ALBUM_ARTIST_PK "\ +        "PRIMARY KEY (album_id, artist_id), CONSTRAINT ALBUM_ARTIST_FK_ALBUM "\ +        "FOREIGN KEY (album_id) REFERENCES album(id), CONSTRAINT "\ +        "ALBUM_ARTIST_FK_ARTIST FOREIGN KEY(artist_id) REFERENCES artist(id))" +    create_album_track_table = "CREATE TABLE IF NOT EXISTS album_track ("\ +        "album_id INTEGER, track_id INTEGER, CONSTRAINT ALBUM_TRACK_PK "\ +        "PRIMARY KEY (album_id, track_id), FOREIGN KEY(album_id) REFERENCES "\ +        "album(id), FOREIGN KEY(track_id) REFERENCES track(id))" +    create_artist_table = "CREATE TABLE IF NOT EXISTS artist (id INTEGER "\ +        "PRIMARY KEY, name TEXT(2000000000), sortname TEXT(2000000000), "\ +        "musicbrainz_artistid TEXT(2000000000))" +    create_artist_track_table = "CREATE TABLE IF NOT EXISTS artist_track ("\ +        "artist_id INTEGER, track_id INTEGER, CONSTRAINT ARTIST_TRACK_PK "\ +        "PRIMARY KEY (artist_id, track_id), FOREIGN KEY(artist_id) "\ +        "REFERENCES artist(id), FOREIGN KEY(track_id) REFERENCES track(id))" +    create_track_table = "CREATE TABLE IF NOT EXISTS track (id INTEGER, "\ +        "tracknumber INTEGER, name TEXT(2000000000), grouping "\ +        "TEXT(2000000000), filename TEXT(2000000000), CONSTRAINT TRACK_PK "\ +        "PRIMARY KEY (id))" +    create_musicbrainz_artist_index = "CREATE UNIQUE INDEX IF NOT EXISTS "\ +        "artist_musicbrainz_artistid ON artist(musicbrainz_artistid ASC)" +    create_track_filename_index = "CREATE INDEX IF NOT EXISTS "\ +        "track_filename_IDX ON track(filename)" +    create_track_grouping_index = "CREATE INDEX IF NOT EXISTS "\ +        "track_grouping_IDX ON track(grouping)" +    create_track_name_index = "CREATE INDEX IF NOT EXISTS track_name_IDX ON "\ +        "track(name)" +    create_track_number_index = "CREATE INDEX IF NOT EXISTS "\ +        "track_tracknumber_IDX ON track(tracknumber)" +      class __DbManager:          config = configparser.ConfigParser()          config.read("mach2.ini") @@ -57,25 +91,30 @@ class DbManager:              yield("COMMIT;") -        def __init__(self): +        def __init__(self, db=None):              new_db = False              cache_size_kb = 9766 -            if not os.path.isfile(self.config["DEFAULT"]["database"]): -                new_db = True +            if db: +                self.conn = sqlite3.connect(db) -            if new_db: -                self.conn = sqlite3.connect(":memory:") -                self.create_tables()              else: -                self.conn = sqlite3.connect(self.config["DEFAULT"]["database"]) -                library_info = os.stat(self.config["DEFAULT"]["database"]) -                cache_size_kb = round((library_info.st_size * 1.2) / 1024) +                if not os.path.isfile(self.config["DEFAULT"]["database"]): +                    new_db = True -                cursor = self.conn.cursor() -                # Setting pragma with ? placeholder produces an error -                cursor.execute("pragma cache_size=-%s" % cache_size_kb) -                cursor.close() +                if new_db: +                    self.conn = sqlite3.connect(":memory:") +                    self.create_tables() +                else: +                    self.conn = sqlite3.connect( +                        self.config["DEFAULT"]["database"]) +                    library_info = os.stat(self.config["DEFAULT"]["database"]) +                    cache_size_kb = round((library_info.st_size * 1.2) / 1024) + +                    cursor = self.conn.cursor() +                    # Setting pragma with ? placeholder produces an error +                    cursor.execute("pragma cache_size=-%s" % cache_size_kb) +                    cursor.close()              self.conn.row_factory = sqlite3.Row @@ -96,6 +135,12 @@ class DbManager:          def __str__(self):              return repr(self) +        def execute(self, script, parameters=None): +            if parameters: +                return self.conn.execute(script, parameters) + +            return self.conn.execute(script) +          def commit(self):              return self.conn.commit() @@ -109,65 +154,25 @@ class DbManager:              return self.conn.interrupt()          def create_tables(self): -            cursor = self.conn.cursor() -            cursor.execute("""CREATE TABLE IF NOT EXISTS album (id -                           INTEGER PRIMARY KEY, name TEXT, date TEXT, -                           musicbrainz_albumid TEXT)""") -            cursor.execute("""CREATE TABLE IF NOT EXISTS album_artist ( -                    album_id INTEGER, -                    artist_id INTEGER, -                    CONSTRAINT ALBUM_ARTIST_PK PRIMARY KEY (album_id, -                    artist_id), -                    CONSTRAINT ALBUM_ARTIST_FK_ALBUM FOREIGN KEY (album_id) -                        REFERENCES album(id), -                    CONSTRAINT ALBUM_ARTIST_FK_ARTIST FOREIGN KEY -                        (artist_id) REFERENCES artist(id) -                )""") -            cursor.execute("""CREATE TABLE IF NOT EXISTS album_track ( -                    album_id INTEGER, -                    track_id INTEGER, -                    CONSTRAINT ALBUM_TRACK_PK PRIMARY KEY (album_id, -                        track_id), -                    FOREIGN KEY(album_id) REFERENCES album(id), -                    FOREIGN KEY(track_id) REFERENCES track(id) -                )""") -            cursor.execute("""CREATE TABLE IF NOT EXISTS artist (id -                    INTEGER PRIMARY KEY, name -                    TEXT(2000000000), sortname TEXT(2000000000), -                    musicbrainz_artistid TEXT(2000000000))""") -            cursor.execute("""CREATE TABLE IF NOT EXISTS artist_track ( -                    artist_id INTEGER, -                    track_id INTEGER, -                    CONSTRAINT ARTIST_TRACK_PK PRIMARY KEY (artist_id, -                        track_id), -                    FOREIGN KEY(artist_id) REFERENCES artist(id), -                    FOREIGN KEY(track_id) REFERENCES track(id) -                )""") -            cursor.execute("""CREATE TABLE IF NOT EXISTS track ( -                    id INTEGER, -                    tracknumber INTEGER, -                    name TEXT(2000000000), -                    grouping TEXT(2000000000), -                    filename TEXT(2000000000), -                    CONSTRAINT TRACK_PK PRIMARY KEY (id) -                )""") -            cursor.execute("""CREATE UNIQUE INDEX IF NOT EXISTS -                           artist_musicbrainz_artistid ON -                           artist(musicbrainz_artistid ASC)""") -            cursor.execute("""CREATE INDEX IF NOT EXISTS track_filename_IDX -                           ON track(filename)""") -            cursor.execute("""CREATE INDEX IF NOT EXISTS track_grouping_IDX -                           ON track(grouping)""") -            cursor.execute("""CREATE INDEX IF NOT EXISTS track_name_IDX ON -                           track(name)""") -            cursor.execute("""CREATE INDEX IF NOT EXISTS -                           track_tracknumber_IDX ON track(tracknumber)""") -            cursor.close() +            with self.conn: +                self.conn.execute(DbManager.create_album_table) +                self.conn.execute(DbManager.create_album_artist_table) +                self.conn.execute(DbManager.create_album_track_table) +                self.conn.execute(DbManager.create_artist_table) +                self.conn.execute(DbManager.create_artist_track_table) +                self.conn.execute(DbManager.create_track_table) +                self.conn.execute(DbManager.create_musicbrainz_artist_index) +                self.conn.execute(DbManager.create_track_filename_index) +                self.conn.execute(DbManager.create_track_grouping_index) +                self.conn.execute(DbManager.create_track_name_index) +                self.conn.execute(DbManager.create_track_number_index)      instance = None -    def __new__(self): -        if not DbManager.instance: +    def __new__(self, db=None): +        if db: +            return DbManager.__DbManager(db) +        elif not DbManager.instance:              DbManager.instance = DbManager.__DbManager()          return DbManager.instance @@ -6,8 +6,8 @@ import os  import sqlite3  import tempfile -from flask import Flask, Response, g, redirect, render_template -from flask import request, url_for +from flask import Blueprint, Flask, Response, current_app, g, redirect, \ +    render_template, request, url_for  from flask.ext.compress import Compress  from flask.ext.login import LoginManager, current_user, login_required  from flask.ext.login import login_user, logout_user @@ -18,37 +18,32 @@ from models.artist import Artist  from models.track import Track  from models.user import User +import builtins -DATABASE = "app.db" -app = Flask(__name__) -app.config.from_object(__name__) +builtins.library_db = None +  config = configparser.ConfigParser()  config.read("mach2.ini") -app.config["DEBUG"] = config["DEFAULT"]["debug"] -app.config["SECRET_KEY"] = config["DEFAULT"]["secret_key"] +mach2 = Blueprint("mach2", __name__)  login_manager = LoginManager() -login_manager.login_view = "login" -login_manager.init_app(app) - -compress = Compress() -compress.init_app(app) +login_manager.login_view = "mach2.login"  def get_db():      db = getattr(g, "_database", None)      if db is None: -        db = sqlite3.connect(DATABASE) +        db = sqlite3.connect(current_app.config["DATABASE"])          db.row_factory = sqlite3.Row          setattr(g, "_database", db)      return db -@app.teardown_appcontext +@mach2.teardown_app_request  def close_connection(exception):      db = getattr(g, "_database", None)      if db is not None: @@ -98,13 +93,13 @@ def load_user_from_request(request):      return None -@app.route("/") +@mach2.route("/")  @login_required  def index():      return render_template("index.html", user=current_user) -@app.route("/albums") +@mach2.route("/albums")  @login_required  def albums():      returned_albums = [] @@ -143,60 +138,61 @@ def albums():      all_params.update(search_params)      if search_params: -        returned_albums = Album.search(**all_params) +        returned_albums = Album.search(db=builtins.library_db, **all_params)      else: -        returned_albums = Album.all(**params) +        returned_albums = Album.all(db=builtins.library_db, **params)      for album in returned_albums: -        albums.append(album.__dict__) +        albums.append(album.as_dict())      return json.dumps(albums) -@app.route("/albums/<int:album_id>/tracks") +@mach2.route("/albums/<int:album_id>/tracks")  @login_required  def album_tracks(album_id):      tracks = [] -    album = Album(id=album_id) +    album = Album(db=builtins.library_db, id=album_id)      for track in album.tracks: -        tracks.append(track.__dict__) +        tracks.append(track.as_dict())      return json.dumps(tracks) -@app.route("/albums/<int:album_id>/artists") +@mach2.route("/albums/<int:album_id>/artists")  @login_required  def album_artists(album_id):      artists = [] -    album = Album(id=album_id) +    album = Album(db=builtins.library_db, id=album_id)      for artist in album.artists: -        artists.append(artist.__dict__) +        artists.append(artist.as_dict())      return json.dumps(artists) -@app.route("/albums/<int:album_id>") +@mach2.route("/albums/<int:album_id>")  @login_required  def album(album_id): -    album = Album(id=album_id) +    album = Album(db=builtins.library_db, id=album_id) -    return json.dumps(album.__dict__) +    return json.dumps(album.as_dict()) -@app.route("/albums/<album_name>") +@mach2.route("/albums/<album_name>")  @login_required  def album_search(album_name):      albums = [] -    for album in Album.search(name={"data": album_name, "operator": "LIKE"}): -        albums.append(album.__dict__) +    for album in Album.search(db=builtins.library_db, +                              name={"data": album_name, "operator": "LIKE"}): +        albums.append(album.as_dict())      return json.dumps(albums) -@app.route("/artists") +@mach2.route("/artists")  @login_required  def artists():      order_by = None @@ -219,64 +215,66 @@ def artists():          off = request.args.get("offset")      if order_by: -        returned_artists = Artist.all(order=order_by, +        returned_artists = Artist.all(db=builtins.library_db, order=order_by,                                        direction=order_direction,                                        limit=lim, offset=off)      else: -        returned_artists = Artist.all(limit=lim, offset=off) +        returned_artists = Artist.all(db=builtins.library_db, limit=lim, +                                      offset=off)      for artist in returned_artists: -        artists.append(artist.__dict__) +        artists.append(artist.as_dict())      return json.dumps(artists) -@app.route("/artists/<int:artist_id>/tracks") +@mach2.route("/artists/<int:artist_id>/tracks")  @login_required  def artist_tracks(artist_id):      tracks = [] -    artist = Artist(id=artist_id) +    artist = Artist(db=builtins.library_db, id=artist_id)      for track in artist.tracks: -        tracks.append(track.__dict__) +        tracks.append(track.as_dict())      return json.dumps(tracks) -@app.route("/artists/<int:artist_id>/albums") +@mach2.route("/artists/<int:artist_id>/albums")  @login_required  def artist_albums(artist_id):      albums = [] -    artist = Artist(id=artist_id) +    artist = Artist(db=builtins.library_db, id=artist_id)      for album in artist.albums: -        albums.append(album.__dict__) +        albums.append(album.as_dict())      return json.dumps(albums) -@app.route("/artists/<int:artist_id>") +@mach2.route("/artists/<int:artist_id>")  @login_required  def artist_info(artist_id): -    artist = Artist(id=artist_id) +    artist = Artist(id=artist_id, db=builtins.library_db) -    return json.dumps(artist.__dict__) +    return json.dumps(artist.as_dict()) -@app.route("/artists/<artist_name>") +@mach2.route("/artists/<artist_name>")  @login_required  def artist_search(artist_name):      artists = [] -    for artist in Artist.search(name={ -                                "data": artist_name, -                                "operator": "LIKE" +    for artist in Artist.search(db=builtins.libary_db, +                                name={ +                                    "data": artist_name, +                                    "operator": "LIKE"                                  }): -        artists.append(artist.__dict__) +        artists.append(artist.as_dict())      return json.dumps(artists) -@app.route("/tracks") +@mach2.route("/tracks")  @login_required  def tracks():      order_by = None @@ -299,30 +297,32 @@ def tracks():          off = request.args.get("offset")      if order_by: -        returned_tracks = Track.all(order=order_by, direction=order_direction, -                                    limit=lim, offset=off) +        returned_tracks = Track.all(db=builtins.library_db, order=order_by, +                                    direction=order_direction, limit=lim, +                                    offset=off)      else: -        returned_tracks = Track.all(limit=lim, offset=off) +        returned_tracks = Track.all(db=builtins.library_db, +                                    limit=lim, offset=off)      for track in returned_tracks: -        tracks.append(track.__dict__) +        tracks.append(track.as_dict())      return json.dumps(tracks) -@app.route("/tracks/<int:track_id>/artists") +@mach2.route("/tracks/<int:track_id>/artists")  @login_required  def track_artists(track_id):      artists = [] -    track = Track(id=track_id) +    track = Track(db=builtins.library_db, id=track_id)      for artist in track.artists: -        artists.append(artist.__dict__) +        artists.append(artist.as_dict())      return json.dumps(artists) -@app.route("/tracks/<int:track_id>") +@mach2.route("/tracks/<int:track_id>")  @login_required  def track(track_id):      def stream_file(filename, chunksize=8192): @@ -335,7 +335,7 @@ def track(track_id):                      os.remove(filename)                      break -    local_track = Track(track_id) +    local_track = Track(db=builtins.library_db, id=track_id)      fd, temp_filename = tempfile.mkstemp() @@ -356,12 +356,13 @@ def track(track_id):      return resp -@app.route("/tracks/<track_name>") +@mach2.route("/tracks/<track_name>")  @login_required  def track_search(track_name):      tracks = [] -    for track in Track.search(name={"data": track_name, "operator": "LIKE"}): -        tracks.append(track.__dict__) +    for track in Track.search(db=builtins.library_db, +                              name={"data": track_name, "operator": "LIKE"}): +        tracks.append(track.as_dict())      return json.dumps(tracks) @@ -378,7 +379,7 @@ def load_user(userid):      return user -@app.route("/login", methods=["GET", "POST"]) +@mach2.route("/login", methods=["GET", "POST"])  def login():      if request.method == "POST":          user = None @@ -396,19 +397,48 @@ def login():          if user and user.verify(password):              login_user(user) -            return redirect(request.args.get("next") or url_for("index")) +            return redirect(request.args.get("next") or url_for("mach2.index"))          else:              user = None      return render_template("login.html") -@app.route("/logout") +@mach2.route("/logout")  @login_required  def logout():      logout_user()      return redirect("/") +@mach2.before_app_first_request +def setup_globals(): +    setattr(g, "_db_path", current_app.config["DATABASE"]) + + +def create_app(database=None, library=None): +    app = Flask(__name__) +    if database: +        app.config["DATABASE"] = database +    else: +        app.config["DATABASE"] = config["DEFAULT"]["app_db"] + +    if library: +        builtins.library_db = library + +    app.config["DEBUG"] = config["DEFAULT"]["debug"] +    app.config["SECRET_KEY"] = config["DEFAULT"]["secret_key"] + +    app.register_blueprint(mach2) + +    login_manager.init_app(app) + +    compress = Compress() +    compress.init_app(app) + +    return app + +  if __name__ == "__main__": +    app = create_app()      app.run() diff --git a/models/album.py b/models/album.py index 0d7cd54..96bea81 100644 --- a/models/album.py +++ b/models/album.py @@ -1,15 +1,17 @@  from common import utils  from db.db_manager import DbManager +from models.base import BaseModel -class Album(): -    def __init__(self, id=None, **kwargs): -        if id is not None: -            db = DbManager() -            cursor = db.cursor() +class Album(BaseModel): -            for row in cursor.execute("SELECT * FROM album WHERE id = ?", -                                      (id,)): +    def __init__(self, id=None, db=None, **kwargs): +        if db: +            self.db = db + +        if id is not None: +            for row in self.db.execute("SELECT * FROM album WHERE id = ?", +                                       (id,)):                  setattr(self, "id", id)                  setattr(self, "name", row[1])                  setattr(self, "date", row[2]) @@ -18,26 +20,32 @@ class Album():                  setattr(self, key, value)      def delete(self): -        db = DbManager() -        cursor = db.cursor() -          for track in self.tracks:              track.delete() -        cursor.execute("BEGIN TRANSACTION") +        with self.db.conn: +            delete_album = "DELETE FROM album WHERE id = ?" +            self.db.execute(delete_album, (self.id,)) -        delete_sql = "DELETE FROM album WHERE id = ?" -        cursor.execute(delete_sql, (self.id,)) +            delete_track_rel = "DELETE FROM album_track WHERE album_id = ?" +            self.db.execute(delete_track_rel, (self.id,)) -        delete_track_rel_sql = "DELETE FROM album_track WHERE album_id = ?" -        cursor.execute(delete_track_rel_sql, (self.id,)) +            delete_artist_rel = "DELETE FROM album_artist WHERE album_id = ?" +            self.db.execute(delete_artist_rel, (self.id,)) -        delete_artist_rel_sql = "DELETE FROM album_artist WHERE album_id = ?" -        cursor.execute(delete_artist_rel_sql, (self.id,)) +        return True -        cursor.execute("COMMIT TRANSACTION") +    @property +    def db(self): +        try: +            return self._db +        except AttributeError: +            self._db = DbManager() +            return self._db -        return True +    @db.setter +    def db(self, db): +        self._db = db      @property      def artists(self): @@ -46,15 +54,13 @@ class Album():          if not hasattr(self, "_artists"):              setattr(self, "_artists", []) -            db = DbManager() -            cursor = db.cursor() - -            for row in cursor.execute("""SELECT artist.* FROM artist INNER JOIN -                                      album_artist ON artist.id = -                                      album_artist.artist_id WHERE album_id = ? -                                      ORDER BY name ASC""", (self.id,)): -                artist = Artist(id=row[0], name=row[1], sortname=row[2], -                                musicbrainz_artistid=row[3]) +            for row in self.db.execute("SELECT artist.* FROM artist INNER " +                                       "JOIN album_artist ON artist.id = " +                                       "album_artist.artist_id WHERE " +                                       "album_id = ? ORDER BY name ASC", +                                       (self.id,)): +                artist = Artist(id=row[0], db=self.db, name=row[1], +                                sortname=row[2], musicbrainz_artistid=row[3])                  self._artists.append(artist)          return self._artists @@ -66,16 +72,16 @@ class Album():          if not hasattr(self, "_tracks"):              setattr(self, "_tracks", []) -            db = DbManager() -            cursor = db.cursor() - -            for row in cursor.execute("""SELECT track.* FROM track -                                      INNER JOIN album_track ON track.id = -                                      album_track.track_id WHERE album_id = ? -                                      ORDER BY tracknumber ASC""", (self.id,)): +            for row in self.db.execute("SELECT track.* FROM track INNER " +                                       "JOIN album_track ON track.id = " +                                       "album_track.track_id WHERE " +                                       "album_id = ? ORDER BY tracknumber " +                                       "ASC", (self.id,)): -                track = Track(id=row[0], tracknumber=row[1], name=row[2], -                              grouping=row[3], filename=row[4]) +                track = Track(id=row["id"], db=self.db, +                              tracknumber=row["tracknumber"], +                              name=row["name"], grouping=row["grouping"], +                              filename=row["filename"])                  self._tracks.append(track)          return self._tracks @@ -89,18 +95,16 @@ class Album():                  dirty_attributes[attr] = value          if len(dirty_attributes) > 0: -            db = DbManager() -            cursor = db.cursor() -              set_clause = utils.update_clause_from_dict(dirty_attributes)              dirty_attributes[id] = self.id              sql = " ".join(("UPDATE album"), set_clause, "WHERE id = :id") -            cursor.execute(sql, dirty_attributes) -    def search(order="album.id", direction="ASC", limit=None, -               offset=None, **search_params): +            with self.db.conn: +                self.db.execute(sql, dirty_attributes) + +    def search(db=None, **search_params):          """Find an album with the given params          Args: @@ -110,8 +114,8 @@ class Album():          """          albums = [] -        db = DbManager() -        cursor = db.cursor() +        if not db: +            db = DbManager()          # unpack search params          where_params = {} @@ -132,20 +136,21 @@ class Album():          result = None          if where_clause:              statement = " ".join(("SELECT * FROM album", where_clause)) -            result = cursor.execute(statement, value_params) +            result = db.execute(statement, value_params)          else: -            result = cursor.execute("SELECT * FROM album") +            result = db.execute("SELECT * FROM album")          for row in result:              albums.append( -                Album(id=row[0], name=row[1], date=row[2]) +                Album(id=row["id"], db=db, name=row["name"], date=row["date"])              )          return albums -    def all(order="album.id", direction="ASC", limit=None, offset=None): -        db = DbManager() -        cursor = db.cursor() +    def all(db=None, order="album.id", direction="ASC", limit=None, +            offset=None): +        if not db: +            db = DbManager()          albums = [] @@ -156,13 +161,13 @@ class Album():          if limit is not None and offset is not None:              select_string = " ".join((select_string, -                                     "LIMIT %s OFFSET %s" % (limit, offset))) +                                      "LIMIT %s OFFSET %s" % (limit, offset))) -        result = cursor.execute(select_string) +        result = db.execute(select_string)          for row in result:              albums.append( -                Album(id=row[0], name=row[1], date=row[2]) +                Album(id=row["id"], db=db, name=row["name"], date=row["date"])              )          return albums diff --git a/models/artist.py b/models/artist.py index fee081c..a76b2ee 100644 --- a/models/artist.py +++ b/models/artist.py @@ -1,44 +1,50 @@  from common import utils  from db.db_manager import DbManager +from models.base import BaseModel -class Artist: -    def __init__(self, id=None, **kwargs): +class Artist(BaseModel): + +    def __init__(self, id=None, db=None, **kwargs): +        if db: +            self.db = db +          if id is not None: -            db = DbManager() -            cursor = db.cursor() - -            for row in cursor.execute("SELECT * FROM artist WHERE id = ?", -                                      (id,)): -                setattr(self, "id", id) -                setattr(self, "name", row[1]) -                setattr(self, "sortname", row[2]) -                setattr(self, "musicbrainz_artistid", row[3]) +            for row in self.db.execute("SELECT * FROM artist WHERE id = ?", +                                       (id,)): +                for key in ["id", "name", "sortname", "musicbrainz_artistid"]: +                    setattr(self, key, row[key])          else:              for (key, value) in kwargs.items():                  setattr(self, key, value)      def delete(self): -        db = DbManager() -        cursor = db.cursor() -          for album in self.albums:              album.delete() -        cursor.execute("BEGIN TRANSACTION") +        with self.db.conn: +            delete_artist = "DELETE FROM artist WHERE id = ?" +            self.db.execute(delete_artist, (self.id,)) -        delete_sql = "DELETE FROM artist WHERE id = ?" -        cursor.execute(delete_sql, (self.id,)) +            delete_track_rel = "DELETE FROM artist_track WHERE artist_id = ?" +            self.db.execute(delete_track_rel, (self.id,)) -        delete_track_rel_sql = "DELETE FROM artist_track WHERE artist_id = ?" -        cursor.execute(delete_track_rel_sql, (self.id,)) +            delete_album_rel = "DELETE FROM album_artist WHERE artist_id = ?" +            self.db.execute(delete_album_rel, (self.id,)) -        delete_album_rel_sql = "DELETE FROM album_artist WHERE artist_id = ?" -        cursor.execute(delete_album_rel_sql, (self.id,)) +        return True -        cursor.execute("COMMIT TRANSACTION") +    @property +    def db(self): +        try: +            return self._db +        except AttributeError: +            self._db = DbManager() +            return self._db -        return True +    @db.setter +    def db(self, db): +        self._db = db      @property      def tracks(self): @@ -47,16 +53,16 @@ class Artist:          if not hasattr(self, "_tracks"):              setattr(self, "_tracks", []) -            db = DbManager() -            cursor = db.cursor() - -            for row in cursor.execute("""SELECT track.* FROM track -                                      INNER JOIN artist_track ON track.id = -                                      artist_track.track_id WHERE artist_id = ? -                                      ORDER BY name ASC""", (self.id,)): +            for row in self.db.execute("SELECT track.* FROM track INNER " +                                       "JOIN artist_track ON track.id = " +                                       "artist_track.track_id WHERE " +                                       "artist_id = ? ORDER BY name ASC", +                                       (self.id,)): -                track = Track(id=row[0], tracknumber=row[1], name=row[2], -                              grouping=row[3], filename=row[4]) +                track = Track(id=row["id"], db=self.db, +                              tracknumber=row["tracknumber"], name=row["name"], +                              grouping=row["grouping"], +                              filename=row["filename"])                  self._tracks.append(track)          return self._tracks @@ -68,14 +74,13 @@ class Artist:          if not hasattr(self, "_albums"):              setattr(self, "_albums", []) -            db = DbManager() -            cursor = db.cursor() - -            for row in cursor.execute("""SELECT album.* FROM album -                                      INNER JOIN album_artist ON album.id = -                                      album_artist.album_id WHERE artist_id = ? -                                      ORDER BY date ASC""", (self.id,)): -                album = Album(id=row[0], name=row[1], date=row[2]) +            for row in self.db.execute("SELECT album.* FROM album INNER " +                                       "JOIN album_artist ON album.id = " +                                       "album_artist.album_id WHERE " +                                       "artist_id = ? ORDER BY date ASC", +                                       (self.id,)): +                album = Album(id=row["id"], db=self.db, name=row["name"], +                              date=row["date"])                  self._albums.append(album)          return self._albums @@ -89,17 +94,16 @@ class Artist:                  dirty_attributes[attr] = value          if len(dirty_attributes) > 0: -            db = DbManager() -            cursor = db.cursor() -              set_clause = utils.update_clause_from_dict(dirty_attributes)              dirty_attributes[id] = self.id              sql = " ".join(("UPDATE artist"), set_clause, "WHERE id = :id") -            cursor.execute(sql, dirty_attributes) -    def search(**search_params): +            with self.db.conn: +                self.db.execute(sql, dirty_attributes) + +    def search(db=None, **search_params):          """Find an artist with the given params          Args: @@ -109,8 +113,8 @@ class Artist:          """          artists = [] -        db = DbManager() -        cursor = db.cursor() +        if not db: +            db = DbManager()          # unpack search params          where_params = {} @@ -124,21 +128,24 @@ class Artist:          result = []          if where_clause:              statement = " ".join(("SELECT * FROM artist", where_clause)) -            result = cursor.execute(statement, value_params) +            result = db.execute(statement, value_params)          else: -            result = cursor.execute("SELECT * FROM artist") +            result = db.execute("SELECT * FROM artist")          for row in result:              artists.append( -                Artist(id=row[0], name=row[1], sortname=row[2], -                       musicbrainz_artistid=row[3]) +                Artist(id=row["id"], db=db, name=row["name"], +                       sortname=row["sortname"], +                       musicbrainz_artistid=row["musicbrainz_artistid"])              )          return artists -    def all(order="sortname", direction="ASC", limit=None, offset=None): -        db = DbManager() -        cursor = db.cursor() +    def all(db=None, order="sortname", direction="ASC", limit=None, +            offset=None): +        if not db: +            db = DbManager() +          artists = []          select_string = "SELECT * FROM artist ORDER BY %s %s" % (order, @@ -146,14 +153,15 @@ class Artist:          if limit is not None and offset is not None:              select_string = " ".join((select_string, -                                     "LIMIT %s OFFSET %s" % (limit, offset))) +                                      "LIMIT %s OFFSET %s" % (limit, offset))) -        result = cursor.execute(select_string) +        result = db.execute(select_string)          for row in result:              artists.append( -                Artist(id=row[0], name=row[1], sortname=row[2], -                       musicbrainz_artistid=row[3]) +                Artist(id=row["id"], db=db, name=row["name"], +                       sortname=row["sortname"], +                       musicbrainz_artistid=row["musicbrainz_artistid"])              )          return artists diff --git a/models/base.py b/models/base.py new file mode 100644 index 0000000..fd40001 --- /dev/null +++ b/models/base.py @@ -0,0 +1,10 @@ +class BaseModel(): + +    def as_dict(self): +        this_dict = {} + +        for k in self.__dict__.keys(): +            if k != "_db": +                this_dict[k] = getattr(self, k) + +        return this_dict diff --git a/models/track.py b/models/track.py index 688f6ff..e0905e6 100644 --- a/models/track.py +++ b/models/track.py @@ -5,78 +5,120 @@ from common import utils  from db.db_manager import DbManager  from models.artist import Artist  from models.album import Album +from models.base import BaseModel  logging.basicConfig(format="%(asctime)s %(message)s", level=logging.DEBUG) -class Track: +class Track(BaseModel): -    def __init__(self, id=None, **kwargs): +    def __init__(self, id=None, db=None, **kwargs): +        if db: +            self.db = db -        setattr(self, "__data", {}) +        self.__data = {}          if id is not None: -            db = DbManager() -            cursor = db.cursor() - -            for row in cursor.execute("""SELECT * FROM track WHERE id = ?""", -                                      (id,)): -                setattr(self, "id", row[0]) -                setattr(self, "tracknumber", row[1]) -                setattr(self, "name", row[2]) -                setattr(self, "grouping", row[3]) -                setattr(self, "filename", row[4]) +            for row in self.db.execute("SELECT * FROM track WHERE id = ?", +                                       (id,)): +                for key in ["id", "tracknumber", "name", "grouping", +                            "filename"]: +                    setattr(self, key, row[key]) +                    self.__data[key] = row[key]          else:              for (key, value) in kwargs.items():                  setattr(self, key, value)                  self.__data[key] = value      def delete(self): -        db = DbManager() -        cursor = db.cursor()          delete_sql = "DELETE FROM track WHERE id = ?" -        cursor.execute(delete_sql, (self.id,)) + +        with self.db.conn: +            self.db.execute(delete_sql, (self.id,)) + +            # If there is an old album, remove it if it no longer has any +            # tracks +            try: +                del self._album +            except Exception: +                pass + +            old_album = self.album + +            if old_album: +                self.db.execute("DELETE FROM album_track WHERE track_id = ?", +                                (self.id,)) + +                if not old_album.tracks: +                    old_album.delete() + +            # If there are old artists, remove them if they no longer have +            # any tracks +            try: +                del self._artists +            except Exception: +                pass +            old_artists = self.artists + +            for old_artist in old_artists: +                self.db.execute("DELETE FROM artist_track WHERE track_id = " +                                "?", (self.id,)) + +                if not old_artist.tracks: +                    old_artist.delete()          return True      @property +    def db(self): +        try: +            return self._db +        except AttributeError: +            self._db = DbManager() +            return self._db + +    @db.setter +    def db(self, db): +        self._db = db + +    @property      def album(self):          if not hasattr(self, "_album"):              setattr(self, "_album", None) -            db = DbManager() -            cursor = db.cursor() - -            for row in cursor.execute("""SELECT album.* FROM album INNER JOIN -                                      album_track ON album.id = -                                      album_track.album_id WHERE track_id = ? -                                      LIMIT 1""", (self.id,)): -                setattr(self, "_album", Album(row[0])) +            for row in self.db.execute("SELECT album.* FROM album INNER " +                                       "JOIN album_track ON album.id = " +                                       "album_track.album_id WHERE " +                                       "track_id = ? LIMIT 1", (self.id,)): +                setattr(self, "_album", Album(id=row["id"], db=self.db, +                                              name=row["name"], +                                              date=row["date"]))          return self._album      @property      def artists(self):          if not hasattr(self, "_artists"): -            db = DbManager() -            cursor = db.cursor() +            cursor = self.db.cursor()              setattr(self, "_artists", []) -            for row in cursor.execute("""SELECT artist.* FROM artist INNER JOIN -                                      artist_track ON artist.id = -                                      artist_track.artist_id WHERE -                                      artist.id = ?""", (self.id,)): -                self._artists.append(Artist(row[0])) +            for row in cursor.execute("SELECT artist.* FROM artist INNER JOIN " +                                      "artist_track ON artist.id = " +                                      "artist_track.artist_id WHERE " +                                      "artist_track.track_id = ?", +                                      (self.id,)): +                self._artists.append(Artist(id=row["id"], db=self.db, +                                            name=row["name"], +                                            sortname=row["sortname"], +                                            musicbrainz_artistid=row[ +                                                "musicbrainz_artistid"]))          return self._artists      def update(self, metadata): -        db = DbManager() -        c = db.cursor() - -        c.execute("BEGIN TRANSACTION") +        c = self.db.cursor()          artist_names = metadata["artist"]          musicbrainz_artist_ids = [] @@ -109,38 +151,38 @@ class Track:              rows = None              if musicbrainz_artistid: -                rows = c.execute("""SELECT * FROM artist WHERE -                    musicbrainz_artistid = ?""", +                rows = c.execute("SELECT * FROM artist WHERE " +                                 "musicbrainz_artistid = ?",                                   (musicbrainz_artistid,))              else: -                rows = c.execute("""SELECT * FROM artist WHERE -                    name = ?""", (artist_name,)) +                rows = c.execute("SELECT * FROM artist WHERE name = ?", +                                 (artist_name,))              row = rows.fetchone()              if row: -                artist = Artist(id=row[0], name=row[1], -                                sortname=row[2], -                                musicbrainz_artistid=row[3]) +                artist = Artist(id=row["id"], db=self.db, name=row["name"], +                                sortname=row["sortname"], +                                musicbrainz_artistid=row[ +                                    "musicbrainz_artistid"])                  if artist.name != artist_name: -                    c.execute("""UPDATE artist SET name = ? WHERE id = ?""", +                    c.execute("UPDATE artist SET name = ? WHERE id = ?",                                (artist_name, artist.id))                      artist.name = artist_name                  if artist.sortname != artistsort: -                    c.execute("""UPDATE artist SET sortname = ? WHERE id = -                              ? """, (artistsort, id)) +                    c.execute("UPDATE artist SET sortname = ? WHERE id = ?", +                              (artistsort, id))                      artist.sortname = artistsort              else: -                c.execute("""INSERT INTO artist -                    (name, sortname, musicbrainz_artistid) VALUES( -                    ?,?,?)""", (artist_name, artistsort, -                                musicbrainz_artistid)) +                c.execute("INSERT INTO artist (name, sortname, " +                          "musicbrainz_artistid) VALUES(?, ?, ?)", +                          (artist_name, artistsort, musicbrainz_artistid))                  artist = Artist( -                    id=c.lastrowid, name=artist_name, +                    id=c.lastrowid, db=self.db, name=artist_name,                      sortname=artistsort,                      musicbrainz_artistid=musicbrainz_artistid                  ) @@ -170,100 +212,135 @@ class Track:          if mb_albumid:              rows = c.execute( -                """SELECT * FROM album WHERE musicbrainz_albumid = ?""", +                "SELECT * FROM album WHERE musicbrainz_albumid = ?",                  (mb_albumid,)              )              row = rows.fetchone()              if row: -                album = Album(id=row[0], name=row[1], date=row[2], -                              mb_albumid=row[3]) +                album = Album(id=row["id"], db=self.db, name=row["name"], +                              date=row["date"], +                              musicbrainz_albumid=row["musicbrainz_albumid"])              else: -                c.execute("""INSERT INTO album (name, `date`, -                          musicbrainz_albumid) VALUES (?,?,?)""", +                c.execute("INSERT INTO album (name, `date`, " +                          "musicbrainz_albumid) VALUES (?, ?, ?)",                            (album_name, album_date, mb_albumid)) -                album = Album(id=c.lastrowid, name=album_name, +                album = Album(id=c.lastrowid, db=self.db, name=album_name,                                date=album_date, musicbrainz_albumid=mb_albumid)          elif album_name:              rows = c.execute( -                """SELECT album.* FROM album INNER JOIN album_artist ON -                album_artist.album_id = album.id WHERE album.name = ? -                AND artist_id = ?""", (album_name, artist.id) +                "SELECT album.* FROM album INNER JOIN album_artist ON " +                "album_artist.album_id = album.id WHERE album.name = ? " +                "AND artist_id = ?", (album_name, artist.id)              )              row = rows.fetchone()              if row: -                album = Album(id=row[0], name=row[1], date=row[2]) +                album = Album(id=row["id"], db=self.db, name=row["name"], +                              date=row["date"], +                              musicbrainz_albumid=row["musicbrainz_albumid"])              else: -                c.execute("""INSERT INTO album (name, `date`) VALUES -                (?,?)""", (album_name, album_date)) +                c.execute("INSERT INTO album (name, `date`) VALUES (?, ?)", +                          (album_name, album_date)) -                album = Album(id=c.lastrowid, name=album_name, +                album = Album(id=c.lastrowid, db=self.db, name=album_name,                                date=album_date)          if album:              if album.name != album_name: -                c.execute("""UPDATE album SET name = ? WHERE id = ?""", +                c.execute("UPDATE album SET name = ? WHERE id = ?",                            (album_name, album.id))                  album.name = album_name              if album.date != album_date: -                c.execute("""UPDATE album SET date = ? WHERE id = ?""", +                c.execute("UPDATE album SET date = ? WHERE id = ?",                            (album_date, album.id))                  album.date = album_date -        for artist in artists: -            if album: -                try: -                    c.execute( -                        """INSERT INTO album_artist (artist_id, -                        album_id) VALUES(?,?)""", -                        (artist.id, album.id) -                    ) -                except sqlite3.IntegrityError: -                    pass -          track_number = None          track_name = None          track_grouping = None          try:              track_number = metadata["tracknumber"][0] +            setattr(self, "tracknumber", track_number)          except KeyError:              pass          try:              track_name = metadata["title"][0] +            setattr(self, "name", track_name)          except KeyError:              pass          try:              track_grouping = metadata["grouping"][0] +            setattr(self, "grouping", track_grouping)          except KeyError:              pass -        c.execute("""UPDATE track SET tracknumber = ?, name = ?, -                  grouping = ? WHERE id = ?""", -                  (track_number, track_name, track_grouping, -                   self.id)) +        c.execute("UPDATE track SET tracknumber = ?, name = ?, grouping = ? " +                  "WHERE id = ?", (track_number, track_name, track_grouping, +                                   self.id)) + +        # If there is an old album, remove it if it no longer has any tracks +        try: +            del self._album +        except Exception: +            pass + +        old_album = self.album + +        if old_album: +            c.execute("DELETE FROM album_track WHERE track_id = ?", (self.id,)) + +        if not old_album.tracks: +            old_album.delete() + +        # If there are old artists, remove them if they no longer have +        # any tracks +        try: +            del self._artists +        except Exception: +            pass +        old_artists = self.artists + +        for old_artist in old_artists: +            c.execute("DELETE FROM artist_track WHERE track_id = ?", +                      (self.id,)) + +            if not old_artist.tracks: +                old_artist.delete()          if album:              try: -                c.execute("""INSERT INTO album_track (album_id, -                          track_id) VALUES(?,?)""", -                          (album.id, self.id)) +                c.execute("INSERT INTO album_track (album_id, track_id) " +                          "VALUES(?, ?)", (album.id, self.id))              except sqlite3.IntegrityError:                  pass +            setattr(self, "_album", album) +          for artist in artists:              try: -                c.execute("""INSERT INTO artist_track -                          (artist_id, track_id) VALUES(?,?)""", -                          (artist.id, self.id)) +                c.execute("INSERT INTO artist_track (artist_id, track_id) " +                          "VALUES(?, ?)", (artist.id, self.id))              except sqlite3.IntegrityError:                  pass -        db.commit() +            if album: +                try: +                    c.execute( +                        "INSERT INTO album_artist (artist_id, album_id) " +                        "VALUES(?, ?)", (artist.id, album.id)) +                except sqlite3.IntegrityError: +                    pass + +        if artists: +            setattr(self, "_artists", artists) + +        self.db.commit() +        c.close()          return True @@ -272,21 +349,25 @@ class Track:          # check if the internal dict has been modified          for (attr, value) in self.__dict__.items(): -            if self.__data[attr] != getattr(self, attr): -                dirty_attributes[attr] = value +            try: +                if self.__data[attr] != getattr(self, attr): +                    dirty_attributes[attr] = value +            except AttributeError: +                pass +            except KeyError: +                pass          if len(dirty_attributes) > 0: -            db = DbManager() -            cursor = db.cursor() -              set_clause = utils.update_clause_from_dict(dirty_attributes) -            dirty_attributes[id] = self.id +            dirty_attributes["id"] = self.id + +            sql = " ".join(("UPDATE track", set_clause, "WHERE id = :id")) -            sql = " ".join(("UPDATE track"), set_clause, "WHERE id = :id") -            cursor.execute(sql, dirty_attributes) +            with self.db.conn: +                self.db.execute(sql, dirty_attributes) -    def search(**search_params): +    def search(db=None, **search_params):          """Find a track with the given params          Args: @@ -296,8 +377,9 @@ class Track:              filename: dict, with 'data' and 'operator' keys          """ -        db = DbManager() -        cursor = db.cursor() +        if not db: +            db = DbManager() +          tracks = []          # unpack search params @@ -312,37 +394,40 @@ class Track:          result = None          if where_clause:              statement = " ".join(("SELECT * FROM track", where_clause)) -            result = cursor.execute(statement, value_params) +            result = db.execute(statement, value_params)          else: -            result = cursor.execute("SELECT * FROM track") +            result = db.execute("SELECT * FROM track")          for row in result:              tracks.append( -                Track(id=row[0], tracknumber=row[1], name=row[3], -                      grouping=row[3], filename=row[4]) +                Track(id=row["id"], db=db, tracknumber=row["tracknumber"], +                      name=row["name"], grouping=row["grouping"], +                      filename=row["filename"])              )          return tracks -    def find_by_path(path): -        db = DbManager() -        cursor = db.cursor() +    def find_by_path(path, db=None): +        if not db: +            db = DbManager()          track = None -        for row in cursor.execute("""SELECT * FROM track WHERE filename = ? -                                  LIMIT 1""", (path,)): -            track = Track(row[0]) +        for row in db.execute("SELECT * FROM track WHERE filename = ? " +                              "LIMIT 1", (path,)): +            track = Track(id=row["id"], db=db, tracknumber=row["tracknumber"], +                          name=row["name"], grouping=row["grouping"], +                          filename=row["filename"])          return track -    def store(filename, metadata): -        if Track.find_by_path(filename): +    def store(filename, metadata, db=None): +        if Track.find_by_path(filename, db=db):              return True -        db = DbManager() -        c = db.cursor() +        if not db: +            db = DbManager() -        c.execute("BEGIN TRANSACTION") +        c = db.cursor()          artist_names = metadata["artist"]          musicbrainz_artist_ids = [] @@ -361,7 +446,7 @@ class Track:          for artist_name in artist_names:              musicbrainz_artistid = None -            artistsort = artist_name +            artistsort = None              try:                  musicbrainz_artistid = musicbrainz_artist_ids[i]              except IndexError: @@ -374,48 +459,47 @@ class Track:              rows = None              row = None              if musicbrainz_artistid: -                rows = c.execute("""SELECT * FROM artist WHERE -                    musicbrainz_artistid = ?""", +                rows = c.execute("SELECT * FROM artist WHERE " +                                 "musicbrainz_artistid = ?",                                   (musicbrainz_artistid,))                  row = rows.fetchone()                  if not row: -                    rows = c.execute("""SELECT * FROM artist WHERE -                        name = ? AND musicbrainz_artistid IS NULL""", +                    rows = c.execute("SELECT * FROM artist WHERE name = ? " +                                     "AND musicbrainz_artistid IS NULL",                                       (artist_name,))                      row = rows.fetchone()              if not row: -                rows = c.execute("""SELECT * FROM artist WHERE -                    name = ?""", (artist_name,)) +                rows = c.execute("SELECT * FROM artist WHERE name = ?", +                                 (artist_name,))                  row = rows.fetchone()              if row: -                artist = Artist(id=row[0], name=row[1], -                                sortname=row[2], -                                musicbrainz_artistid=row[3]) +                artist = Artist(id=row["id"], db=db, name=row["name"], +                                sortname=row["sortname"], +                                musicbrainz_artistid=row[ +                                    "musicbrainz_artistid"])                  if (musicbrainz_artistid and -                        (not hasattr(artist, "musicbrainz_artistid") -                            or not artist.musicbrainz_artistid)): -                    c.execute("""UPDATE artist SET -                        musicbrainz_artistid = ? WHERE id = ?""", +                    (not hasattr(artist, "musicbrainz_artistid") or +                     not artist.musicbrainz_artistid)): +                    c.execute("UPDATE artist SET musicbrainz_artistid = ? " +                              "WHERE id = ?",                                (musicbrainz_artistid, artist.id))                  if (artistsort and -                        (not hasattr(artist, "sortname") -                            or not artist.sortname)): -                    c.execute("""UPDATE artist SET -                            sortname = ? WHERE id = ?""", +                    (not hasattr(artist, "sortname") or +                     not artist.sortname)): +                    c.execute("UPDATE artist SET sortname = ? WHERE id = ?",                                (artistsort, artist.id))              else: -                c.execute("""INSERT INTO artist -                    (name, sortname, musicbrainz_artistid) VALUES( -                    ?,?,?)""", (artist_name, artistsort, -                                musicbrainz_artistid)) +                c.execute("INSERT INTO artist (name, sortname, " +                          "musicbrainz_artistid) VALUES(?, ?, ?)", +                          (artist_name, artistsort, musicbrainz_artistid))                  artist = Artist( -                    id=c.lastrowid, name=artist_name, +                    id=c.lastrowid, db=db, name=artist_name,                      sortname=artistsort,                      musicbrainz_artistid=musicbrainz_artistid                  ) @@ -444,50 +528,46 @@ class Track:              pass          if mb_albumid: -            rows = c.execute( -                """SELECT * FROM album WHERE musicbrainz_albumid = ?""", -                (mb_albumid,) -            ) +            rows = c.execute("SELECT * FROM album WHERE " +                             "musicbrainz_albumid = ?", (mb_albumid,))              row = rows.fetchone()              if row: -                album = Album(id=row[0], name=row[1], date=row[2], -                              mb_albumid=row[3]) +                album = Album(id=row["id"], db=db, name=row["name"], +                              date=row["date"], musicbrainz_albumid=row[ +                    "musicbrainz_albumid"])              else: -                c.execute("""INSERT INTO album (name, `date`, -                          musicbrainz_albumid) VALUES (?,?,?)""", +                c.execute("INSERT INTO album (name, `date`, " +                          "musicbrainz_albumid) VALUES (?, ?, ?)",                            (album_name, album_date, mb_albumid)) -                album = Album(id=c.lastrowid, name=album_name, +                album = Album(id=c.lastrowid, db=db, name=album_name,                                date=album_date, musicbrainz_albumid=mb_albumid)          elif album_name:              for artist in artists: -                rows = c.execute( -                    """SELECT album.* FROM album INNER JOIN album_artist ON -                    album_artist.album_id = album.id WHERE album.name = ? -                    AND artist_id = ?""", (album_name, artist.id) -                ) +                rows = c.execute("SELECT album.* FROM album INNER JOIN " +                                 "album_artist ON album_artist.album_id = " +                                 "album.id WHERE album.name = ? AND " +                                 "artist_id = ?", (album_name, artist.id))                  row = rows.fetchone()                  if row: -                    album = Album(id=row[0], name=row[1], date=row[2]) +                    album = Album(id=row["id"], db=db, name=row["name"], +                                  date=row["date"])                  else: -                    c.execute("""INSERT INTO album (name, `date`) VALUES -                    (?,?)""", (album_name, album_date)) +                    c.execute("INSERT INTO album (name, `date`) VALUES(?, ?)", +                              (album_name, album_date)) -                    album = Album(id=c.lastrowid, name=album_name, +                    album = Album(id=c.lastrowid, db=db, name=album_name,                                    date=album_date)          for artist in artists:              if album:                  try: -                    c.execute( -                        """INSERT INTO album_artist (artist_id, -                        album_id) VALUES(?,?)""", -                        (artist.id, album.id) -                    ) +                    c.execute("INSERT INTO album_artist (artist_id, album_id) " +                              "VALUES(?, ?)", (artist.id, album.id))                  except sqlite3.IntegrityError:                      pass @@ -508,63 +588,65 @@ class Track:              pass          track = None -        rows = c.execute("""SELECT * FROM track WHERE filename = ?""", -                         (filename,)) +        rows = c.execute("SELECT * FROM track WHERE filename = ?", (filename,))          row = rows.fetchone()          if row: -            track = Track(id=row[0], tracknumber=row[1], name=row[2], -                          grouping=row[3], filename=row[4]) +            track = Track(id=row["id"], db=db, +                          tracknumber=row["tracknumber"], name=row["name"], +                          grouping=row["grouping"], filename=row["filename"])          else: -            c.execute("""INSERT INTO track (tracknumber, name, -                grouping, filename) VALUES(?,?,?,?)""", +            c.execute("INSERT INTO track (tracknumber, name, grouping, " +                      "filename) VALUES(?, ?, ?, ?)",                        (track_number, track_name, track_grouping,                         filename)) -            track = Track(id=c.lastrowid, tracknumber=track_number, +            track = Track(id=c.lastrowid, db=db, tracknumber=track_number,                            name=track_name, grouping=track_grouping,                            filename=filename)          if album:              try: -                c.execute("""INSERT INTO album_track (album_id, -                          track_id) VALUES(?,?)""", -                          (album.id, track.id)) +                c.execute("INSERT INTO album_track (album_id, track_id) " +                          "VALUES(?,?)", (album.id, track.id))              except sqlite3.IntegrityError:                  pass          for artist in artists:              try: -                c.execute("""INSERT INTO artist_track -                          (artist_id, track_id) VALUES(?,?)""", -                          (artist.id, track.id)) +                c.execute("INSERT INTO artist_track (artist_id, track_id) " +                          "VALUES(?, ?)", (artist.id, track.id))              except sqlite3.IntegrityError:                  pass          db.commit() +        c.close() -        return True +        return track + +    def all(db=None, order="track.id", direction="ASC", limit=None, +            offset=None): +        if not db: +            db = DbManager() -    def all(order="track.id", direction="ASC", limit=None, offset=None): -        db = DbManager()          tracks = [] -        select_string = """SELECT * FROM track LEFT JOIN artist_track ON -            artist_track.track_id = track.id LEFT JOIN artist ON -            artist_track.artist_id = artist.id LEFT JOIN album_track ON -            album_track.track_id = track.id LEFT JOIN album ON -            album_track.album_id = album.id ORDER BY %s %s""" % (order, -                                                                 direction) +        select_string = "SELECT * FROM track LEFT JOIN artist_track ON " \ +            "artist_track.track_id = track.id LEFT JOIN artist ON " \ +            "artist_track.artist_id = artist.id LEFT JOIN album_track ON " \ +            "album_track.track_id = track.id LEFT JOIN album ON " \ +            "album_track.album_id = album.id ORDER BY %s %s" % (order, +                                                                direction) -        if limit is not None and offset is not None: +        if limit and offset:              select_string = " ".join((select_string, -                                     "LIMIT %s OFFSET %s" % (limit, offset))) +                                      "LIMIT %s OFFSET %s" % (limit, offset)))          result = db.execute(select_string)          for row in result: -            tracks.append( -                Track(id=row[0], tracknumber=row[1], name=row[3], -                      grouping=row[3], filename=row[4]) -            ) +            tracks.append(Track(id=row["id"], db=db, +                                tracknumber=row["tracknumber"], +                                name=row["name"], grouping=row["name"], +                                filename=row["filename"]))          return tracks diff --git a/requirements.txt b/requirements.txt index 1433b04..f3895ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ -Flask==0.10.1 -Flask-Compress==1.3.0 -Flask-Login==0.3.2 -gevent==1.1rc3 -mutagen==1.31 -passlib==1.6.5
\ No newline at end of file +Flask>=0.10.1 +Flask-Compress>=1.3.0 +Flask-Login>=0.3.2 +gevent>=1.1rc3 +mutagen>=1.31 +passlib>=1.6.5 +pytest>=2.8.7 diff --git a/tests/common/utils_test.py b/tests/common/utils_test.py new file mode 100644 index 0000000..9cf1c55 --- /dev/null +++ b/tests/common/utils_test.py @@ -0,0 +1,14 @@ +from common import utils + + +def test_make_where_clause(): +    params_with_between = {"column": "BETWEEN"} + +    assert utils.make_where_clause(params_with_between) == "WHERE column "\ +        "BETWEEN :column1 AND :column2" + + +def test_update_clause_from_dict(): +    test_data = {"name": "Flaf"} + +    assert utils.update_clause_from_dict(test_data) == "SET name = :name" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..0028475 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,44 @@ +import os + +import pytest + +from db.db_manager import DbManager + + +@pytest.fixture(scope="module") +def database(request): +    database = DbManager( +        db=os.path.join(os.path.dirname(os.path.realpath(__file__)), +                        "test.db")) + +    def fin(): +        database.close() + +    request.addfinalizer(fin) + +    return database + + +@pytest.fixture(scope="module") +def test_file(): +    test_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), +                             "test.ogg") + +    return test_file + + +@pytest.fixture(scope="class") +def app(request): +    db = os.path.join(os.path.dirname(os.path.realpath(__file__)), +                      "testapp.db") +    library_db = DbManager( +        db=os.path.join(os.path.dirname(os.path.realpath(__file__)), +                        "test.db")) + +    def fin(): +        library_db.close() + +    request.addfinalizer(fin) + +    request.cls.db = db +    request.cls.library_db = library_db diff --git a/tests/db/db_manager_test.py b/tests/db/db_manager_test.py new file mode 100644 index 0000000..911c62d --- /dev/null +++ b/tests/db/db_manager_test.py @@ -0,0 +1,53 @@ +from db.db_manager import DbManager + + +class TestDbManager: + +    def test_create_tables(self): +        assert DbManager.create_album_table == "CREATE TABLE IF NOT EXISTS "\ +            "album (id INTEGER PRIMARY KEY, name TEXT, date TEXT, "\ +            "musicbrainz_albumid TEXT)" + +        assert DbManager.create_album_artist_table == "CREATE TABLE IF NOT "\ +            "EXISTS album_artist (album_id INTEGER, artist_id INTEGER, "\ +            "CONSTRAINT ALBUM_ARTIST_PK PRIMARY KEY (album_id, artist_id), "\ +            "CONSTRAINT ALBUM_ARTIST_FK_ALBUM FOREIGN KEY (album_id) "\ +            "REFERENCES album(id), CONSTRAINT ALBUM_ARTIST_FK_ARTIST "\ +            "FOREIGN KEY(artist_id) REFERENCES artist(id))" + +        assert DbManager.create_album_track_table == "CREATE TABLE IF NOT "\ +            "EXISTS album_track (album_id INTEGER, track_id INTEGER, "\ +            "CONSTRAINT ALBUM_TRACK_PK PRIMARY KEY (album_id, track_id), "\ +            "FOREIGN KEY(album_id) REFERENCES album(id), FOREIGN "\ +            "KEY(track_id) REFERENCES track(id))" + +        assert DbManager.create_artist_table == "CREATE TABLE IF NOT EXISTS "\ +            "artist (id INTEGER PRIMARY KEY, name TEXT(2000000000), sortname "\ +            "TEXT(2000000000), musicbrainz_artistid TEXT(2000000000))" + +        assert DbManager.create_artist_track_table == "CREATE TABLE IF NOT "\ +            "EXISTS artist_track (artist_id INTEGER, track_id INTEGER, "\ +            "CONSTRAINT ARTIST_TRACK_PK PRIMARY KEY (artist_id, track_id), "\ +            "FOREIGN KEY(artist_id) REFERENCES artist(id), FOREIGN "\ +            "KEY(track_id) REFERENCES track(id))" + +        assert DbManager.create_track_table == "CREATE TABLE IF NOT EXISTS "\ +            "track (id INTEGER, tracknumber INTEGER, name TEXT(2000000000), "\ +            "grouping TEXT(2000000000), filename TEXT(2000000000), "\ +            "CONSTRAINT TRACK_PK PRIMARY KEY (id))" + +        assert DbManager.create_musicbrainz_artist_index == "CREATE UNIQUE "\ +            "INDEX IF NOT EXISTS artist_musicbrainz_artistid ON "\ +            "artist(musicbrainz_artistid ASC)" + +        assert DbManager.create_track_filename_index == "CREATE INDEX IF NOT "\ +            "EXISTS track_filename_IDX ON track(filename)" + +        assert DbManager.create_track_grouping_index == "CREATE INDEX IF NOT "\ +            "EXISTS track_grouping_IDX ON track(grouping)" + +        assert DbManager.create_track_name_index == "CREATE INDEX IF NOT "\ +            "EXISTS track_name_IDX ON track(name)" + +        assert DbManager.create_track_number_index == "CREATE INDEX IF NOT "\ +            "EXISTS track_tracknumber_IDX ON track(tracknumber)" diff --git a/tests/mach2_test.py b/tests/mach2_test.py new file mode 100644 index 0000000..c45ff2a --- /dev/null +++ b/tests/mach2_test.py @@ -0,0 +1,52 @@ +import json +import unittest + +import pytest + +from mach2 import create_app + + +@pytest.mark.usefixtures("app") +class Mach2TestCase(unittest.TestCase): + +    def setUp(self): +        app = create_app(database=self.db, library=self.library_db) +        app.config['TESTING'] = True +        self.app = app.test_client() + +    def login(self, username, password): +        return self.app.post('/login', data=dict( +            username=username, +            password=password +        ), follow_redirects=True) + +    def logout(self): +        return self.app.get('/logout', follow_redirects=True) + +    def test_login(self): +        rv = self.login("admin", "testpass") +        assert bytes("Log out", "utf-8") in rv.data +        self.logout() +        rv = self.login("wrong", "definitelywrong") +        assert bytes("Log out", "utf-8") not in rv.data +        self.logout() + +    def test_album(self): +        self.login("admin", "testpass") + +        rv = self.app.get("/albums/1") +        assert bytes("Album 1", "utf-8") in rv.data + +        self.logout() + +    def test_artists(self): +        self.login("admin", "testpass") +        rv = self.app.get("/artists") + +        assert bytes("Artist 1", "utf-8") in rv.data +        assert bytes("Artist 2", "utf-8") in rv.data + +        artists = json.loads(rv.data.decode("utf-8")) +        assert artists + +        self.logout() diff --git a/tests/models/album_test.py b/tests/models/album_test.py new file mode 100644 index 0000000..680a5cb --- /dev/null +++ b/tests/models/album_test.py @@ -0,0 +1,63 @@ +from models.album import Album + + +def test_instance(database): +    album = Album(id=1, db=database) +    assert album.id == 1 +    assert album.name == "Album 1" +    assert album.date == "1999-02-04" + + +def test_artists(database): +    album = Album(id=1, db=database) +    assert len(album.artists) == 1 +    assert album.artists[0].name == "Artist 2" + + +def test_tracks(database): +    album = Album(id=1, db=database) +    assert len(album.tracks) == 2 +    assert album.tracks[0].name == "Album track 1" +    assert album.tracks[0].tracknumber == 1 +    assert album.tracks[0].filename == "album/1.mp3" +    assert album.tracks[1].name == "Album track 2" +    assert album.tracks[1].tracknumber == 2 +    assert album.tracks[1].grouping == "swing" +    assert album.tracks[1].filename == "album/2.mp3" + + +def test_delete(database): +    with database.conn: +        cursor = database.cursor() + +        cursor.execute("INSERT INTO album (name, date) VALUES(?,?)", +                       ("Test album", "2016-02-05")) + +        album_id = cursor.lastrowid +        cursor.close() + +        album = Album(album_id, db=database) + +        assert album.delete() + +    test_album = Album(album_id, db=database) +    assert not hasattr(test_album, "name") + + +def test_search(database): +    search_payload = {"name": {"data": "Album 1", "operator": "="}} +    album_results = Album.search(db=database, **search_payload) + +    assert len(album_results) > 0 + +    invalid_search_payload = {"name": {"data": "This album does not exist", +                                       "operator": "="}} +    no_album_results = Album.search(db=database, **invalid_search_payload) + +    assert len(no_album_results) == 0 + + +def test_all(database): +    album_results = Album.all(db=database) + +    assert len(album_results) > 0 diff --git a/tests/models/artist_test.py b/tests/models/artist_test.py new file mode 100644 index 0000000..bb66813 --- /dev/null +++ b/tests/models/artist_test.py @@ -0,0 +1,67 @@ +from models.artist import Artist + + +def test_instance(database): +    album = Artist(id=1, db=database) +    assert album.id == 1 +    assert album.name == "Artist 1" + + +def test_albums(database): +    artist1 = Artist(id=1, db=database) +    assert len(artist1.albums) == 0 +    artist2 = Artist(id=2, db=database) +    assert len(artist2.albums) == 1 +    assert artist2.albums[0].name == "Album 1" +    assert artist2.albums[0].date == "1999-02-04" + + +def test_tracks(database): +    artist1 = Artist(id=1, db=database) +    assert len(artist1.tracks) == 1 +    assert artist1.tracks[0].name == "Non album track" +    assert artist1.tracks[0].tracknumber is None +    assert artist1.tracks[0].filename == "1.mp3" +    artist2 = Artist(id=2, db=database) +    assert artist2.tracks[0].name == "Album track 1" +    assert artist2.tracks[0].tracknumber == 1 +    assert artist2.tracks[0].filename == "album/1.mp3" +    assert artist2.tracks[1].name == "Album track 2" +    assert artist2.tracks[1].tracknumber == 2 +    assert artist2.tracks[1].grouping == "swing" +    assert artist2.tracks[1].filename == "album/2.mp3" + + +def test_delete(database): +    with database.conn: +        cursor = database.cursor() + +        cursor.execute("INSERT INTO artist (name) VALUES(?)", ("Test artist",)) + +        artist_id = cursor.lastrowid + +        artist = Artist(artist_id, db=database) + +        assert artist.delete() + +    test_artist = Artist(artist_id, db=database) +    assert not hasattr(test_artist, "name") + + +def test_search(database): +    search_payload = {"name": {"data": "Artist 1", "operator": "="}} +    artist_results = Artist.search(db=database, **search_payload) + +    assert len(artist_results) > 0 + +    invalid_search_payload = {"name": {"data": "This artist does not exist", +                                       "operator": "="}} +    no_artist_results = Artist.search(db=database, **invalid_search_payload) + +    assert len(no_artist_results) == 0 + + +def test_all(database): +    artist_results = Artist.all(db=database) + +    assert len(artist_results) > 0 diff --git a/tests/models/track_test.py b/tests/models/track_test.py new file mode 100644 index 0000000..56685fd --- /dev/null +++ b/tests/models/track_test.py @@ -0,0 +1,106 @@ +import mutagen + +from models.track import Track + + +def test_instance(database): +    track = Track(id=1, db=database) +    assert track.id == 1 +    assert track.name == "Non album track" +    assert track.filename == "1.mp3" + + +def test_as_dict(database): +    track = Track(id=1, db=database) + +    track_dict = track.as_dict() + +    assert "_db" not in track_dict.keys() +    assert track_dict["id"] == 1 +    assert track_dict["name"] == "Non album track" +    assert track_dict["filename"] == "1.mp3" + + +def test_album(database): +    track1 = Track(id=1, db=database) +    assert track1.album is None +    track2 = Track(id=2, db=database) +    assert track2.album.name == "Album 1" +    assert track2.album.date == "1999-02-04" + + +def test_artists(database): +    track = Track(id=1, db=database) +    assert track.artists is not None +    assert len(track.artists) > 0 +    assert track.artists[0].name == "Artist 1" + + +def test_find_by_path(database): +    track1 = Track.find_by_path("album/2.mp3", db=database) + +    assert track1.filename == "album/2.mp3" +    assert track1.name == "Album track 2" +    assert track1.grouping == "swing" + +    nonexistent_track = Track.find_by_path("path/does/not/exist.mp3", +                                           db=database) +    assert nonexistent_track is None + + +def test_search(database): +    tracks = Track.search(db=database, name={"data": "Album track %", +                                             "operator": "LIKE"}) + +    assert tracks is not None +    assert len(tracks) == 2 + + +def test_store(database, test_file): +    metadata = mutagen.File(test_file, easy=True) + +    test_track = Track.store(test_file, metadata, db=database) + +    assert test_track.filename == test_file +    assert test_track.name == "Silence" +    assert test_track.grouping == "Jazz" +    assert test_track.tracknumber == 3 + +    assert test_track.album.name == "Dummy album" +    assert test_track.album.date == "2003" + +    assert test_track.artists +    assert test_track.artists[0].name == "Test Artist Flaf" + + +def test_update(database, test_file): +    metadata = {"artist": ["New artist"], "title": ["New title"]} + +    test_track = Track.find_by_path(test_file, db=database) +    test_track.update(metadata) + +    assert test_track.artists +    assert len(test_track.artists) == 1 +    assert test_track.artists[0].name == "New artist" +    assert test_track.name == "New title" + + +def test_save(database, test_file): +    test_track = Track.find_by_path(test_file, db=database) + +    test_track.name = "Totally new name" +    test_track.save() + +    new_track_to_test = Track.find_by_path(test_file, db=database) + +    assert new_track_to_test.name == "Totally new name" + + +def test_delete(database, test_file): +    test_track = Track.find_by_path(test_file, db=database) + +    test_track.delete() + +    should_not_exist = Track.find_by_path(test_file, db=database) + +    assert should_not_exist is None diff --git a/tests/test.db b/tests/test.db Binary files differnew file mode 100644 index 0000000..ccb7825 --- /dev/null +++ b/tests/test.db diff --git a/tests/test.ogg b/tests/test.ogg Binary files differnew file mode 100644 index 0000000..4ece5bc --- /dev/null +++ b/tests/test.ogg diff --git a/tests/testapp.db b/tests/testapp.db Binary files differnew file mode 100644 index 0000000..2fc7b8c --- /dev/null +++ b/tests/testapp.db  | 
