Handle duplicates in playlist
This commit is contained in:
2
setup.py
2
setup.py
@@ -56,7 +56,7 @@ setup(
|
|||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'bottle',
|
'bottle',
|
||||||
'dataclasses-json @ git+https://github.com/sumnerevans/dataclasses-json@cc2eaeb#egg=dataclasses-json',
|
'dataclasses-json @ git+https://github.com/sumnerevans/dataclasses-json@cc2eaeb#egg=dataclasses-json', # noqa: E501
|
||||||
'deepdiff',
|
'deepdiff',
|
||||||
'Deprecated',
|
'Deprecated',
|
||||||
'fuzzywuzzy',
|
'fuzzywuzzy',
|
||||||
|
@@ -199,6 +199,9 @@ class Adapter(abc.ABC):
|
|||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# TODO some way of specifying what types of schemas can be provided (for
|
||||||
|
# example, http, https, file)
|
||||||
|
|
||||||
# Data Retrieval Methods
|
# Data Retrieval Methods
|
||||||
# These properties determine if what things the adapter can be used to do
|
# These properties determine if what things the adapter can be used to do
|
||||||
# at the current moment.
|
# at the current moment.
|
||||||
|
@@ -138,7 +138,7 @@ class SortedManyToManyField(ManyToManyField):
|
|||||||
database = self.model._meta.database
|
database = self.model._meta.database
|
||||||
schema = self.model._meta.schema
|
schema = self.model._meta.schema
|
||||||
table_name = '{}_{}_through'.format(*tables)
|
table_name = '{}_{}_through'.format(*tables)
|
||||||
indexes = (((lhs._meta.name, rhs._meta.name), True), )
|
indexes = (((lhs._meta.name, rhs._meta.name, 'position'), True), )
|
||||||
|
|
||||||
params = {'on_delete': self._on_delete, 'on_update': self._on_update}
|
params = {'on_delete': self._on_delete, 'on_update': self._on_update}
|
||||||
attrs = {
|
attrs = {
|
||||||
|
@@ -28,11 +28,12 @@ except Exception:
|
|||||||
glib_notify_exists = False
|
glib_notify_exists = False
|
||||||
|
|
||||||
from .adapters import AdapterManager
|
from .adapters import AdapterManager
|
||||||
|
from .adapters.api_objects import Playlist
|
||||||
from .cache_manager import CacheManager
|
from .cache_manager import CacheManager
|
||||||
from .config import AppConfiguration, ReplayGainType
|
from .config import AppConfiguration, ReplayGainType
|
||||||
from .dbus_manager import dbus_propagate, DBusManager
|
from .dbus_manager import dbus_propagate, DBusManager
|
||||||
from .players import ChromecastPlayer, MPVPlayer, PlayerEvent
|
from .players import ChromecastPlayer, MPVPlayer, PlayerEvent
|
||||||
from .server.api_objects import Child, Directory, Playlist
|
from .server.api_objects import Child, Directory
|
||||||
from .ui.configure_servers import ConfigureServersDialog
|
from .ui.configure_servers import ConfigureServersDialog
|
||||||
from .ui.main import MainWindow
|
from .ui.main import MainWindow
|
||||||
from .ui.settings import SettingsDialog
|
from .ui.settings import SettingsDialog
|
||||||
@@ -288,7 +289,7 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
if not self.dbus_manager:
|
if not self.dbus_manager:
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(track_ids):
|
if len(track_ids) == 0:
|
||||||
# We are lucky, just return an empty list.
|
# We are lucky, just return an empty list.
|
||||||
return GLib.Variant('(aa{sv})', ([], ))
|
return GLib.Variant('(aa{sv})', ([], ))
|
||||||
|
|
||||||
@@ -302,8 +303,10 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Get rid of all of the tracks that were not requested.
|
# Get rid of all of the tracks that were not requested.
|
||||||
metadatas = filter(
|
metadatas = list(
|
||||||
lambda m: m['mpris:trackid'] in track_ids, metadatas)
|
filter(lambda m: m['mpris:trackid'] in track_ids, metadatas))
|
||||||
|
|
||||||
|
assert len(metadatas) == len(track_ids)
|
||||||
|
|
||||||
# Sort them so they get returned in the same order as they were
|
# Sort them so they get returned in the same order as they were
|
||||||
# requested.
|
# requested.
|
||||||
@@ -322,17 +325,18 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
|
|
||||||
def activate_playlist(playlist_id: str):
|
def activate_playlist(playlist_id: str):
|
||||||
playlist_id = playlist_id.split('/')[-1]
|
playlist_id = playlist_id.split('/')[-1]
|
||||||
playlist = CacheManager.get_playlist(playlist_id).result()
|
playlist = AdapterManager.get_playlist_details(
|
||||||
|
playlist_id).result()
|
||||||
|
|
||||||
# Calculate the song id to play.
|
# Calculate the song id to play.
|
||||||
song_idx = 0
|
song_idx = 0
|
||||||
if self.app_config.state.shuffle_on:
|
if self.app_config.state.shuffle_on:
|
||||||
song_idx = random.randint(0, len(playlist.entry) - 1)
|
song_idx = random.randint(0, len(playlist.songs) - 1)
|
||||||
|
|
||||||
self.on_song_clicked(
|
self.on_song_clicked(
|
||||||
None,
|
None,
|
||||||
song_idx,
|
song_idx,
|
||||||
[s.id for s in playlist.entry],
|
[s.id for s in playlist.songs],
|
||||||
{'active_playlist_id': playlist_id},
|
{'active_playlist_id': playlist_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -342,13 +346,13 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
order: str,
|
order: str,
|
||||||
reverse_order: bool,
|
reverse_order: bool,
|
||||||
) -> GLib.Variant:
|
) -> GLib.Variant:
|
||||||
playlists_result = CacheManager.get_playlists()
|
playlists_result = AdapterManager.get_playlists()
|
||||||
if playlists_result.is_future:
|
if not playlists_result.data_is_available:
|
||||||
# We don't want to wait for the response in this case, so just
|
# We don't want to wait for the response in this case, so just
|
||||||
# return an empty array.
|
# return an empty array.
|
||||||
return GLib.Variant('(a(oss))', ([], ))
|
return GLib.Variant('(a(oss))', ([], ))
|
||||||
|
|
||||||
playlists = playlists_result.result()
|
playlists = list(playlists_result.result())
|
||||||
|
|
||||||
sorters = {
|
sorters = {
|
||||||
'Alphabetical': lambda p: p.name,
|
'Alphabetical': lambda p: p.name,
|
||||||
@@ -362,7 +366,7 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
|
|
||||||
def make_playlist_tuple(p: Playlist) -> GLib.Variant:
|
def make_playlist_tuple(p: Playlist) -> GLib.Variant:
|
||||||
cover_art_filename = CacheManager.get_cover_art_filename(
|
cover_art_filename = CacheManager.get_cover_art_filename(
|
||||||
p.coverArt,
|
p.cover_art,
|
||||||
allow_download=False,
|
allow_download=False,
|
||||||
).result()
|
).result()
|
||||||
return (f'/playlist/{p.id}', p.name, cover_art_filename or '')
|
return (f'/playlist/{p.id}', p.name, cover_art_filename or '')
|
||||||
@@ -400,6 +404,7 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
'org.mpris.MediaPlayer2.TrackList': {
|
'org.mpris.MediaPlayer2.TrackList': {
|
||||||
'GoTo': set_pos_fn,
|
'GoTo': set_pos_fn,
|
||||||
'GetTracksMetadata': get_tracks_metadata,
|
'GetTracksMetadata': get_tracks_metadata,
|
||||||
|
# 'RemoveTrack': remove_track,
|
||||||
},
|
},
|
||||||
'org.mpris.MediaPlayer2.Playlists': {
|
'org.mpris.MediaPlayer2.Playlists': {
|
||||||
'ActivatePlaylist': activate_playlist,
|
'ActivatePlaylist': activate_playlist,
|
||||||
|
@@ -140,6 +140,15 @@ def test_caching_get_playlist_details(cache_adapter: FilesystemAdapter):
|
|||||||
duration=timedelta(seconds=21.8),
|
duration=timedelta(seconds=21.8),
|
||||||
path='/foo/song1.mp3',
|
path='/foo/song1.mp3',
|
||||||
),
|
),
|
||||||
|
SubsonicAPI.Song(
|
||||||
|
'1',
|
||||||
|
'Song 1',
|
||||||
|
parent='foo',
|
||||||
|
album='foo',
|
||||||
|
artist='foo',
|
||||||
|
duration=timedelta(seconds=21.8),
|
||||||
|
path='/foo/song1.mp3',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
cache_adapter.ingest_new_data(
|
cache_adapter.ingest_new_data(
|
||||||
FilesystemAdapter.FunctionNames.GET_PLAYLIST_DETAILS,
|
FilesystemAdapter.FunctionNames.GET_PLAYLIST_DETAILS,
|
||||||
@@ -150,8 +159,8 @@ def test_caching_get_playlist_details(cache_adapter: FilesystemAdapter):
|
|||||||
playlist = cache_adapter.get_playlist_details('1')
|
playlist = cache_adapter.get_playlist_details('1')
|
||||||
assert playlist.id == '1'
|
assert playlist.id == '1'
|
||||||
assert playlist.name == 'foo'
|
assert playlist.name == 'foo'
|
||||||
assert playlist.song_count == 2
|
assert playlist.song_count == 3
|
||||||
assert playlist.duration == timedelta(seconds=32)
|
assert playlist.duration == timedelta(seconds=53.8)
|
||||||
for actual, song in zip(playlist.songs, songs):
|
for actual, song in zip(playlist.songs, songs):
|
||||||
for k, v in asdict(song).items():
|
for k, v in asdict(song).items():
|
||||||
assert getattr(actual, k, None) == v
|
assert getattr(actual, k, None) == v
|
||||||
|
Reference in New Issue
Block a user