mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 17:06:17 +00:00
Getting rid of SQLAlchemy, let's try Storm
This commit is contained in:
parent
4ce3958cbb
commit
cf40286838
357
db.py
357
db.py
@ -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)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user