Got streaming working; converted to use GET requests
This commit is contained in:
@@ -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(
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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(
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user