diff --git a/.gitignore b/.gitignore index ce11010..57c56ef 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ *.pyc -*.swp +.*.sw[a-z] *~ build/ dist/ diff --git a/README.md b/README.md index c8c05fc..5c06ca4 100644 --- a/README.md +++ b/README.md @@ -135,3 +135,10 @@ Instead of manually running a scan every time your library changes, you can run listen to any library change and update the database accordingly. The daemon is `bin/supysonic-watcher` and can be run as an *init.d* script. +Upgrading +--------- + +Some commits might introduce changes in the database schema. When that's the case migration scripts will +be provided in the *schema/migration* folder, prefixed by the date of commit that introduced the changes. +Those scripts shouldn't be used when initializing a new database, only when upgrading from a previous schema. + diff --git a/schema/migration/20161030.mysql.sql b/schema/migration/20161030.mysql.sql new file mode 100644 index 0000000..2fd7bf4 --- /dev/null +++ b/schema/migration/20161030.mysql.sql @@ -0,0 +1,9 @@ +START TRANSACTION; + +ALTER TABLE track ADD artist_id CHAR(36) AFTER album_id; +UPDATE track SET artist_id = (SELECT artist_id FROM album WHERE id = track.album_id); +ALTER TABLE track MODIFY artist_id CHAR(36) NOT NULL; +ALTER TABLE track ADD FOREIGN KEY (artist_id) REFERENCES artist; + +COMMIT; + diff --git a/schema/migration/20161030.postgresql.sql b/schema/migration/20161030.postgresql.sql new file mode 100644 index 0000000..34a7677 --- /dev/null +++ b/schema/migration/20161030.postgresql.sql @@ -0,0 +1,9 @@ +START TRANSACTION; + +ALTER TABLE track ADD artist_id UUID; +UPDATE track SET artist_id = (SELECT artist_id FROM album WHERE id = track.album_id); +ALTER TABLE track ALTER artist_id SET NOT NULL; +ALTER TABLE track ADD FOREIGN KEY (artist_id) REFERENCES artist; + +COMMIT; + diff --git a/schema/migration/20161030.sqlite.sql b/schema/migration/20161030.sqlite.sql new file mode 100755 index 0000000..134b112 --- /dev/null +++ b/schema/migration/20161030.sqlite.sql @@ -0,0 +1,35 @@ +-- PRAGMA foreign_keys = OFF; +BEGIN TRANSACTION; + +ALTER TABLE track RENAME TO track_old; +CREATE TABLE track ( + id CHAR(36) PRIMARY KEY, + disc INTEGER NOT NULL, + number INTEGER NOT NULL, + title VARCHAR(256) NOT NULL, + year INTEGER, + genre VARCHAR(256), + duration INTEGER NOT NULL, + album_id CHAR(36) NOT NULL REFERENCES album, + artist_id CHAR(36) NOT NULL REFERENCES artist, + bitrate INTEGER NOT NULL, + path VARCHAR(4096) NOT NULL, + content_type VARCHAR(32) NOT NULL, + created DATETIME NOT NULL, + last_modification INTEGER NOT NULL, + play_count INTEGER NOT NULL, + last_play DATETIME, + root_folder_id CHAR(36) NOT NULL REFERENCES folder, + folder_id CHAR(36) NOT NULL REFERENCES folder +); + +INSERT INTO track(id, disc, number, title, year, genre, duration, album_id, artist_id, bitrate, path, content_type, created, last_modification, play_count, last_play, root_folder_id, folder_id) +SELECT t.id, disc, number, title, year, genre, duration, album_id, artist_id, bitrate, path, content_type, created, last_modification, play_count, last_play, root_folder_id, folder_id +FROM track_old t, album a +WHERE album_id = a.id; + +DROP TABLE track_old; + +COMMIT; +-- PRAGMA foreign_keys = ON; + diff --git a/schema/mysql.sql b/schema/mysql.sql index 5d2ccd0..7d33311 100644 --- a/schema/mysql.sql +++ b/schema/mysql.sql @@ -29,6 +29,7 @@ CREATE TABLE track ( genre VARCHAR(256), duration INTEGER NOT NULL, album_id CHAR(36) NOT NULL REFERENCES album, + artist_id CHAR(36) NOT NULL REFERENCES artist, bitrate INTEGER NOT NULL, path VARCHAR(4096) NOT NULL, content_type VARCHAR(32) NOT NULL, diff --git a/schema/postgresql.sql b/schema/postgresql.sql index 1aad5e0..08e8f19 100644 --- a/schema/postgresql.sql +++ b/schema/postgresql.sql @@ -29,6 +29,7 @@ CREATE TABLE track ( genre VARCHAR(256), duration INTEGER NOT NULL, album_id UUID NOT NULL REFERENCES album, + artist_id UUID NOT NULL REFERENCES artist, bitrate INTEGER NOT NULL, path VARCHAR(4096) NOT NULL, content_type VARCHAR(32) NOT NULL, diff --git a/schema/sqlite.sql b/schema/sqlite.sql index f5e4cda..f6ded12 100644 --- a/schema/sqlite.sql +++ b/schema/sqlite.sql @@ -29,6 +29,7 @@ CREATE TABLE track ( genre VARCHAR(256), duration INTEGER NOT NULL, album_id CHAR(36) NOT NULL REFERENCES album, + artist_id CHAR(36) NOT NULL REFERENCES artist, bitrate INTEGER NOT NULL, path VARCHAR(4096) NOT NULL, content_type VARCHAR(32) NOT NULL, diff --git a/supysonic/db.py b/supysonic/db.py index d30f658..0f45882 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -151,6 +151,8 @@ class Track(object): duration = Int() album_id = UUID() album = Reference(album_id, Album.id) + artist_id = UUID() + artist = Reference(artist_id, Artist.id) bitrate = Int() path = Unicode() # unique @@ -173,7 +175,7 @@ class Track(object): 'isDir': False, 'title': self.title, 'album': self.album.name, - 'artist': self.album.artist.name, + 'artist': self.artist.name, 'track': self.number, 'size': os.path.getsize(self.path), 'contentType': self.content_type, @@ -185,7 +187,7 @@ class Track(object): 'discNumber': self.disc, 'created': self.created.isoformat(), 'albumId': str(self.album_id), - 'artistId': str(self.album.artist_id), + 'artistId': str(self.artist_id), 'type': 'music' } @@ -226,6 +228,7 @@ class Track(object): Folder.tracks = ReferenceSet(Folder.id, Track.folder_id) Album.tracks = ReferenceSet(Album.id, Track.album_id) +Artist.tracks = ReferenceSet(Artist.id, Track.artist_id) class User(object): __storm_table__ = 'user' diff --git a/supysonic/scanner.py b/supysonic/scanner.py index c184054..ec5a82a 100644 --- a/supysonic/scanner.py +++ b/supysonic/scanner.py @@ -107,7 +107,7 @@ class Scanner: self.__deleted_albums += 1 self.__albums_to_check.clear() - for artist in [ a for a in self.__artists_to_check if not a.albums.count() ]: + for artist in [ a for a in self.__artists_to_check if not a.albums.count() and not a.tracks.count() ]: self.__store.remove(artist) self.__deleted_artists += 1 self.__artists_to_check.clear() @@ -148,35 +148,45 @@ class Scanner: tr.path = path add = True + artist = self.__try_read_tag(tag, 'artist', '') + album = self.__try_read_tag(tag, 'album', '') + albumartist = self.__try_read_tag(tag, 'albumartist', artist) + tr.disc = self.__try_read_tag(tag, 'discnumber', 1, lambda x: int(x[0].split('/')[0])) tr.number = self.__try_read_tag(tag, 'tracknumber', 1, lambda x: int(x[0].split('/')[0])) tr.title = self.__try_read_tag(tag, 'title', '') tr.year = self.__try_read_tag(tag, 'date', None, lambda x: int(x[0].split('-')[0])) tr.genre = self.__try_read_tag(tag, 'genre') tr.duration = int(tag.info.length) - if not add: - old_album = tr.album - new_album = self.__find_album(self.__try_read_tag(tag, 'artist', ''), self.__try_read_tag(tag, 'album', '')) - if old_album.id != new_album.id: - tr.album = new_album - self.__albums_to_check.add(old_album) + tr.bitrate = (tag.info.bitrate if hasattr(tag.info, 'bitrate') else int(os.path.getsize(path) * 8 / tag.info.length)) / 1000 tr.content_type = get_mime(os.path.splitext(path)[1][1:]) tr.last_modification = os.path.getmtime(path) + tralbum = self.__find_album(albumartist, album) + trartist = self.__find_artist(artist) + if add: - tralbum = self.__find_album(self.__try_read_tag(tag, 'artist', ''), self.__try_read_tag(tag, 'album', '')) trroot = self.__find_root_folder(path) trfolder = self.__find_folder(path) # Set the references at the very last as searching for them will cause the added track to be flushed, even if # it is incomplete, causing not null constraints errors. tr.album = tralbum + tr.artist = trartist tr.folder = trfolder tr.root_folder = trroot self.__store.add(tr) self.__added_tracks += 1 + else: + if tr.album.id != tralbum.id: + self.__albums_to_check.add(tr.album) + tr.album = tralbum + + if tr.artist.id != trartist.id: + self.__artists_to_check.add(tr.artist) + tr.artist = trartist def remove_file(self, path): tr = self.__store.find(Track, Track.path == path).one() @@ -185,6 +195,7 @@ class Scanner: self.__folders_to_check.add(tr.folder) self.__albums_to_check.add(tr.album) + self.__artists_to_check.add(tr.artist) self.__store.remove(tr) self.__deleted_tracks += 1