add support for editing playlist entries

This commit is contained in:
evilchili 2022-12-07 17:05:44 -08:00
parent 3fdd3ee9a5
commit 27e8ee48eb
2 changed files with 77 additions and 39 deletions

View File

@ -17,7 +17,15 @@ EDITOR_TEMPLATE = """
# Groove On Demand Playlist Editor # Groove On Demand Playlist Editor
# #
# This file is in YAML format. Blank lines and lines beginning with # are # This file is in YAML format. Blank lines and lines beginning with # are
# ignored. Here's a complete example: # ignored, and the following structure is expected:
#
# PLAYLIST_TITLE:
# description: STRING_DESCRIPTION
# entries:
# - TRACK_ARTIST - TRACK_TITLE
# ...
#
# Here's a complete example, with a multi-line description:
# #
# My Awesome Jams, Vol. 2: # My Awesome Jams, Vol. 2:
# description: | # description: |
@ -26,9 +34,15 @@ EDITOR_TEMPLATE = """
# #
# yo. # yo.
# entries: # entries:
# - Beastie Boys - Help Me, Ronda # - Beastie Boys: Help Me, Rhonda
# - Bob and Doug McKenzie - Messiah (Hallelujah Eh) # - Bob and Doug McKenzie: Messiah (Hallelujah Eh)
# #
# Tracks can be reordered or removed. You can also add a track, if the artist/title
# combination exactly matches precisely one Track entry your database. Searches are
# case-sensitive.
#
# Playlists can be renamed and descriptions can be updated or removed. If you rename a
# playlist, its slug will be regnenerated, breaking previous web links to said playlist.
""" """

View File

@ -28,7 +28,7 @@ class Playlist:
self._slug = slug self._slug = slug
self._name = name self._name = name
self._description = description self._description = description
self._entries = None self._entries = []
self._record = None self._record = None
self._create_ok = create_ok self._create_ok = create_ok
self._deleted = False self._deleted = False
@ -62,12 +62,13 @@ class Playlist:
return self._description return self._description
@property @property
def summary(self): def info(self):
return ' :: '.join([ count = len(self.entries)
f"[ {self.record.id} ]", return f"{self.name}: {self.url} [{count} tracks]\n{self.description}\n"
self.record.name,
f"http://{os.environ['HOST']}/{self.slug}", @property
]) def url(self) -> str:
return f"http://{os.environ['HOST']}:{os.environ['PORT']}/{self.slug}"
@property @property
def slug(self) -> str: def slug(self) -> str:
@ -83,28 +84,29 @@ class Playlist:
Cache the playlist row from the database and return it. Optionally create it if it doesn't exist. Cache the playlist row from the database and return it. Optionally create it if it doesn't exist.
""" """
if not self._record: if not self._record:
self._record = self.get_or_create() self.get_or_create()
return self._record return self._record
@property @property
def entries(self) -> Union[List, None]: def entries(self) -> List:
""" """
Cache the list of entries on this playlist and return it. Cache the list of entries on this playlist and return it.
""" """
if self.record and not self._entries: if not self._entries:
query = self.session.query( if self.record:
db.entry, query = self.session.query(
db.track db.entry,
).filter( db.track
(db.playlist.c.id == self.record.id) ).filter(
).filter( (db.playlist.c.id == self.record.id)
db.entry.c.playlist_id == db.playlist.c.id ).filter(
).filter( db.entry.c.playlist_id == db.playlist.c.id
db.entry.c.track_id == db.track.c.id ).filter(
).order_by( db.entry.c.track_id == db.track.c.id
db.entry.c.track ).order_by(
) db.entry.c.track
self._entries = query.all() )
self._entries = query.all()
return self._entries return self._entries
@property @property
@ -120,11 +122,9 @@ class Playlist:
@property @property
def as_string(self) -> str: def as_string(self) -> str:
if not self.exists: text = self.info
return '' for (tracknum, entry) in enumerate(self.entries):
text = f"{self.summary}\n" text += f" - {tracknum+1} {entry.artist} - {entry.title}\n"
for entry in self.entries:
text += f" - {entry.track} {entry.artist} - {entry.title}\n"
return text return text
@property @property
@ -132,7 +132,7 @@ class Playlist:
template_vars = self.as_dict template_vars = self.as_dict
template_vars['entries'] = '' template_vars['entries'] = ''
for entry in self.entries: for entry in self.entries:
template_vars['entries'] += f" - {entry.artist} - {entry.title}\n" template_vars['entries'] += f' - "{entry.artist}": "{entry.title}"\n'
return EDITOR_TEMPLATE.format(**template_vars) return EDITOR_TEMPLATE.format(**template_vars)
def _get_tracks_by_path(self, paths: List[str]) -> List: def _get_tracks_by_path(self, paths: List[str]) -> List:
@ -142,6 +142,14 @@ class Playlist:
""" """
return [self.session.query(db.track).filter(db.track.c.relpath.ilike(f"%{path}%")).one() for path in paths] return [self.session.query(db.track).filter(db.track.c.relpath.ilike(f"%{path}%")).one() for path in paths]
def _get_tracks_by_artist_and_title(self, entries: List[tuple]) -> List:
return [
self.session.query(db.track).filter(
db.track.c.artist == artist, db.track.c.title == title
).one()
for (artist, title) in entries
]
def edit(self): def edit(self):
edits = self.editor.edit(self) edits = self.editor.edit(self)
if not edits: if not edits:
@ -154,7 +162,8 @@ class Playlist:
self._slug = new.slug self._slug = new.slug
self._name = new.name.strip() self._name = new.name.strip()
self._description = new.description.strip() self._description = new.description.strip()
self._record = self.save() self._entries = new._entries
self.save()
def add(self, paths: List[str]) -> int: def add(self, paths: List[str]) -> int:
""" """
@ -198,13 +207,14 @@ class Playlist:
def get_or_create(self, create_ok: bool = False) -> Row: def get_or_create(self, create_ok: bool = False) -> Row:
try: try:
return self._get() self._record = self._get()
return
except NoResultFound: except NoResultFound:
logging.debug(f"Could not find a playlist with slug {self.slug}.") logging.debug(f"Could not find a playlist with slug {self.slug}.")
if self.deleted: if self.deleted:
raise RuntimeError("Object has been deleted.") raise RuntimeError("Object has been deleted.")
if self._create_ok or create_ok: if self._create_ok or create_ok:
return self.save() self.save()
def _get(self): def _get(self):
return self.session.query(db.playlist).filter( return self.session.query(db.playlist).filter(
@ -237,9 +247,16 @@ class Playlist:
'description': self.description 'description': self.description
} }
logging.debug(f"Saving values: {values}") logging.debug(f"Saving values: {values}")
obj = self._update(values) if self._record else self._insert(values) self._record = self._update(values) if self._record else self._insert(values)
logging.debug(f"Saved playlist {obj.id} with slug {obj.slug}") logging.debug(f"Saved playlist {self._record.id} with slug {self._record.slug}")
return obj self.save_entries()
def save_entries(self):
plid = self.record.id
stmt = delete(db.entry).where(db.entry.c.playlist_id == plid)
logging.debug(f"Deleting entries associated with playlist {plid}: {stmt}")
self.session.execute(stmt)
return self.create_entries(self.entries)
def load(self): def load(self):
self.get_or_create(create_ok=False) self.get_or_create(create_ok=False)
@ -282,7 +299,7 @@ class Playlist:
try: try:
name = list(source.keys())[0].strip() name = list(source.keys())[0].strip()
description = (source[name]['description'] or '').strip() description = (source[name]['description'] or '').strip()
return Playlist( pl = Playlist(
slug=slugify(name), slug=slugify(name),
name=name, name=name,
description=description, description=description,
@ -291,7 +308,14 @@ class Playlist:
except (IndexError, KeyError): except (IndexError, KeyError):
PlaylistImportError("The specified source was not a valid playlist.") PlaylistImportError("The specified source was not a valid playlist.")
pl._entries = pl._get_tracks_by_artist_and_title(entries=[
list(entry.items())[0] for entry in source[name]['entries']
])
return pl
def __eq__(self, obj): def __eq__(self, obj):
logging.debug(f"Comparing obj to self:\n{obj.as_string}\n--\n{self.as_string}")
return obj.as_string == self.as_string
for key in ('slug', 'name', 'description'): for key in ('slug', 'name', 'description'):
if getattr(obj, key) != getattr(self, key): if getattr(obj, key) != getattr(self, key):
logging.debug(f"{key}: {getattr(obj, key)} != {getattr(self, key)}") logging.debug(f"{key}: {getattr(obj, key)} != {getattr(self, key)}")