Got streaming working; converted to use GET requests

This commit is contained in:
Sumner Evans
2019-08-02 16:53:00 -06:00
parent a04fe75738
commit 9fb625a37f
5 changed files with 151 additions and 165 deletions

View File

@@ -336,44 +336,42 @@ class LibremsonicApp(Gtk.Application):
# Do this the old fashioned way so that we can have access to ``reset``
# in the callback.
def do_play_song(song: Child):
# Do this the old fashioned way so that we can have access to
# ``song`` in the callback.
def filename_future_done(song_file):
self.state.current_song = song
self.state.playing = True
self.update_window()
uri, stream = CacheManager.get_song_filename_or_stream(song)
# Prevent it from doing the thing where it continually loads
# songs when it has to download.
if reset:
self.had_progress_value = False
self.state.song_progress = 0
self.state.current_song = song
self.state.playing = True
self.update_window()
self.player.command('loadfile', song_file, 'replace',
f'start={self.state.song_progress}')
# TODO refactor to use record-file if necessary. This will
# require changes in the way that the get song filename future
# works.
self.player.pause = False
# Prevent it from doing the thing where it continually loads
# songs when it has to download.
if reset:
self.had_progress_value = False
self.state.song_progress = 0
if old_play_queue:
self.state.old_play_queue = old_play_queue
# If streaming, also download the song.
# TODO: make it configurable if we do this download
if stream:
CacheManager.batch_download_songs(
[song.id],
before_download=lambda: self.update_window(),
on_song_download_complete=lambda _: self.update_window(),
)
if play_queue:
self.state.play_queue = play_queue
self.save_play_queue()
self.player.command(
'loadfile',
uri,
'replace',
f'start={self.state.song_progress}',
)
def before_song_download():
# Pause the currently playing song. This prevents anything from
# playing while a download occurs.
self.player.pause = True
self.state.playing = False
self.update_window()
self.player.pause = False
song_filename_future = CacheManager.get_song_filename(
song, before_download=before_song_download)
song_filename_future.add_done_callback(
lambda f: GLib.idle_add(filename_future_done, f.result()), )
if old_play_queue:
self.state.old_play_queue = old_play_queue
if play_queue:
self.state.play_queue = play_queue
self.save_play_queue()
song_details_future = CacheManager.get_song_details(song)
song_details_future.add_done_callback(

View File

@@ -4,13 +4,22 @@ import threading
import shutil
import json
import hashlib
from collections import defaultdict
from collections import defaultdict, namedtuple
from concurrent.futures import ThreadPoolExecutor, Future
from enum import EnumMeta, Enum
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Union, Callable, Set, DefaultDict
from typing import (
Any,
List,
Optional,
Union,
Callable,
Set,
DefaultDict,
Tuple,
)
import requests
@@ -85,6 +94,7 @@ class CacheManager(metaclass=Singleton):
server: Server
browse_by_tags: bool
download_move_lock = threading.Lock()
download_set_lock = threading.Lock()
current_downloads: Set[Path] = set()
@@ -129,7 +139,6 @@ class CacheManager(metaclass=Singleton):
# Non-ID3 caches
('albums', Child, list),
('album_details', Child, dict),
('artists', Artist, list),
('artist_details', Directory, dict),
('artist_infos', ArtistInfo, dict),
@@ -137,7 +146,6 @@ class CacheManager(metaclass=Singleton):
# ID3 caches
('albums_id3', AlbumWithSongsID3, list),
('album_details_id3', AlbumWithSongsID3, dict),
('artists_id3', ArtistID3, list),
('artist_details_id3', ArtistWithAlbumsID3, dict),
('artist_infos_id3', ArtistInfo2, dict),
@@ -183,7 +191,7 @@ class CacheManager(metaclass=Singleton):
return Path(xdg_cache_home).joinpath('libremsonic',
*relative_paths)
def return_cache_or_download(
def return_cached_or_download(
self,
relative_path: Union[Path, str],
download_fn: Callable[[], bytes],
@@ -202,16 +210,20 @@ class CacheManager(metaclass=Singleton):
before_download()
self.save_file(download_path, download_fn())
# Move the file to its cache download location.
os.makedirs(abs_path.parent, exist_ok=True)
shutil.move(download_path, abs_path)
# Move the file to its cache download location. We need a lock
# here just in case we fired two downloads of the same asset
# for some reason.
with self.download_move_lock:
os.makedirs(abs_path.parent, exist_ok=True)
if download_path.exists():
shutil.move(download_path, abs_path)
with self.download_set_lock:
self.current_downloads.discard(abs_path)
return str(abs_path)
def delete_cache(self, relative_path: Union[Path, str]):
def delete_cached(self, relative_path: Union[Path, str]):
"""
:param relative_path: The path to the cached element to delete.
Note that this can be a globed path.
@@ -262,7 +274,7 @@ class CacheManager(metaclass=Singleton):
# Invalidate the cached photo if we are forcing a retrieval
# from the server.
if force:
self.delete_cache(
self.delete_cached(
f'cover_art/{playlist_details.coverArt}_*')
return playlist_details
@@ -371,7 +383,7 @@ class CacheManager(metaclass=Singleton):
artist.child[0].coverArt, size=300).result()
url_hash = hashlib.md5(lastfm_url.encode('utf-8')).hexdigest()
return self.return_cache_or_download(
return self.return_cached_or_download(
f'cover_art/artist.{url_hash}',
lambda: requests.get(lastfm_url).content,
before_download=before_download,
@@ -437,21 +449,21 @@ class CacheManager(metaclass=Singleton):
def do_download_song(song_id):
# Do the actual download.
song_details_future = CacheManager.get_song_details(song_id)
song_filename_future = CacheManager.get_song_filename(
song_details_future.result(),
song = song_details_future.result()
self.return_cached_or_download(
song.path,
lambda: self.server.download(song.id),
before_download=before_download,
)
def filename_future_done(f):
self.download_limiter_semaphore.release()
on_song_download_complete(song_id)
song_filename_future.add_done_callback(filename_future_done)
self.download_limiter_semaphore.release()
on_song_download_complete(song_id)
def do_batch_download_songs():
self.current_downloads = self.current_downloads.union(
set(song_ids))
for song_id in song_ids:
print(song_id)
self.download_limiter_semaphore.acquire()
CacheManager.executor.submit(do_download_song, song_id)
@@ -465,7 +477,7 @@ class CacheManager(metaclass=Singleton):
force: bool = False,
) -> Future:
def do_get_cover_art_filename() -> str:
return self.return_cache_or_download(
return self.return_cached_or_download(
f'cover_art/{id}_{size}',
lambda: self.server.get_cover_art(id, str(size)),
before_download=before_download,
@@ -492,22 +504,15 @@ class CacheManager(metaclass=Singleton):
return CacheManager.executor.submit(do_get_song_details)
def get_song_filename(
def get_song_filename_or_stream(
self,
song: Child,
before_download: Callable[[], None] = lambda: None,
force: bool = False,
) -> Future:
def do_get_song_filename() -> str:
song_filename = self.return_cache_or_download(
song.path,
lambda: self.server.download(song.id),
before_download=before_download,
force=force,
)
return song_filename
) -> Tuple[str, bool]:
abs_path = self.calculate_abs_path(song.path)
if not abs_path.exists():
return (self.server.get_stream_url(song.id), True)
return CacheManager.executor.submit(do_get_song_filename)
return (str(abs_path), False)
def get_cached_status(self, song: Child) -> SongCacheStatus:
cache_path = self.calculate_abs_path(song.path)

View File

@@ -1,4 +1,5 @@
import math
from urllib.parse import urlencode
from time import sleep
from deprecated import deprecated
from typing import Any, Optional, Dict, List, Union, Iterator, cast
@@ -77,7 +78,7 @@ class Server:
def _subsonic_error_to_exception(self, error) -> Exception:
return Exception(f'{error.code}: {error.message}')
def _post(self, url, **params):
def _get(self, url, **params):
params = {**self._get_params(), **params}
print(f'[START] post: {url}')
@@ -86,15 +87,16 @@ class Server:
if type(v) == datetime:
params[k] = int(cast(datetime, v).timestamp() * 1000)
result = requests.post(url, data=params)
result = requests.get(url, params=params)
print(result.url)
# TODO make better
if result.status_code != 200:
raise Exception(f'Fail! {result.status_code}')
print(f'[FINISH] post: {url}')
print(f'[FINISH] post: {result.url}')
return result
def _post_json(
def _get_json(
self,
url: str,
**params: Union[None, str, datetime, int, List[int]],
@@ -107,7 +109,7 @@ class Server:
response, deserialized
:raises Exception: needs some work TODO
"""
result = self._post(url, **params)
result = self._get(url, **params)
subsonic_response = result.json()['subsonic-response']
# TODO make better
@@ -126,48 +128,28 @@ class Server:
return response
def _stream(self, url, **params) -> Iterator[Any]:
"""
Stream a file.
"""
result = self._post(url, **params)
content_type = result.headers.get('Content-Type', '')
if 'application/json' in content_type:
# Error occurred
subsonic_response = result.json()['subsonic-response']
# TODO make better
if not subsonic_response:
raise Exception('Fail!')
response = Response.from_json(subsonic_response)
raise self._subsonic_error_to_exception(response.error)
else:
return result.iter_content(chunk_size=1024)
def do_download(self, url, **params) -> bytes:
print('download', url)
return self._post(url, **params).content
return self._get(url, **params).content
def ping(self) -> Response:
"""
Used to test connectivity with the server.
"""
return self._post_json(self._make_url('ping'))
return self._get_json(self._make_url('ping'))
def get_license(self) -> License:
"""
Get details about the software license.
"""
result = self._post_json(self._make_url('getLicense'))
result = self._get_json(self._make_url('getLicense'))
return result.license
def get_music_folders(self) -> MusicFolders:
"""
Returns all configured top-level music folders.
"""
result = self._post_json(self._make_url('getMusicFolders'))
result = self._get_json(self._make_url('getMusicFolders'))
return result.musicFolders
def get_indexes(
@@ -183,9 +165,9 @@ class Server:
:param if_modified_since: If specified, only return a result if the
artist collection has changed since the given time.
"""
result = self._post_json(self._make_url('getIndexes'),
musicFolderId=music_folder_id,
ifModifiedSince=if_modified_since)
result = self._get_json(self._make_url('getIndexes'),
musicFolderId=music_folder_id,
ifModifiedSince=if_modified_since)
return result.indexes
def get_music_directory(self, dir_id) -> Directory:
@@ -196,15 +178,15 @@ class Server:
:param dir_id: A string which uniquely identifies the music folder.
Obtained by calls to ``getIndexes`` or ``getMusicDirectory``.
"""
result = self._post_json(self._make_url('getMusicDirectory'),
id=str(dir_id))
result = self._get_json(self._make_url('getMusicDirectory'),
id=str(dir_id))
return result.directory
def get_genres(self) -> Genres:
"""
Returns all genres.
"""
result = self._post_json(self._make_url('getGenres'))
result = self._get_json(self._make_url('getGenres'))
return result.genres
def get_artists(self, music_folder_id: int = None) -> ArtistsID3:
@@ -214,8 +196,8 @@ class Server:
:param music_folder_id: If specified, only return artists in the music
folder with the given ID. See ``getMusicFolders``.
"""
result = self._post_json(self._make_url('getArtists'),
musicFolderId=music_folder_id)
result = self._get_json(self._make_url('getArtists'),
musicFolderId=music_folder_id)
return result.artists
def get_artist(self, artist_id: int) -> ArtistWithAlbumsID3:
@@ -225,7 +207,7 @@ class Server:
:param artist_id: The artist ID.
"""
result = self._post_json(self._make_url('getArtist'), id=artist_id)
result = self._get_json(self._make_url('getArtist'), id=artist_id)
return result.artist
def get_album(self, album_id: int) -> AlbumWithSongsID3:
@@ -235,7 +217,7 @@ class Server:
:param album_id: The album ID.
"""
result = self._post_json(self._make_url('getAlbum'), id=album_id)
result = self._get_json(self._make_url('getAlbum'), id=album_id)
return result.album
def get_song(self, song_id: int) -> Child:
@@ -244,14 +226,14 @@ class Server:
:param song_id: The song ID.
"""
result = self._post_json(self._make_url('getSong'), id=song_id)
result = self._get_json(self._make_url('getSong'), id=song_id)
return result.song
def get_videos(self) -> Optional[List[Child]]:
"""
Returns all video files.
"""
result = self._post_json(self._make_url('getVideos'))
result = self._get_json(self._make_url('getVideos'))
return result.videos.video
def get_video_info(self, video_id: int) -> Optional[VideoInfo]:
@@ -261,7 +243,7 @@ class Server:
:param video_id: The video ID.
"""
result = self._post_json(self._make_url('getVideoInfo'), id=video_id)
result = self._get_json(self._make_url('getVideoInfo'), id=video_id)
return result.videoInfo
def get_artist_info(
@@ -281,7 +263,7 @@ class Server:
present in the media library. Defaults to false according to API
Spec.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getArtistInfo'),
id=id,
count=count,
@@ -305,7 +287,7 @@ class Server:
present in the media library. Defaults to false according to API
Spec.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getArtistInfo2'),
id=id,
count=count,
@@ -319,7 +301,7 @@ class Server:
:param id: The album or song ID.
"""
result = self._post_json(self._make_url('getAlbumInfo'), id=id)
result = self._get_json(self._make_url('getAlbumInfo'), id=id)
return result.albumInfo
def get_album_info2(self, id: int) -> Optional[AlbumInfo]:
@@ -328,7 +310,7 @@ class Server:
:param id: The album or song ID.
"""
result = self._post_json(self._make_url('getAlbumInfo2'), id=id)
result = self._get_json(self._make_url('getAlbumInfo2'), id=id)
return result.albumInfo
def get_similar_songs(self, id: int, count: int = None) -> List[Child]:
@@ -341,7 +323,7 @@ class Server:
:param count: Max number of songs to return. Defaults to 50 according
to API Spec.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getSimilarSongs'),
id=id,
count=count,
@@ -356,7 +338,7 @@ class Server:
:param count: Max number of songs to return. Defaults to 50 according
to API Spec.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getSimilarSongs2'),
id=id,
count=count,
@@ -371,7 +353,7 @@ class Server:
:param count: Max number of songs to return. Defaults to 50 according
to API Spec.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getTopSongs'),
artist=artist,
count=count,
@@ -413,7 +395,7 @@ class Server:
:param music_folder_id: (Since 1.11.0) Only return albums in the music
folder with the given ID. See ``getMusicFolders``.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getAlbumList'),
type=type,
size=size,
@@ -458,7 +440,7 @@ class Server:
:param music_folder_id: (Since 1.11.0) Only return albums in the music
folder with the given ID. See ``getMusicFolders``.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getAlbumList2'),
type=type,
size=size,
@@ -489,7 +471,7 @@ class Server:
:param music_folder_id: Only return albums in the music folder with the
given ID. See ``getMusicFolders``.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getRandomSongs'),
size=size,
genre=genre,
@@ -517,7 +499,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return albums in the music
folder with the given ID. See ``getMusicFolders``.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getSongsByGenre'),
genre=genre,
count=count,
@@ -531,7 +513,7 @@ class Server:
Returns what is currently being played by all users. Takes no extra
parameters.
"""
result = self._post_json(self._make_url('getNowPlaying'))
result = self._get_json(self._make_url('getNowPlaying'))
return result.nowPlaying
def get_starred(self, music_folder_id: int = None) -> Starred:
@@ -541,7 +523,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return results from the
music folder with the given ID. See ``getMusicFolders``.
"""
result = self._post_json(self._make_url('getStarred'))
result = self._get_json(self._make_url('getStarred'))
return result.starred
def get_starred2(self, music_folder_id: int = None) -> Starred2:
@@ -551,7 +533,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return results from the
music folder with the given ID. See ``getMusicFolders``.
"""
result = self._post_json(self._make_url('getStarred2'))
result = self._get_json(self._make_url('getStarred2'))
return result.starred2
@deprecated(version='1.4.0', reason='You should use search2 instead.')
@@ -577,7 +559,7 @@ class Server:
:param offset: Search result offset. Used for paging.
:param newer_than: Only return matches that are newer than this.
"""
result = self._post_json(
result = self._get_json(
self._make_url('search'),
artist=artist,
album=album,
@@ -621,7 +603,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return results from the
music folder with the given ID. See ``getMusicFolders``.
"""
result = self._post_json(
result = self._get_json(
self._make_url('search2'),
query=query,
artistCount=artist_count,
@@ -664,7 +646,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return results from the
music folder with the given ID. See ``getMusicFolders``.
"""
result = self._post_json(
result = self._get_json(
self._make_url('search3'),
query=query,
artistCount=artist_count,
@@ -685,8 +667,8 @@ class Server:
user rather than for the authenticated user. The authenticated user
must have admin role if this parameter is used.
"""
result = self._post_json(self._make_url('getPlaylists'),
username=username)
result = self._get_json(self._make_url('getPlaylists'),
username=username)
return result.playlists
def get_playlist(self, id: int = None) -> PlaylistWithSongs:
@@ -696,7 +678,7 @@ class Server:
:param username: ID of the playlist to return, as obtained by
``getPlaylists``.
"""
result = self._post_json(self._make_url('getPlaylist'), id=id)
result = self._get_json(self._make_url('getPlaylist'), id=id)
return result.playlist
def create_playlist(
@@ -714,7 +696,7 @@ class Server:
:param song_id: ID(s) of a song in the playlist. Can be a single ID or
a list of IDs.
"""
result = self._post_json(
result = self._get_json(
self._make_url('createPlaylist'),
playlistId=playlist_id,
name=name,
@@ -749,7 +731,7 @@ class Server:
:param song_id_to_remove: Remove the song at this/these position(s) in
the playlist. Can be a single ID or a list of IDs.
"""
return self._post_json(
return self._get_json(
self._make_url('updatePlaylist'),
playlistId=playlist_id,
name=name,
@@ -763,9 +745,9 @@ class Server:
"""
Deletes a saved playlist
"""
return self._post_json(self._make_url('deletePlaylist'), id=id)
return self._get_json(self._make_url('deletePlaylist'), id=id)
def stream(
def get_stream_url(
self,
id: str,
max_bit_rate: int = None,
@@ -776,7 +758,7 @@ class Server:
converted: bool = False,
):
"""
Streams a given file.
Gets the URL to streams a given file.
:param id: A string which uniquely identifies the file to stream.
Obtained by calls to ``getMusicDirectory``.
@@ -803,9 +785,8 @@ class Server:
returned instead of the original. Defaults to False according to
the API Spec.
"""
# TODO make this a decent object
return self._stream(
self._make_url('stream'),
params = dict(
**self._get_params(),
id=id,
maxBitRate=max_bit_rate,
format=format,
@@ -814,6 +795,8 @@ class Server:
estimateContentLength=estimate_content_length,
converted=converted,
)
params = {k: v for k, v in params.items() if v}
return self._make_url('stream') + '?' + urlencode(params)
def download(self, id: str):
"""
@@ -843,7 +826,7 @@ class Server:
:param artist: The artist name.
:param title: The song title.
"""
result = self._post_json(
result = self._get_json(
self._make_url('getLyrics'),
artist=artist,
title=title,
@@ -878,7 +861,7 @@ class Server:
according to ID3 tags rather than file structure. Can be a single
ID or a list of IDs.
"""
return self._post_json(
return self._get_json(
self._make_url('star'),
id=id,
albumId=album_id,
@@ -905,7 +888,7 @@ class Server:
according to ID3 tags rather than file structure. Can be a single
ID or a list of IDs.
"""
return self._post_json(
return self._get_json(
self._make_url('unstar'),
id=id,
albumId=album_id,
@@ -921,9 +904,9 @@ class Server:
:param rating: The rating between 1 and 5 (inclusive), or 0 to remove
the rating.
"""
return self._post_json(self._make_url('setRating'),
id=id,
rating=rating)
return self._get_json(self._make_url('setRating'),
id=id,
rating=rating)
def scrobble(
self,
@@ -953,7 +936,7 @@ class Server:
:param submission: Whether this is a "submission" or a "now playing"
notification.
"""
return self._post_json(
return self._get_json(
self._make_url('scrobble'),
id=id,
time=time,
@@ -965,7 +948,7 @@ class Server:
Returns information about shared media this user is allowed to manage.
Takes no extra parameters.
"""
result = self._post_json(self._make_url('getShares'))
result = self._get_json(self._make_url('getShares'))
return result.shares
def create_share(
@@ -987,7 +970,7 @@ class Server:
to people visiting the shared media.
:param expires: The time at which the share expires.
"""
result = self._post_json(
result = self._get_json(
self._make_url('createShare'),
id=id,
description=description,
@@ -1009,7 +992,7 @@ class Server:
to people visiting the shared media.
:param expires: The time at which the share expires.
"""
return self._post_json(
return self._get_json(
self._make_url('updateShare'),
id=id,
description=description,
@@ -1022,13 +1005,13 @@ class Server:
:param id: ID of the share to delete.
"""
return self._post_json(self._make_url('deleteShare'), id=id)
return self._get_json(self._make_url('deleteShare'), id=id)
def get_internet_radio_stations(self) -> InternetRadioStations:
"""
Returns all internet radio stations. Takes no extra parameters.
"""
result = self._post_json(self._make_url('getInternetRadioStations'))
result = self._get_json(self._make_url('getInternetRadioStations'))
return result.internetRadioStations
def create_internet_radio_station(
@@ -1045,7 +1028,7 @@ class Server:
:param name: The user-defined name for the station.
:param homepage_url: The home page URL for the station.
"""
return self._post_json(
return self._get_json(
self._make_url('createInternetRadioStation'),
streamUrl=stream_url,
name=name,
@@ -1068,7 +1051,7 @@ class Server:
:param name: The user-defined name for the station.
:param homepage_url: The home page URL for the station.
"""
return self._post_json(
return self._get_json(
self._make_url('updateInternetRadioStation'),
id=id,
streamUrl=stream_url,
@@ -1083,8 +1066,8 @@ class Server:
:param id: The ID for the station.
"""
return self._post_json(self._make_url('deleteInternetRadioStation'),
id=id)
return self._get_json(self._make_url('deleteInternetRadioStation'),
id=id)
def get_user(self, username: str) -> User:
"""
@@ -1095,7 +1078,7 @@ class Server:
:param username: The name of the user to retrieve. You can only
retrieve your own user unless you have admin privileges.
"""
result = self._post_json(self._make_url('getUser'), username=username)
result = self._get_json(self._make_url('getUser'), username=username)
return result.user
def get_users(self) -> Users:
@@ -1104,7 +1087,7 @@ class Server:
folder access they have. Only users with admin privileges are allowed
to call this method.
"""
result = self._post_json(self._make_url('getUsers'))
result = self._get_json(self._make_url('getUsers'))
return result.users
def create_user(
@@ -1158,7 +1141,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) IDs of the music folders the
user is allowed access to. Can be a single ID or a list of IDs.
"""
return self._post_json(
return self._get_json(
self._make_url('createUser'),
username=username,
password=password,
@@ -1230,7 +1213,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) IDs of the music folders the
user is allowed access to. Can be a single ID or a list of IDs.
"""
return self._post_json(
return self._get_json(
self._make_url('updateUser'),
username=username,
password=password,
@@ -1257,7 +1240,7 @@ class Server:
:param username: The name of the new user.
"""
return self._post_json(self._make_url('deleteUser'), username=username)
return self._get_json(self._make_url('deleteUser'), username=username)
def change_password(self, username: str, password: str) -> Response:
"""
@@ -1268,7 +1251,7 @@ class Server:
:param password: The new password of the new user, either in clear text
of hex-encoded.
"""
return self._post_json(
return self._get_json(
self._make_url('changePassword'),
username=username,
password=password,
@@ -1279,7 +1262,7 @@ class Server:
Returns all bookmarks for this user. A bookmark is a position within a
certain media file.
"""
result = self._post_json(self._make_url('getBookmarks'))
result = self._get_json(self._make_url('getBookmarks'))
return result.bookmarks
def create_bookmarks(
@@ -1297,7 +1280,7 @@ class Server:
:param position: The position (in milliseconds) within the media file.
:param comment: A user-defined comment.
"""
return self._post_json(
return self._get_json(
self._make_url('createBookmark'),
id=id,
position=position,
@@ -1311,7 +1294,7 @@ class Server:
:param id: ID of the media file for which to delete the bookmark. Other
users' bookmarks are not affected.
"""
return self._post_json(self._make_url('deleteBookmark'), id=id)
return self._get_json(self._make_url('deleteBookmark'), id=id)
def get_play_queue(self) -> Optional[PlayQueue]:
"""
@@ -1322,7 +1305,7 @@ class Server:
retaining the same play queue (for instance when listening to an audio
book).
"""
result = self._post_json(self._make_url('getPlayQueue'))
result = self._get_json(self._make_url('getPlayQueue'))
return result.playQueue
def save_play_queue(
@@ -1344,7 +1327,7 @@ class Server:
:param position: The position in milliseconds within the currently
playing song.
"""
return self._post_json(
return self._get_json(
self._make_url('savePlayQueue'),
id=id,
current=current,
@@ -1356,12 +1339,12 @@ class Server:
Returns the current status for media library scanning. Takes no extra
parameters.
"""
result = self._post_json(self._make_url('getScanStatus'))
result = self._get_json(self._make_url('getScanStatus'))
return result.scanStatus
def start_scan(self) -> ScanStatus:
"""
Initiates a rescan of the media libraries. Takes no extra parameters.
"""
result = self._post_json(self._make_url('startScan'))
result = self._get_json(self._make_url('startScan'))
return result.scanStatus

View File

@@ -85,7 +85,7 @@ class ArtistList(Gtk.Box):
self.add(list_actions)
list_scroll_window = Gtk.ScrolledWindow(min_content_width=220)
list_scroll_window = Gtk.ScrolledWindow(min_content_width=250)
self.list = Gtk.ListBox(name='artist-list-listbox')
self.loading_indicator = Gtk.ListBoxRow(

View File

@@ -327,7 +327,7 @@ class PlaylistsPanel(Gtk.Paned):
)
cover_art_filename = f'cover_art/{playlist.coverArt}_*'
CacheManager.delete_cache(cover_art_filename)
CacheManager.delete_cached(cover_art_filename)
self.update_playlist_list(force=True)
self.update_playlist_view(playlist.id, force=True)