Removed CacheManager from the face of the earth
This commit is contained in:
@@ -41,7 +41,6 @@ except Exception:
|
|||||||
|
|
||||||
from .adapters import AdapterManager, AlbumSearchQuery, Result
|
from .adapters import AdapterManager, AlbumSearchQuery, Result
|
||||||
from .adapters.api_objects import Playlist, PlayQueue, Song
|
from .adapters.api_objects import Playlist, PlayQueue, Song
|
||||||
from .cache_manager import CacheManager
|
|
||||||
from .config import AppConfiguration, ReplayGainType
|
from .config import AppConfiguration, ReplayGainType
|
||||||
from .dbus import dbus_propagate, DBusManager
|
from .dbus import dbus_propagate, DBusManager
|
||||||
from .players import ChromecastPlayer, MPVPlayer, PlayerEvent
|
from .players import ChromecastPlayer, MPVPlayer, PlayerEvent
|
||||||
@@ -880,7 +879,6 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
self.save_play_queue()
|
self.save_play_queue()
|
||||||
if self.dbus_manager:
|
if self.dbus_manager:
|
||||||
self.dbus_manager.shutdown()
|
self.dbus_manager.shutdown()
|
||||||
CacheManager.shutdown()
|
|
||||||
AdapterManager.shutdown()
|
AdapterManager.shutdown()
|
||||||
|
|
||||||
# ########## HELPER METHODS ########## #
|
# ########## HELPER METHODS ########## #
|
||||||
|
@@ -1,398 +0,0 @@
|
|||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import threading
|
|
||||||
from collections import defaultdict
|
|
||||||
from concurrent.futures import Future, ThreadPoolExecutor
|
|
||||||
from datetime import datetime
|
|
||||||
from enum import EnumMeta
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import (
|
|
||||||
Any,
|
|
||||||
Callable,
|
|
||||||
DefaultDict,
|
|
||||||
Dict,
|
|
||||||
Generic,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
Set,
|
|
||||||
TypeVar,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version("NM", "1.0")
|
|
||||||
from gi.repository import NM
|
|
||||||
|
|
||||||
networkmanager_imported = True
|
|
||||||
except Exception:
|
|
||||||
# I really don't care what kind of exception it is, all that matters is the
|
|
||||||
# import failed for some reason.
|
|
||||||
logging.warning(
|
|
||||||
"Unable to import NM from GLib. Detection of SSID will be disabled."
|
|
||||||
)
|
|
||||||
networkmanager_imported = False
|
|
||||||
|
|
||||||
from .config import AppConfiguration
|
|
||||||
from .server import Server
|
|
||||||
from .server.api_object import APIObject
|
|
||||||
from .server.api_objects import (
|
|
||||||
AlbumWithSongsID3,
|
|
||||||
Artist,
|
|
||||||
ArtistID3,
|
|
||||||
ArtistInfo2,
|
|
||||||
ArtistWithAlbumsID3,
|
|
||||||
Child,
|
|
||||||
Directory,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Singleton(type):
|
|
||||||
"""
|
|
||||||
Metaclass for :class:`CacheManager` so that it can be used like a
|
|
||||||
singleton.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __getattr__(cls, name: str) -> Any:
|
|
||||||
if not CacheManager._instance:
|
|
||||||
return None
|
|
||||||
# If the cache has a function to do the thing we want, use it. If
|
|
||||||
# not, then go directly to the server (this is useful for things
|
|
||||||
# that just send data to the server.)
|
|
||||||
if hasattr(CacheManager._instance, name):
|
|
||||||
return getattr(CacheManager._instance, name)
|
|
||||||
else:
|
|
||||||
return getattr(CacheManager._instance.server, name)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
|
|
||||||
class CacheManager(metaclass=Singleton):
|
|
||||||
"""
|
|
||||||
Handles everything related to caching metadata and song files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
executor: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=50)
|
|
||||||
should_exit: bool = False
|
|
||||||
|
|
||||||
class Result(Generic[T]):
|
|
||||||
# This needs to accept some way of:
|
|
||||||
# 1. getting data from the server to fulfill the request
|
|
||||||
# 2. coercing the data to the schema of the cachedb
|
|
||||||
# 3. queries for retriving the data from the cachedb
|
|
||||||
# All results should be retrieved using select statements from the DB
|
|
||||||
"""
|
|
||||||
A result from a CacheManager function. This is effectively a wrapper
|
|
||||||
around a Future, but it can also resolve immediately if the data
|
|
||||||
already exists.
|
|
||||||
"""
|
|
||||||
data: Optional[T] = None
|
|
||||||
future: Optional[Future] = None
|
|
||||||
on_cancel: Optional[Callable[[], None]] = None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_data(data: T) -> "CacheManager.Result[T]":
|
|
||||||
result: "CacheManager.Result[T]" = CacheManager.Result()
|
|
||||||
result.data = data
|
|
||||||
return result
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_server(
|
|
||||||
download_fn: Callable[[], T],
|
|
||||||
before_download: Callable[[], Any] = None,
|
|
||||||
after_download: Callable[[T], Any] = None,
|
|
||||||
on_cancel: Callable[[], Any] = None,
|
|
||||||
) -> "CacheManager.Result[T]":
|
|
||||||
result: "CacheManager.Result[T]" = CacheManager.Result()
|
|
||||||
|
|
||||||
def future_fn() -> T:
|
|
||||||
if before_download:
|
|
||||||
before_download()
|
|
||||||
return download_fn()
|
|
||||||
|
|
||||||
result.future = CacheManager.executor.submit(future_fn)
|
|
||||||
result.on_cancel = on_cancel
|
|
||||||
|
|
||||||
if after_download is not None:
|
|
||||||
result.future.add_done_callback(
|
|
||||||
lambda f: after_download and after_download(f.result())
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def result(self) -> T:
|
|
||||||
if self.data is not None:
|
|
||||||
return self.data
|
|
||||||
if self.future is not None:
|
|
||||||
return self.future.result()
|
|
||||||
|
|
||||||
raise Exception(
|
|
||||||
"CacheManager.Result did not have either a data or future " "member."
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_done_callback(self, fn: Callable, *args):
|
|
||||||
if self.future is not None:
|
|
||||||
self.future.add_done_callback(fn, *args)
|
|
||||||
else:
|
|
||||||
# Run the function immediately if it's not a future.
|
|
||||||
fn(self, *args)
|
|
||||||
|
|
||||||
def cancel(self) -> bool:
|
|
||||||
if self.on_cancel is not None:
|
|
||||||
self.on_cancel()
|
|
||||||
|
|
||||||
if self.future is not None:
|
|
||||||
return self.future.cancel()
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_future(self) -> bool:
|
|
||||||
return self.future is not None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def ready() -> bool:
|
|
||||||
return CacheManager._instance is not None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def shutdown():
|
|
||||||
logging.info("CacheManager shutdown start")
|
|
||||||
CacheManager.should_exit = True
|
|
||||||
CacheManager.executor.shutdown()
|
|
||||||
CacheManager._instance.save_cache_info()
|
|
||||||
logging.info("CacheManager shutdown complete")
|
|
||||||
|
|
||||||
class CacheEncoder(json.JSONEncoder):
|
|
||||||
def default(self, obj: Any) -> Optional[Union[int, List, Dict]]:
|
|
||||||
"""
|
|
||||||
Encodes Python objects to JSON.
|
|
||||||
|
|
||||||
- ``datetime`` objects are converted to UNIX timestamps (``int``)
|
|
||||||
- ``set`` objects are converted to ``list`` objects
|
|
||||||
- ``APIObject`` objects are recursively encoded
|
|
||||||
- ``EnumMeta`` objects are ignored
|
|
||||||
- everything else is encoded using the default encoder
|
|
||||||
"""
|
|
||||||
if type(obj) == datetime:
|
|
||||||
return int(obj.timestamp() * 1000)
|
|
||||||
elif type(obj) == set:
|
|
||||||
return list(obj)
|
|
||||||
elif isinstance(obj, APIObject):
|
|
||||||
return {k: v for k, v in obj.__dict__.items() if v is not None}
|
|
||||||
elif isinstance(obj, EnumMeta):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return json.JSONEncoder.default(self, obj)
|
|
||||||
|
|
||||||
class __CacheManagerInternal:
|
|
||||||
# Thread lock for preventing threads from overriding the state while
|
|
||||||
# it's being saved.
|
|
||||||
cache_lock = threading.Lock()
|
|
||||||
|
|
||||||
cache: DefaultDict[str, Any] = defaultdict(dict)
|
|
||||||
permanently_cached_paths: Set[str] = set()
|
|
||||||
|
|
||||||
# The server instance.
|
|
||||||
server: Server
|
|
||||||
|
|
||||||
# TODO (#56): need to split out the song downloads and make them higher
|
|
||||||
# priority I think. Maybe even need to just make this a priority queue.
|
|
||||||
download_set_lock = threading.Lock()
|
|
||||||
current_downloads: Set[str] = set()
|
|
||||||
|
|
||||||
def __init__(self, app_config: AppConfiguration):
|
|
||||||
self.app_config = app_config
|
|
||||||
assert self.app_config.server is not None
|
|
||||||
self.app_config.server
|
|
||||||
|
|
||||||
# If connected to the "Local Network SSID", use the "Local Network
|
|
||||||
# Address" instead of the "Server Address".
|
|
||||||
hostname = self.app_config.server.server_address
|
|
||||||
if self.app_config.server.local_network_ssid in self.current_ssids:
|
|
||||||
hostname = self.app_config.server.local_network_address
|
|
||||||
|
|
||||||
self.server = Server(
|
|
||||||
name=self.app_config.server.name,
|
|
||||||
hostname=hostname,
|
|
||||||
username=self.app_config.server.username,
|
|
||||||
password=self.app_config.server.password,
|
|
||||||
disable_cert_verify=self.app_config.server.disable_cert_verify,
|
|
||||||
)
|
|
||||||
self.download_limiter_semaphore = threading.Semaphore(
|
|
||||||
self.app_config.concurrent_download_limit
|
|
||||||
)
|
|
||||||
|
|
||||||
self.load_cache_info()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_ssids(self) -> Set[str]:
|
|
||||||
if not networkmanager_imported:
|
|
||||||
return set()
|
|
||||||
|
|
||||||
self.networkmanager_client = NM.Client.new()
|
|
||||||
self.nmclient_initialized = False
|
|
||||||
self._current_ssids: Set[str] = set()
|
|
||||||
if not self.nmclient_initialized:
|
|
||||||
# Only look at the active WiFi connections.
|
|
||||||
for ac in self.networkmanager_client.get_active_connections():
|
|
||||||
if ac.get_connection_type() != "802-11-wireless":
|
|
||||||
continue
|
|
||||||
devs = ac.get_devices()
|
|
||||||
if len(devs) != 1:
|
|
||||||
continue
|
|
||||||
if devs[0].get_device_type() != NM.DeviceType.WIFI:
|
|
||||||
continue
|
|
||||||
|
|
||||||
self._current_ssids.add(ac.get_id())
|
|
||||||
|
|
||||||
return self._current_ssids
|
|
||||||
|
|
||||||
def load_cache_info(self):
|
|
||||||
cache_meta_file = self.calculate_abs_path(".cache_meta")
|
|
||||||
|
|
||||||
meta_json = {}
|
|
||||||
if cache_meta_file.exists():
|
|
||||||
with open(cache_meta_file, "r") as f:
|
|
||||||
try:
|
|
||||||
meta_json = json.load(f)
|
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
# Just continue with the default meta_json.
|
|
||||||
logging.warning("Unable to load cache", stack_info=True)
|
|
||||||
|
|
||||||
cache_version = meta_json.get("version", 0)
|
|
||||||
|
|
||||||
if cache_version < 1:
|
|
||||||
logging.info("Migrating cache to version 1.")
|
|
||||||
cover_art_re = re.compile(r"(\d+)_(\d+)")
|
|
||||||
abs_path = self.calculate_abs_path("cover_art/")
|
|
||||||
abs_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
for cover_art_file in abs_path.iterdir():
|
|
||||||
match = cover_art_re.match(cover_art_file.name)
|
|
||||||
if match:
|
|
||||||
art_id, dimensions = map(int, match.groups())
|
|
||||||
if dimensions == 1000:
|
|
||||||
no_dimens = cover_art_file.parent.joinpath("{art_id}")
|
|
||||||
logging.info(f"Moving {cover_art_file} to {no_dimens}")
|
|
||||||
shutil.move(cover_art_file, no_dimens)
|
|
||||||
else:
|
|
||||||
logging.info(f"Deleting {cover_art_file}")
|
|
||||||
cover_art_file.unlink()
|
|
||||||
|
|
||||||
self.cache["version"] = 1
|
|
||||||
|
|
||||||
cache_configs = [
|
|
||||||
("song_details", Child, dict),
|
|
||||||
# Non-ID3 caches
|
|
||||||
("music_directories", Directory, dict),
|
|
||||||
("indexes", Artist, list),
|
|
||||||
# ID3 caches
|
|
||||||
("albums", AlbumWithSongsID3, "dict-list"),
|
|
||||||
("album_details", AlbumWithSongsID3, dict),
|
|
||||||
("artists", ArtistID3, list),
|
|
||||||
("artist_details", ArtistWithAlbumsID3, dict),
|
|
||||||
("artist_infos", ArtistInfo2, dict),
|
|
||||||
]
|
|
||||||
for name, type_name, default in cache_configs:
|
|
||||||
if default == list:
|
|
||||||
self.cache[name] = [
|
|
||||||
type_name.from_json(x) for x in meta_json.get(name) or []
|
|
||||||
]
|
|
||||||
elif default == dict:
|
|
||||||
self.cache[name] = {
|
|
||||||
id: type_name.from_json(x)
|
|
||||||
for id, x in (meta_json.get(name) or {}).items()
|
|
||||||
}
|
|
||||||
elif default == "dict-list":
|
|
||||||
self.cache[name] = {
|
|
||||||
n: [type_name.from_json(x) for x in xs]
|
|
||||||
for n, xs in (meta_json.get(name) or {}).items()
|
|
||||||
}
|
|
||||||
|
|
||||||
def save_cache_info(self):
|
|
||||||
os.makedirs(self.app_config.cache_location, exist_ok=True)
|
|
||||||
|
|
||||||
cache_meta_file = self.calculate_abs_path(".cache_meta")
|
|
||||||
os.makedirs(os.path.dirname(cache_meta_file), exist_ok=True)
|
|
||||||
with open(cache_meta_file, "w+") as f, self.cache_lock:
|
|
||||||
f.write(json.dumps(self.cache, indent=2, cls=CacheManager.CacheEncoder))
|
|
||||||
|
|
||||||
def save_file(self, absolute_path: Path, data: bytes):
|
|
||||||
# Make the necessary directories and write to file.
|
|
||||||
os.makedirs(absolute_path.parent, exist_ok=True)
|
|
||||||
with open(absolute_path, "wb+") as f:
|
|
||||||
f.write(data)
|
|
||||||
|
|
||||||
def calculate_abs_path(self, *relative_paths) -> Path:
|
|
||||||
assert self.app_config.server is not None
|
|
||||||
return Path(self.app_config.cache_location).joinpath(
|
|
||||||
self.app_config.server.strhash(), *relative_paths
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_future(fn: Callable, *args) -> Future:
|
|
||||||
"""Creates a future on the CacheManager's executor."""
|
|
||||||
return CacheManager.executor.submit(fn, *args)
|
|
||||||
|
|
||||||
def get_indexes(
|
|
||||||
self,
|
|
||||||
before_download: Callable[[], None] = lambda: None,
|
|
||||||
force: bool = False,
|
|
||||||
) -> "CacheManager.Result[List[Artist]]":
|
|
||||||
cache_name = "indexes"
|
|
||||||
|
|
||||||
if self.cache.get(cache_name) and not force:
|
|
||||||
return CacheManager.Result.from_data(self.cache[cache_name])
|
|
||||||
|
|
||||||
def download_fn() -> List[Artist]:
|
|
||||||
artists: List[Artist] = []
|
|
||||||
for index in self.server.get_indexes().index:
|
|
||||||
artists.extend(index.artist)
|
|
||||||
return artists
|
|
||||||
|
|
||||||
def after_download(artists: List[Artist]):
|
|
||||||
with self.cache_lock:
|
|
||||||
self.cache[cache_name] = artists
|
|
||||||
self.save_cache_info()
|
|
||||||
|
|
||||||
return CacheManager.Result.from_server(
|
|
||||||
download_fn,
|
|
||||||
before_download=before_download,
|
|
||||||
after_download=after_download,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_music_directory(
|
|
||||||
self,
|
|
||||||
id: int,
|
|
||||||
before_download: Callable[[], None] = lambda: None,
|
|
||||||
force: bool = False,
|
|
||||||
) -> "CacheManager.Result[Directory]":
|
|
||||||
cache_name = "music_directories"
|
|
||||||
|
|
||||||
if id in self.cache.get(cache_name, {}) and not force:
|
|
||||||
return CacheManager.Result.from_data(self.cache[cache_name][id])
|
|
||||||
|
|
||||||
def after_download(directory: Directory):
|
|
||||||
with self.cache_lock:
|
|
||||||
self.cache[cache_name][id] = directory
|
|
||||||
self.save_cache_info()
|
|
||||||
|
|
||||||
return CacheManager.Result.from_server(
|
|
||||||
lambda: self.server.get_music_directory(id),
|
|
||||||
before_download=before_download,
|
|
||||||
after_download=after_download,
|
|
||||||
)
|
|
||||||
|
|
||||||
_instance: Optional[__CacheManagerInternal] = None
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
raise Exception("Do not instantiate the CacheManager.")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def reset(app_config: AppConfiguration):
|
|
||||||
CacheManager._instance = CacheManager.__CacheManagerInternal(app_config)
|
|
@@ -153,10 +153,8 @@ class AppConfiguration:
|
|||||||
self._state = UIState()
|
self._state = UIState()
|
||||||
|
|
||||||
# Do the import in the function to avoid circular imports.
|
# Do the import in the function to avoid circular imports.
|
||||||
from sublime.cache_manager import CacheManager
|
|
||||||
from sublime.adapters import AdapterManager
|
from sublime.adapters import AdapterManager
|
||||||
|
|
||||||
CacheManager.reset(self)
|
|
||||||
AdapterManager.reset(self)
|
AdapterManager.reset(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
"""
|
|
||||||
This module defines a stateless server which interops with the Subsonic API.
|
|
||||||
"""
|
|
||||||
from .server import Server
|
|
||||||
|
|
||||||
__all__ = ("Server",)
|
|
@@ -1,20 +0,0 @@
|
|||||||
# from dataclasses import Field, fields
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from sublime.from_json import from_json as _from_json
|
|
||||||
|
|
||||||
|
|
||||||
class APIObject:
|
|
||||||
"""Defines the base class for objects coming from the Subsonic API."""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_json(cls, data: Dict[str, Any]) -> Any:
|
|
||||||
"""
|
|
||||||
Creates an :class:`APIObject` by taking the ``data`` and passing it to the class
|
|
||||||
constructor and then recursively calling ``from_json`` on all of the fields.
|
|
||||||
``data`` just has to be a well-formed :class:`dict`, so it can come from the
|
|
||||||
JSON or XML APIs.
|
|
||||||
|
|
||||||
:param data: a Python dictionary representation of the data to deserialize
|
|
||||||
"""
|
|
||||||
return _from_json(cls, data)
|
|
@@ -1,936 +0,0 @@
|
|||||||
"""
|
|
||||||
WARNING: AUTOGENERATED FILE
|
|
||||||
This file was generated by the api_object_generator.py
|
|
||||||
script. Do not modify this file directly, rather modify the
|
|
||||||
script or run it on a new API version.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from datetime import datetime
|
|
||||||
from enum import Enum
|
|
||||||
from typing import Any, List, Optional
|
|
||||||
|
|
||||||
from sublime.server.api_object import APIObject
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AlbumInfo(APIObject):
|
|
||||||
notes: List[str] = field(default_factory=list)
|
|
||||||
musicBrainzId: List[str] = field(default_factory=list)
|
|
||||||
lastFmUrl: List[str] = field(default_factory=list)
|
|
||||||
smallImageUrl: List[str] = field(default_factory=list)
|
|
||||||
mediumImageUrl: List[str] = field(default_factory=list)
|
|
||||||
largeImageUrl: List[str] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AverageRating(APIObject, float):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MediaType(APIObject, Enum):
|
|
||||||
MUSIC = "music"
|
|
||||||
PODCAST = "podcast"
|
|
||||||
AUDIOBOOK = "audiobook"
|
|
||||||
VIDEO = "video"
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class UserRating(APIObject, int):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Child(APIObject):
|
|
||||||
id: str
|
|
||||||
isDir: bool
|
|
||||||
title: str
|
|
||||||
value: Optional[str] = None
|
|
||||||
parent: Optional[str] = None
|
|
||||||
album: Optional[str] = None
|
|
||||||
artist: Optional[str] = None
|
|
||||||
track: Optional[int] = None
|
|
||||||
year: Optional[int] = None
|
|
||||||
genre: Optional[str] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
size: Optional[int] = None
|
|
||||||
contentType: Optional[str] = None
|
|
||||||
suffix: Optional[str] = None
|
|
||||||
transcodedContentType: Optional[str] = None
|
|
||||||
transcodedSuffix: Optional[str] = None
|
|
||||||
duration: Optional[int] = None
|
|
||||||
bitRate: Optional[int] = None
|
|
||||||
path: Optional[str] = None
|
|
||||||
isVideo: Optional[bool] = None
|
|
||||||
userRating: Optional[UserRating] = None
|
|
||||||
averageRating: Optional[AverageRating] = None
|
|
||||||
playCount: Optional[int] = None
|
|
||||||
discNumber: Optional[int] = None
|
|
||||||
created: Optional[datetime] = None
|
|
||||||
starred: Optional[datetime] = None
|
|
||||||
albumId: Optional[str] = None
|
|
||||||
artistId: Optional[str] = None
|
|
||||||
type: Optional[MediaType] = None
|
|
||||||
bookmarkPosition: Optional[int] = None
|
|
||||||
originalWidth: Optional[int] = None
|
|
||||||
originalHeight: Optional[int] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AlbumList(APIObject):
|
|
||||||
album: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AlbumID3(APIObject):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
songCount: int
|
|
||||||
duration: int
|
|
||||||
created: datetime
|
|
||||||
value: Optional[str] = None
|
|
||||||
artist: Optional[str] = None
|
|
||||||
artistId: Optional[str] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
playCount: Optional[int] = None
|
|
||||||
starred: Optional[datetime] = None
|
|
||||||
year: Optional[int] = None
|
|
||||||
genre: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AlbumList2(APIObject):
|
|
||||||
album: List[AlbumID3] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AlbumWithSongsID3(APIObject):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
songCount: int
|
|
||||||
duration: int
|
|
||||||
created: datetime
|
|
||||||
song: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
artist: Optional[str] = None
|
|
||||||
artistId: Optional[str] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
playCount: Optional[int] = None
|
|
||||||
starred: Optional[datetime] = None
|
|
||||||
year: Optional[int] = None
|
|
||||||
genre: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Artist(APIObject):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
value: Optional[str] = None
|
|
||||||
artistImageUrl: Optional[str] = None
|
|
||||||
starred: Optional[datetime] = None
|
|
||||||
userRating: Optional[UserRating] = None
|
|
||||||
averageRating: Optional[AverageRating] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ArtistInfoBase(APIObject):
|
|
||||||
biography: List[str] = field(default_factory=list)
|
|
||||||
musicBrainzId: List[str] = field(default_factory=list)
|
|
||||||
lastFmUrl: List[str] = field(default_factory=list)
|
|
||||||
smallImageUrl: List[str] = field(default_factory=list)
|
|
||||||
mediumImageUrl: List[str] = field(default_factory=list)
|
|
||||||
largeImageUrl: List[str] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ArtistInfo(APIObject):
|
|
||||||
similarArtist: List[Artist] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
biography: List[str] = field(default_factory=list)
|
|
||||||
musicBrainzId: List[str] = field(default_factory=list)
|
|
||||||
lastFmUrl: List[str] = field(default_factory=list)
|
|
||||||
smallImageUrl: List[str] = field(default_factory=list)
|
|
||||||
mediumImageUrl: List[str] = field(default_factory=list)
|
|
||||||
largeImageUrl: List[str] = field(default_factory=list)
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ArtistID3(APIObject):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
albumCount: int
|
|
||||||
value: Optional[str] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
artistImageUrl: Optional[str] = None
|
|
||||||
starred: Optional[datetime] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ArtistInfo2(APIObject):
|
|
||||||
similarArtist: List[ArtistID3] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
biography: List[str] = field(default_factory=list)
|
|
||||||
musicBrainzId: List[str] = field(default_factory=list)
|
|
||||||
lastFmUrl: List[str] = field(default_factory=list)
|
|
||||||
smallImageUrl: List[str] = field(default_factory=list)
|
|
||||||
mediumImageUrl: List[str] = field(default_factory=list)
|
|
||||||
largeImageUrl: List[str] = field(default_factory=list)
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ArtistWithAlbumsID3(APIObject):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
albumCount: int
|
|
||||||
album: List[AlbumID3] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
artistImageUrl: Optional[str] = None
|
|
||||||
starred: Optional[datetime] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class IndexID3(APIObject):
|
|
||||||
name: str
|
|
||||||
artist: List[ArtistID3] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ArtistsID3(APIObject):
|
|
||||||
ignoredArticles: str
|
|
||||||
index: List[IndexID3] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Bookmark(APIObject):
|
|
||||||
position: int
|
|
||||||
username: str
|
|
||||||
created: datetime
|
|
||||||
changed: datetime
|
|
||||||
entry: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
comment: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Bookmarks(APIObject):
|
|
||||||
bookmark: List[Bookmark] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ChatMessage(APIObject):
|
|
||||||
username: str
|
|
||||||
time: int
|
|
||||||
message: str
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ChatMessages(APIObject):
|
|
||||||
chatMessage: List[ChatMessage] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Directory(APIObject):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
child: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
parent: Optional[str] = None
|
|
||||||
starred: Optional[datetime] = None
|
|
||||||
userRating: Optional[UserRating] = None
|
|
||||||
averageRating: Optional[AverageRating] = None
|
|
||||||
playCount: Optional[int] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Error(APIObject):
|
|
||||||
code: int
|
|
||||||
value: Optional[str] = None
|
|
||||||
message: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Genre(APIObject):
|
|
||||||
songCount: int
|
|
||||||
albumCount: int
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Genres(APIObject):
|
|
||||||
genre: List[Genre] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Index(APIObject):
|
|
||||||
name: str
|
|
||||||
artist: List[Artist] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Indexes(APIObject):
|
|
||||||
lastModified: int
|
|
||||||
ignoredArticles: str
|
|
||||||
shortcut: List[Artist] = field(default_factory=list)
|
|
||||||
index: List[Index] = field(default_factory=list)
|
|
||||||
child: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class InternetRadioStation(APIObject):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
streamUrl: str
|
|
||||||
value: Optional[str] = None
|
|
||||||
homePageUrl: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class InternetRadioStations(APIObject):
|
|
||||||
internetRadioStation: List[InternetRadioStation] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class JukeboxStatus(APIObject):
|
|
||||||
currentIndex: int
|
|
||||||
playing: bool
|
|
||||||
gain: float
|
|
||||||
value: Optional[str] = None
|
|
||||||
position: Optional[int] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class JukeboxPlaylist(APIObject):
|
|
||||||
currentIndex: int
|
|
||||||
playing: bool
|
|
||||||
gain: float
|
|
||||||
entry: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
position: Optional[int] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class License(APIObject):
|
|
||||||
valid: bool
|
|
||||||
value: Optional[str] = None
|
|
||||||
email: Optional[str] = None
|
|
||||||
licenseExpires: Optional[datetime] = None
|
|
||||||
trialExpires: Optional[datetime] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Lyrics(APIObject):
|
|
||||||
artist: Optional[str] = None
|
|
||||||
value: Optional[str] = None
|
|
||||||
title: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class MusicFolder(APIObject):
|
|
||||||
id: int
|
|
||||||
value: Optional[str] = None
|
|
||||||
name: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class MusicFolders(APIObject):
|
|
||||||
musicFolder: List[MusicFolder] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
class PodcastStatus(APIObject, Enum):
|
|
||||||
NEW = "new"
|
|
||||||
DOWNLOADING = "downloading"
|
|
||||||
COMPLETED = "completed"
|
|
||||||
ERROR = "error"
|
|
||||||
DELETED = "deleted"
|
|
||||||
SKIPPED = "skipped"
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class PodcastEpisode(APIObject):
|
|
||||||
channelId: str
|
|
||||||
status: PodcastStatus
|
|
||||||
id: str
|
|
||||||
isDir: bool
|
|
||||||
title: str
|
|
||||||
streamId: Optional[str] = None
|
|
||||||
description: Optional[str] = None
|
|
||||||
publishDate: Optional[datetime] = None
|
|
||||||
value: Optional[str] = None
|
|
||||||
parent: Optional[str] = None
|
|
||||||
album: Optional[str] = None
|
|
||||||
artist: Optional[str] = None
|
|
||||||
track: Optional[int] = None
|
|
||||||
year: Optional[int] = None
|
|
||||||
genre: Optional[str] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
size: Optional[int] = None
|
|
||||||
contentType: Optional[str] = None
|
|
||||||
suffix: Optional[str] = None
|
|
||||||
transcodedContentType: Optional[str] = None
|
|
||||||
transcodedSuffix: Optional[str] = None
|
|
||||||
duration: Optional[int] = None
|
|
||||||
bitRate: Optional[int] = None
|
|
||||||
path: Optional[str] = None
|
|
||||||
isVideo: Optional[bool] = None
|
|
||||||
userRating: Optional[UserRating] = None
|
|
||||||
averageRating: Optional[AverageRating] = None
|
|
||||||
playCount: Optional[int] = None
|
|
||||||
discNumber: Optional[int] = None
|
|
||||||
created: Optional[datetime] = None
|
|
||||||
starred: Optional[datetime] = None
|
|
||||||
albumId: Optional[str] = None
|
|
||||||
artistId: Optional[str] = None
|
|
||||||
type: Optional[MediaType] = None
|
|
||||||
bookmarkPosition: Optional[int] = None
|
|
||||||
originalWidth: Optional[int] = None
|
|
||||||
originalHeight: Optional[int] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class NewestPodcasts(APIObject):
|
|
||||||
episode: List[PodcastEpisode] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class NowPlayingEntry(APIObject):
|
|
||||||
username: str
|
|
||||||
minutesAgo: int
|
|
||||||
playerId: int
|
|
||||||
id: str
|
|
||||||
isDir: bool
|
|
||||||
title: str
|
|
||||||
playerName: Optional[str] = None
|
|
||||||
value: Optional[str] = None
|
|
||||||
parent: Optional[str] = None
|
|
||||||
album: Optional[str] = None
|
|
||||||
artist: Optional[str] = None
|
|
||||||
track: Optional[int] = None
|
|
||||||
year: Optional[int] = None
|
|
||||||
genre: Optional[str] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
size: Optional[int] = None
|
|
||||||
contentType: Optional[str] = None
|
|
||||||
suffix: Optional[str] = None
|
|
||||||
transcodedContentType: Optional[str] = None
|
|
||||||
transcodedSuffix: Optional[str] = None
|
|
||||||
duration: Optional[int] = None
|
|
||||||
bitRate: Optional[int] = None
|
|
||||||
path: Optional[str] = None
|
|
||||||
isVideo: Optional[bool] = None
|
|
||||||
userRating: Optional[UserRating] = None
|
|
||||||
averageRating: Optional[AverageRating] = None
|
|
||||||
playCount: Optional[int] = None
|
|
||||||
discNumber: Optional[int] = None
|
|
||||||
created: Optional[datetime] = None
|
|
||||||
starred: Optional[datetime] = None
|
|
||||||
albumId: Optional[str] = None
|
|
||||||
artistId: Optional[str] = None
|
|
||||||
type: Optional[MediaType] = None
|
|
||||||
bookmarkPosition: Optional[int] = None
|
|
||||||
originalWidth: Optional[int] = None
|
|
||||||
originalHeight: Optional[int] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class NowPlaying(APIObject):
|
|
||||||
entry: List[NowPlayingEntry] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class PlayQueue(APIObject):
|
|
||||||
username: str
|
|
||||||
changed: datetime
|
|
||||||
changedBy: str
|
|
||||||
entry: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
current: Optional[int] = None
|
|
||||||
position: Optional[int] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Playlist(APIObject):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
songCount: int
|
|
||||||
duration: int
|
|
||||||
created: datetime
|
|
||||||
changed: datetime
|
|
||||||
allowedUser: List[str] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
comment: Optional[str] = None
|
|
||||||
owner: Optional[str] = None
|
|
||||||
public: Optional[bool] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class PlaylistWithSongs(APIObject):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
songCount: int
|
|
||||||
duration: int
|
|
||||||
created: datetime
|
|
||||||
changed: datetime
|
|
||||||
entry: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
allowedUser: List[str] = field(default_factory=list)
|
|
||||||
comment: Optional[str] = None
|
|
||||||
owner: Optional[str] = None
|
|
||||||
public: Optional[bool] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Playlists(APIObject):
|
|
||||||
playlist: List[Playlist] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class PodcastChannel(APIObject):
|
|
||||||
id: str
|
|
||||||
url: str
|
|
||||||
status: PodcastStatus
|
|
||||||
episode: List[PodcastEpisode] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
title: Optional[str] = None
|
|
||||||
description: Optional[str] = None
|
|
||||||
coverArt: Optional[str] = None
|
|
||||||
originalImageUrl: Optional[str] = None
|
|
||||||
errorMessage: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Podcasts(APIObject):
|
|
||||||
channel: List[PodcastChannel] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
class ResponseStatus(APIObject, Enum):
|
|
||||||
OK = "ok"
|
|
||||||
FAILED = "failed"
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ScanStatus(APIObject):
|
|
||||||
scanning: bool
|
|
||||||
value: Optional[str] = None
|
|
||||||
count: Optional[int] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SearchResult(APIObject):
|
|
||||||
offset: int
|
|
||||||
totalHits: int
|
|
||||||
match: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SearchResult2(APIObject):
|
|
||||||
artist: List[Artist] = field(default_factory=list)
|
|
||||||
album: List[Child] = field(default_factory=list)
|
|
||||||
song: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SearchResult3(APIObject):
|
|
||||||
artist: List[ArtistID3] = field(default_factory=list)
|
|
||||||
album: List[AlbumID3] = field(default_factory=list)
|
|
||||||
song: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Share(APIObject):
|
|
||||||
id: str
|
|
||||||
url: str
|
|
||||||
username: str
|
|
||||||
created: datetime
|
|
||||||
visitCount: int
|
|
||||||
entry: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
description: Optional[str] = None
|
|
||||||
expires: Optional[datetime] = None
|
|
||||||
lastVisited: Optional[datetime] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Shares(APIObject):
|
|
||||||
share: List[Share] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SimilarSongs(APIObject):
|
|
||||||
song: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SimilarSongs2(APIObject):
|
|
||||||
song: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Songs(APIObject):
|
|
||||||
song: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Starred(APIObject):
|
|
||||||
artist: List[Artist] = field(default_factory=list)
|
|
||||||
album: List[Child] = field(default_factory=list)
|
|
||||||
song: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Starred2(APIObject):
|
|
||||||
artist: List[ArtistID3] = field(default_factory=list)
|
|
||||||
album: List[AlbumID3] = field(default_factory=list)
|
|
||||||
song: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class TopSongs(APIObject):
|
|
||||||
song: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class User(APIObject):
|
|
||||||
username: str
|
|
||||||
scrobblingEnabled: bool
|
|
||||||
adminRole: bool
|
|
||||||
settingsRole: bool
|
|
||||||
downloadRole: bool
|
|
||||||
uploadRole: bool
|
|
||||||
playlistRole: bool
|
|
||||||
coverArtRole: bool
|
|
||||||
commentRole: bool
|
|
||||||
podcastRole: bool
|
|
||||||
streamRole: bool
|
|
||||||
jukeboxRole: bool
|
|
||||||
shareRole: bool
|
|
||||||
videoConversionRole: bool
|
|
||||||
folder: List[int] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
email: Optional[str] = None
|
|
||||||
maxBitRate: Optional[int] = None
|
|
||||||
avatarLastChanged: Optional[datetime] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Users(APIObject):
|
|
||||||
user: List[User] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Version(APIObject, str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AudioTrack(APIObject):
|
|
||||||
id: str
|
|
||||||
value: Optional[str] = None
|
|
||||||
name: Optional[str] = None
|
|
||||||
languageCode: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Captions(APIObject):
|
|
||||||
id: str
|
|
||||||
value: Optional[str] = None
|
|
||||||
name: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class VideoConversion(APIObject):
|
|
||||||
id: str
|
|
||||||
value: Optional[str] = None
|
|
||||||
bitRate: Optional[int] = None
|
|
||||||
audioTrackId: Optional[int] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class VideoInfo(APIObject):
|
|
||||||
id: str
|
|
||||||
captions: List[Captions] = field(default_factory=list)
|
|
||||||
audioTrack: List[AudioTrack] = field(default_factory=list)
|
|
||||||
conversion: List[VideoConversion] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Videos(APIObject):
|
|
||||||
video: List[Child] = field(default_factory=list)
|
|
||||||
value: Optional[str] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Response(APIObject):
|
|
||||||
musicFolders: Optional[MusicFolders] = None
|
|
||||||
indexes: Optional[Indexes] = None
|
|
||||||
directory: Optional[Directory] = None
|
|
||||||
genres: Optional[Genres] = None
|
|
||||||
artists: Optional[ArtistsID3] = None
|
|
||||||
artist: Optional[ArtistWithAlbumsID3] = None
|
|
||||||
album: Optional[AlbumWithSongsID3] = None
|
|
||||||
song: Optional[Child] = None
|
|
||||||
videos: Optional[Videos] = None
|
|
||||||
videoInfo: Optional[VideoInfo] = None
|
|
||||||
nowPlaying: Optional[NowPlaying] = None
|
|
||||||
searchResult: Optional[SearchResult] = None
|
|
||||||
searchResult2: Optional[SearchResult2] = None
|
|
||||||
searchResult3: Optional[SearchResult3] = None
|
|
||||||
playlists: Optional[Playlists] = None
|
|
||||||
playlist: Optional[PlaylistWithSongs] = None
|
|
||||||
jukeboxStatus: Optional[JukeboxStatus] = None
|
|
||||||
jukeboxPlaylist: Optional[JukeboxPlaylist] = None
|
|
||||||
license: Optional[License] = None
|
|
||||||
users: Optional[Users] = None
|
|
||||||
user: Optional[User] = None
|
|
||||||
chatMessages: Optional[ChatMessages] = None
|
|
||||||
albumList: Optional[AlbumList] = None
|
|
||||||
albumList2: Optional[AlbumList2] = None
|
|
||||||
randomSongs: Optional[Songs] = None
|
|
||||||
songsByGenre: Optional[Songs] = None
|
|
||||||
lyrics: Optional[Lyrics] = None
|
|
||||||
podcasts: Optional[Podcasts] = None
|
|
||||||
newestPodcasts: Optional[NewestPodcasts] = None
|
|
||||||
internetRadioStations: Optional[InternetRadioStations] = None
|
|
||||||
bookmarks: Optional[Bookmarks] = None
|
|
||||||
playQueue: Optional[PlayQueue] = None
|
|
||||||
shares: Optional[Shares] = None
|
|
||||||
starred: Optional[Starred] = None
|
|
||||||
starred2: Optional[Starred2] = None
|
|
||||||
albumInfo: Optional[AlbumInfo] = None
|
|
||||||
artistInfo: Optional[ArtistInfo] = None
|
|
||||||
artistInfo2: Optional[ArtistInfo2] = None
|
|
||||||
similarSongs: Optional[SimilarSongs] = None
|
|
||||||
similarSongs2: Optional[SimilarSongs2] = None
|
|
||||||
topSongs: Optional[TopSongs] = None
|
|
||||||
scanStatus: Optional[ScanStatus] = None
|
|
||||||
error: Optional[Error] = None
|
|
||||||
value: Optional[str] = None
|
|
||||||
status: Optional[ResponseStatus] = None
|
|
||||||
version: Optional[Version] = None
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
return getattr(self, key, default)
|
|
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ from typing import Any
|
|||||||
from gi.repository import GObject, Gtk
|
from gi.repository import GObject, Gtk
|
||||||
|
|
||||||
from sublime.config import AppConfiguration, ServerConfiguration
|
from sublime.config import AppConfiguration, ServerConfiguration
|
||||||
from sublime.server import Server
|
|
||||||
from sublime.ui.common import EditFormDialog, IconButton
|
from sublime.ui.common import EditFormDialog, IconButton
|
||||||
|
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ class EditServerDialog(EditFormDialog):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
test_server = Gtk.Button(label="Test Connection to Server")
|
test_server = Gtk.Button(label="Test Connection to Server")
|
||||||
test_server.connect("clicked", self.on_test_server_clicked)
|
# test_server.connect("clicked", self.on_test_server_clicked)
|
||||||
|
|
||||||
open_in_browser = Gtk.Button(label="Open in Browser")
|
open_in_browser = Gtk.Button(label="Open in Browser")
|
||||||
open_in_browser.connect("clicked", self.on_open_in_browser_clicked)
|
open_in_browser.connect("clicked", self.on_open_in_browser_clicked)
|
||||||
@@ -35,43 +34,43 @@ class EditServerDialog(EditFormDialog):
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def on_test_server_clicked(self, event: Any):
|
# def on_test_server_clicked(self, event: Any):
|
||||||
# Instantiate the server.
|
# # Instantiate the server.
|
||||||
server_address = self.data["server_address"].get_text()
|
# server_address = self.data["server_address"].get_text()
|
||||||
server = Server(
|
# server = Server(
|
||||||
name=self.data["name"].get_text(),
|
# name=self.data["name"].get_text(),
|
||||||
hostname=server_address,
|
# hostname=server_address,
|
||||||
username=self.data["username"].get_text(),
|
# username=self.data["username"].get_text(),
|
||||||
password=self.data["password"].get_text(),
|
# password=self.data["password"].get_text(),
|
||||||
disable_cert_verify=self.data["disable_cert_verify"].get_active(),
|
# disable_cert_verify=self.data["disable_cert_verify"].get_active(),
|
||||||
)
|
# )
|
||||||
|
|
||||||
# Try to ping, and show a message box with whether or not it worked.
|
# # Try to ping, and show a message box with whether or not it worked.
|
||||||
try:
|
# try:
|
||||||
server.ping()
|
# server.ping()
|
||||||
dialog = Gtk.MessageDialog(
|
# dialog = Gtk.MessageDialog(
|
||||||
transient_for=self,
|
# transient_for=self,
|
||||||
message_type=Gtk.MessageType.INFO,
|
# message_type=Gtk.MessageType.INFO,
|
||||||
buttons=Gtk.ButtonsType.OK,
|
# buttons=Gtk.ButtonsType.OK,
|
||||||
text="Connection to server successful.",
|
# text="Connection to server successful.",
|
||||||
)
|
# )
|
||||||
dialog.format_secondary_markup(
|
# dialog.format_secondary_markup(
|
||||||
f"Connection to {server_address} successful."
|
# f"Connection to {server_address} successful."
|
||||||
)
|
# )
|
||||||
except Exception as err:
|
# except Exception as err:
|
||||||
dialog = Gtk.MessageDialog(
|
# dialog = Gtk.MessageDialog(
|
||||||
transient_for=self,
|
# transient_for=self,
|
||||||
message_type=Gtk.MessageType.ERROR,
|
# message_type=Gtk.MessageType.ERROR,
|
||||||
buttons=Gtk.ButtonsType.OK,
|
# buttons=Gtk.ButtonsType.OK,
|
||||||
text="Connection to server unsuccessful.",
|
# text="Connection to server unsuccessful.",
|
||||||
)
|
# )
|
||||||
dialog.format_secondary_markup(
|
# dialog.format_secondary_markup(
|
||||||
f"Connection to {server_address} resulted in the following "
|
# f"Connection to {server_address} resulted in the following "
|
||||||
f"error:\n\n{err}"
|
# f"error:\n\n{err}"
|
||||||
)
|
# )
|
||||||
|
|
||||||
dialog.run()
|
# dialog.run()
|
||||||
dialog.destroy()
|
# dialog.destroy()
|
||||||
|
|
||||||
def on_open_in_browser_clicked(self, event: Any):
|
def on_open_in_browser_clicked(self, event: Any):
|
||||||
subprocess.call(["xdg-open", self.data["server_address"].get_text()])
|
subprocess.call(["xdg-open", self.data["server_address"].get_text()])
|
||||||
|
Reference in New Issue
Block a user