1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-09-20 03:11:04 +00:00

Getting rid of SQLAlchemy, let's try Storm

This commit is contained in:
spl0k 2014-03-06 20:02:35 +01:00
parent 4ce3958cbb
commit cf40286838

357
db.py
View File

@ -18,124 +18,29 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import config from storm.properties import *
from storm.references import *
from sqlalchemy import create_engine, Table, Column, ForeignKey, func
from sqlalchemy import Integer, String, Boolean, DateTime
from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.types import TypeDecorator, BINARY
from sqlalchemy.dialects.postgresql import UUID as pgUUID
import uuid, datetime, time import uuid, datetime, time
import os.path import os.path
class UUID(TypeDecorator):
"""Platform-somewhat-independent UUID type
Uses Postgresql's UUID type, otherwise uses BINARY(16),
should be more efficient than a CHAR(32).
Mix of http://stackoverflow.com/a/812363
and http://www.sqlalchemy.org/docs/core/types.html#backend-agnostic-guid-type
"""
impl = BINARY
def load_dialect_impl(self, dialect):
if dialect.name == 'postgresql':
return dialect.type_descriptor(pgUUID())
else:
return dialect.type_descriptor(BINARY(16))
def process_bind_param(self, value, dialect):
if value and isinstance(value, uuid.UUID):
if dialect.name == 'postgresql':
return str(value)
return value.bytes
if value and not isinstance(value, uuid.UUID):
raise ValueError, 'value %s is not a valid uuid.UUID' % value
return None
def process_result_value(self, value, dialect):
if value:
if dialect.name == 'postgresql':
return uuid.UUID(value)
return uuid.UUID(bytes = value)
return None
def is_mutable(self):
return False
@staticmethod
def gen_id_column():
return Column(UUID, primary_key = True, default = uuid.uuid4)
def now(): def now():
return datetime.datetime.now().replace(microsecond = 0) return datetime.datetime.now().replace(microsecond = 0)
engine = create_engine(config.get('base', 'database_uri'), convert_unicode = True) class Folder(object):
session = scoped_session(sessionmaker(autocommit = False, autoflush = False, bind = engine)) __storm_table__ = 'folder'
Base = declarative_base() id = UUID(primary = True, default_factory = uuid.uuid4)
Base.query = session.query_property() root = Bool(default = False)
name = Unicode()
path = Unicode() # unique
created = DateTime(default_factory = now)
has_cover_art = Bool(default = False)
last_scan = Int(default = 0)
class User(Base): parent_id = UUID() # nullable
__tablename__ = 'user' parent = Reference(parent_id, id)
children = ReferenceSet(id, parent_id)
id = UUID.gen_id_column()
name = Column(String(64), unique = True)
mail = Column(String(255))
password = Column(String(40))
salt = Column(String(6))
admin = Column(Boolean, default = False)
lastfm_session = Column(String(32), nullable = True)
lastfm_status = Column(Boolean, default = True) # True: ok/unlinked, False: invalid session
last_play_id = Column(UUID, ForeignKey('track.id'), nullable = True)
last_play = relationship('Track')
last_play_date = Column(DateTime, nullable = True)
def as_subsonic_user(self):
return {
'username': self.name,
'email': self.mail,
'scrobblingEnabled': self.lastfm_session is not None and self.lastfm_status,
'adminRole': self.admin,
'settingsRole': True,
'downloadRole': True,
'uploadRole': False,
'playlistRole': True,
'coverArtRole': False,
'commentRole': False,
'podcastRole': False,
'streamRole': True,
'jukeboxRole': False,
'shareRole': False
}
class ClientPrefs(Base):
__tablename__ = 'client_prefs'
user_id = Column(UUID, ForeignKey('user.id'), primary_key = True)
client_name = Column(String(32), nullable = False, primary_key = True)
format = Column(String(8), nullable = True)
bitrate = Column(Integer, nullable = True)
class Folder(Base):
__tablename__ = 'folder'
id = UUID.gen_id_column()
root = Column(Boolean, default = False)
name = Column(String(255))
path = Column(String(4096)) # should be unique, but mysql don't like such large columns
created = Column(DateTime, default = now)
has_cover_art = Column(Boolean, default = False)
last_scan = Column(Integer, default = 0)
parent_id = Column(UUID, ForeignKey('folder.id'), nullable = True)
children = relationship('Folder', backref = backref('parent', remote_side = [ id ]))
def as_subsonic_child(self, user): def as_subsonic_child(self, user):
info = { info = {
@ -164,12 +69,11 @@ class Folder(Base):
return info return info
class Artist(Base): class Artist(object):
__tablename__ = 'artist' __storm_table__ = 'artist'
id = UUID.gen_id_column() id = UUID(primary = True, default_factory = uuid.uuid4)
name = Column(String(255), unique = True) name = Unicode() # unique
albums = relationship('Album', backref = 'artist')
def as_subsonic_artist(self, user): def as_subsonic_artist(self, user):
info = { info = {
@ -185,13 +89,13 @@ class Artist(Base):
return info return info
class Album(Base): class Album(object):
__tablename__ = 'album' __storm_table__ = 'album'
id = UUID.gen_id_column() id = UUID(primary = True, default_factory = uuid.uuid4)
name = Column(String(255)) name = Unicode()
artist_id = Column(UUID, ForeignKey('artist.id')) artist_id = UUID()
tracks = relationship('Track', backref = 'album') artist = Reference(artist_id, Artist.id)
def as_subsonic_album(self, user): def as_subsonic_album(self, user):
info = { info = {
@ -216,31 +120,34 @@ class Album(Base):
year = min(map(lambda t: t.year if t.year else 9999, self.tracks)) year = min(map(lambda t: t.year if t.year else 9999, self.tracks))
return '%i%s' % (year, self.name.lower()) return '%i%s' % (year, self.name.lower())
class Track(Base): Artist.albums = ReferenceSet(Artist.id, Album.artist_id)
__tablename__ = 'track'
id = UUID.gen_id_column() class Track(object):
disc = Column(Integer) __storm_table__ = 'track'
number = Column(Integer)
title = Column(String(255))
year = Column(Integer, nullable = True)
genre = Column(String(255), nullable = True)
duration = Column(Integer)
album_id = Column(UUID, ForeignKey('album.id'))
bitrate = Column(Integer)
path = Column(String(4096)) # should be unique, but mysql don't like such large columns id = UUID(primary = True, default_factory = uuid.uuid4)
content_type = Column(String(32)) disc = Int()
created = Column(DateTime, default = now) number = Int()
last_modification = Column(Integer) title = Unicode()
year = Int() # nullable
genre = Unicode() # nullable
duration = Int()
album_id = UUID()
album = Reference(album_id, Album.id)
bitrate = Int()
play_count = Column(Integer, default = 0) path = Unicode() # unique
last_play = Column(DateTime, nullable = True) content_type = Unicode()
created = DateTime(default_factory = now)
last_modification = Int()
root_folder_id = Column(UUID, ForeignKey('folder.id')) play_count = Int(default = 0)
root_folder = relationship('Folder', primaryjoin = Folder.id == root_folder_id) last_play = DateTime() # nullable
folder_id = Column(UUID, ForeignKey('folder.id'))
folder = relationship('Folder', primaryjoin = Folder.id == folder_id, backref = 'tracks') root_folder_id = UUID()
root_folder = Reference(root_folder_id, Folder.id)
folder_id = UUID()
folder = Reference(folder_id, Folder.id)
def as_subsonic_child(self, user): def as_subsonic_child(self, user):
info = { info = {
@ -300,75 +207,109 @@ class Track(Base):
def sort_key(self): def sort_key(self):
return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower() return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower()
class StarredFolder(Base): Folder.tracks = ReferenceSet(Folder.id, Track.folder_id)
__tablename__ = 'starred_folder' Album.tracks = ReferenceSet(Album.id, Track.album_id)
user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) class User(object):
starred_id = Column(UUID, ForeignKey('folder.id'), primary_key = True) __storm_table__ = 'user'
date = Column(DateTime, default = now)
user = relationship('User') id = UUID(primary = True, default_factory = uuid.uuid4)
starred = relationship('Folder') name = Unicode() # unique
mail = Unicode()
password = Unicode()
salt = Unicode()
admin = Bool(default = False)
lastfm_session = Unicode() # nullable
lastfm_status = Bool(default = True) # True: ok/unlinked, False: invalid session
class StarredArtist(Base): last_play_id = UUID() # nullable
__tablename__ = 'starred_artist' last_play = Reference(last_play_id, Track.id)
last_play_date = DateTime() # nullable
user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) def as_subsonic_user(self):
starred_id = Column(UUID, ForeignKey('artist.id'), primary_key = True) return {
date = Column(DateTime, default = now) 'username': self.name,
'email': self.mail,
'scrobblingEnabled': self.lastfm_session is not None and self.lastfm_status,
'adminRole': self.admin,
'settingsRole': True,
'downloadRole': True,
'uploadRole': False,
'playlistRole': True,
'coverArtRole': False,
'commentRole': False,
'podcastRole': False,
'streamRole': True,
'jukeboxRole': False,
'shareRole': False
}
user = relationship('User') class ClientPrefs(object):
starred = relationship('Artist') __storm_table__ = 'client_prefs'
__storm_primary__ = 'user_id, client_name'
class StarredAlbum(Base): user_id = UUID()
__tablename__ = 'starred_album' client_name = Unicode()
format = Unicode() # nullable
bitrate = Int() # nullable
user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) class BaseStarred(object):
starred_id = Column(UUID, ForeignKey('album.id'), primary_key = True) __storm_primary__ = 'user_id, starred_id'
date = Column(DateTime, default = now)
user = relationship('User') user_id = UUID()
starred = relationship('Album') starred_id = UUID()
date = DateTime(default_factory = now)
class StarredTrack(Base): user = Reference(user_id, User.id)
__tablename__ = 'starred_track'
user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) class StarredFolder(BaseStarred):
starred_id = Column(UUID, ForeignKey('track.id'), primary_key = True) __storm_table__ = 'starred_folder'
date = Column(DateTime, default = now)
user = relationship('User') starred = Reference(BaseStarred.starred_id, Folder.id)
starred = relationship('Track')
class RatingFolder(Base): class StarredArtist(BaseStarred):
__tablename__ = 'rating_folder' __storm_table__ = 'starred_artist'
user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) starred = Reference(BaseStarred.starred_id, Artist.id)
rated_id = Column(UUID, ForeignKey('folder.id'), primary_key = True)
rating = Column(Integer)
user = relationship('User') class StarredAlbum(BaseStarred):
rated = relationship('Folder') __storm_table__ = 'starred_album'
class RatingTrack(Base): starred = Reference(BaseStarred.starred_id, Album.id)
__tablename__ = 'rating_track'
user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) class StarredTrack(BaseStarred):
rated_id = Column(UUID, ForeignKey('track.id'), primary_key = True) __storm_table__ = 'starred_track'
rating = Column(Integer)
user = relationship('User') starred = Reference(BaseStarred.starred_id, Track.id)
rated = relationship('Track')
class ChatMessage(Base): class BaseRating(object):
__tablename__ = 'chat_message' __storm_primary__ = 'user_id, rated_id'
id = UUID.gen_id_column() user_id = UUID()
user_id = Column(UUID, ForeignKey('user.id')) rated_id = UUID()
time = Column(Integer, default = lambda: int(time.time())) rating = Int()
message = Column(String(512))
user = relationship('User') user = Reference(user_id, User.id)
class RatingFolder(BaseRating):
__storm_table__ = 'rating_folder'
rated = Reference(BaseRating.rated_id, Folder.id)
class RatingTrack(BaseRating):
__storm_table__ = 'rating_track'
rated = Reference(BaseRating.rated_id, Track.id)
class ChatMessage(object):
__storm_table__ = 'chat_message'
id = UUID(primary = True, default_factory = uuid.uuid4)
user_id = UUID()
time = Int(default_factory = lambda: int(time.time()))
message = Unicode()
user = Reference(user_id, User.id)
def responsize(self): def responsize(self):
return { return {
@ -377,23 +318,17 @@ class ChatMessage(Base):
'message': self.message 'message': self.message
} }
playlist_track_assoc = Table('playlist_track', Base.metadata, class Playlist(object):
Column('playlist_id', UUID, ForeignKey('playlist.id')), __storm_table__ = 'playlist'
Column('track_id', UUID, ForeignKey('track.id'))
)
class Playlist(Base): id = UUID(primary = True, default_factory = uuid.uuid4)
__tablename__ = 'playlist' user_id = UUID()
name = Unicode()
comment = Unicode() # nullable
public = Bool(default = False)
created = DateTime(default_factory = now)
id = UUID.gen_id_column() user = Reference(user_id, User.id)
user_id = Column(UUID, ForeignKey('user.id'))
name = Column(String(255))
comment = Column(String(255), nullable = True)
public = Column(Boolean, default = False)
created = Column(DateTime, default = now)
user = relationship('User')
tracks = relationship('Track', secondary = playlist_track_assoc)
def as_subsonic_playlist(self, user): def as_subsonic_playlist(self, user):
info = { info = {
@ -409,10 +344,12 @@ class Playlist(Base):
info['comment'] = self.comment info['comment'] = self.comment
return info return info
def init_db(): class PlaylistTrack(object):
Base.metadata.create_all(bind = engine) __storm_table__ = 'playlist_track'
__storm_primary__ = 'playlist_id, track_id'
def recreate_db(): playlist_id = UUID()
Base.metadata.drop_all(bind = engine) track_id = UUID()
Base.metadata.create_all(bind = engine)
Playlist.tracks = ReferenceSet(Playlist.id, PlaylistTrack.playlist_id, PlaylistTrack.track_id, Track.id)