Started work on making adapter-defined config
This commit is contained in:
@@ -1,17 +1,35 @@
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from typing import Dict, Optional, Type
|
||||
|
||||
import yaml
|
||||
import dataclasses_json
|
||||
from dataclasses_json import dataclass_json, DataClassJsonMixin
|
||||
|
||||
from sublime.adapters import ConfigurationStore
|
||||
from sublime.ui.state import UIState
|
||||
|
||||
|
||||
# JSON decoder and encoder translations
|
||||
decoder_functions = {
|
||||
Path: (lambda p: Path(p) if p else None),
|
||||
}
|
||||
encoder_functions = {
|
||||
Path: (lambda p: str(p.resolve()) if p else None),
|
||||
}
|
||||
|
||||
for type_, translation_function in decoder_functions.items():
|
||||
dataclasses_json.cfg.global_config.decoders[type_] = translation_function
|
||||
dataclasses_json.cfg.global_config.decoders[Optional[type_]] = translation_function
|
||||
|
||||
for type_, translation_function in encoder_functions.items():
|
||||
dataclasses_json.cfg.global_config.encoders[type_] = translation_function
|
||||
dataclasses_json.cfg.global_config.encoders[Optional[type_]] = translation_function
|
||||
|
||||
|
||||
class ReplayGainType(Enum):
|
||||
NO = 0
|
||||
TRACK = 1
|
||||
@@ -30,55 +48,33 @@ class ReplayGainType(Enum):
|
||||
}[replay_gain_type.lower()]
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class ServerConfiguration:
|
||||
name: str = "Default"
|
||||
server_address: str = "http://yourhost"
|
||||
local_network_address: str = ""
|
||||
local_network_ssid: str = ""
|
||||
username: str = ""
|
||||
password: str = ""
|
||||
sync_enabled: bool = True
|
||||
disable_cert_verify: bool = False
|
||||
version: int = 0
|
||||
class ProviderConfiguration:
|
||||
id: str
|
||||
name: str
|
||||
ground_truth_adapter_type: Type
|
||||
ground_truth_adapter_config: ConfigurationStore
|
||||
caching_adapter_type: Optional[Type] = None
|
||||
caching_adapter_config: Optional[ConfigurationStore] = None
|
||||
|
||||
def migrate(self):
|
||||
self.version = 0
|
||||
|
||||
_strhash: Optional[str] = None
|
||||
|
||||
def strhash(self) -> str:
|
||||
# TODO (#197): make this configurable by the adapters the combination of the
|
||||
# hashes will be the hash dir
|
||||
"""
|
||||
Returns the MD5 hash of the server's name, server address, and
|
||||
username. This should be used whenever it's necessary to uniquely
|
||||
identify the server, rather than using the name (which is not
|
||||
necessarily unique).
|
||||
|
||||
>>> sc = ServerConfiguration(
|
||||
... name='foo',
|
||||
... server_address='bar',
|
||||
... username='baz',
|
||||
... )
|
||||
>>> sc.strhash()
|
||||
'6df23dc03f9b54cc38a0fc1483df6e21'
|
||||
"""
|
||||
if not self._strhash:
|
||||
server_info = self.name + self.server_address + self.username
|
||||
self._strhash = hashlib.md5(server_info.encode("utf-8")).hexdigest()
|
||||
return self._strhash
|
||||
self.ground_truth_adapter_type.migrate_configuration(
|
||||
self.ground_truth_adapter_config
|
||||
)
|
||||
if self.caching_adapter_type:
|
||||
self.caching_adapter_type.migrate_configuration(self.caching_adapter_config)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AppConfiguration:
|
||||
version: int = 3
|
||||
cache_location: str = ""
|
||||
class AppConfiguration(DataClassJsonMixin):
|
||||
version: int = 5
|
||||
cache_location: Optional[Path] = None
|
||||
filename: Optional[Path] = None
|
||||
|
||||
# Servers
|
||||
servers: List[ServerConfiguration] = field(default_factory=list)
|
||||
current_server_index: int = -1
|
||||
# Providers
|
||||
providers: Dict[str, ProviderConfiguration] = field(default_factory=dict)
|
||||
current_provider_id: Optional[str] = None
|
||||
|
||||
# Global Settings
|
||||
song_play_notification: bool = True
|
||||
@@ -96,19 +92,16 @@ class AppConfiguration:
|
||||
|
||||
@staticmethod
|
||||
def load_from_file(filename: Path) -> "AppConfiguration":
|
||||
args = {}
|
||||
config = AppConfiguration()
|
||||
try:
|
||||
if filename.exists():
|
||||
with open(filename, "r") as f:
|
||||
field_names = {f.name for f in fields(AppConfiguration)}
|
||||
args = yaml.load(f, Loader=yaml.CLoader).items()
|
||||
args = dict(filter(lambda kv: kv[0] in field_names, args))
|
||||
config = AppConfiguration.from_json(f.read())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
config = AppConfiguration(**args)
|
||||
config.filename = filename
|
||||
|
||||
config.save()
|
||||
return config
|
||||
|
||||
def __post_init__(self):
|
||||
@@ -116,82 +109,67 @@ class AppConfiguration:
|
||||
if not self.cache_location:
|
||||
path = Path(os.environ.get("XDG_DATA_HOME") or "~/.local/share")
|
||||
path = path.expanduser().joinpath("sublime-music").resolve()
|
||||
self.cache_location = path.as_posix()
|
||||
|
||||
# Deserialize the YAML into the ServerConfiguration object.
|
||||
if len(self.servers) > 0 and type(self.servers[0]) != ServerConfiguration:
|
||||
self.servers = [ServerConfiguration(**sc) for sc in self.servers]
|
||||
self.cache_location = str(path)
|
||||
|
||||
self._state = None
|
||||
self._current_server_hash = None
|
||||
self._current_provider_id = None
|
||||
self.migrate()
|
||||
|
||||
def migrate(self):
|
||||
for server in self.servers:
|
||||
server.migrate()
|
||||
for _, provider in self.providers.items():
|
||||
provider.migrate()
|
||||
|
||||
if self.version < 4:
|
||||
self.allow_song_downloads = not self.always_stream
|
||||
|
||||
self.version = 4
|
||||
self.version = 5
|
||||
self.state.migrate()
|
||||
|
||||
@property
|
||||
def server(self) -> Optional[ServerConfiguration]:
|
||||
if 0 <= self.current_server_index < len(self.servers):
|
||||
return self.servers[self.current_server_index]
|
||||
|
||||
return None
|
||||
def provider(self) -> Optional[ProviderConfiguration]:
|
||||
return self.providers.get(self._current_provider_id or "")
|
||||
|
||||
@property
|
||||
def state(self) -> UIState:
|
||||
server = self.server
|
||||
if not server:
|
||||
if not (provider := self.provider):
|
||||
return UIState()
|
||||
|
||||
# If the server has changed, then retrieve the new server's state.
|
||||
if self._current_server_hash != server.strhash():
|
||||
# If the provider has changed, then retrieve the new provider's state.
|
||||
if self._current_provider_id != provider.id:
|
||||
self.load_state()
|
||||
|
||||
return self._state
|
||||
|
||||
def load_state(self):
|
||||
self._state = UIState()
|
||||
if not self.server:
|
||||
if not (provider := self.provider):
|
||||
return
|
||||
|
||||
self._current_server_hash = self.server.strhash()
|
||||
if (
|
||||
state_file_location := self.state_file_location
|
||||
) and state_file_location.exists():
|
||||
self._current_provider_id = provider.id
|
||||
if (state_filename := self._state_file_location) and state_filename.exists():
|
||||
try:
|
||||
with open(state_file_location, "rb") as f:
|
||||
with open(state_filename, "rb") as f:
|
||||
self._state = pickle.load(f)
|
||||
except Exception:
|
||||
logging.warning(f"Couldn't load state from {state_file_location}")
|
||||
logging.warning(f"Couldn't load state from {state_filename}")
|
||||
# Just ignore any errors, it is only UI state.
|
||||
self._state = UIState()
|
||||
|
||||
@property
|
||||
def state_file_location(self) -> Optional[Path]:
|
||||
if self.server is None:
|
||||
def _state_file_location(self) -> Optional[Path]:
|
||||
if not (provider := self.provider):
|
||||
return None
|
||||
|
||||
server_hash = self.server.strhash()
|
||||
|
||||
state_file_location = Path(os.environ.get("XDG_DATA_HOME") or "~/.local/share")
|
||||
return state_file_location.expanduser().joinpath(
|
||||
"sublime-music", server_hash, "state.pickle"
|
||||
state_filename = Path(os.environ.get("XDG_DATA_HOME") or "~/.local/share")
|
||||
return state_filename.expanduser().joinpath(
|
||||
"sublime-music", provider.id, "state.pickle"
|
||||
)
|
||||
|
||||
def save(self):
|
||||
# Save the config as YAML.
|
||||
self.filename.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self.filename, "w+") as f:
|
||||
f.write(yaml.dump(asdict(self)))
|
||||
f.write(self.to_json(indent=2, sort_keys=True))
|
||||
|
||||
# Save the state for the current server.
|
||||
if state_file_location := self.state_file_location:
|
||||
state_file_location.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(state_file_location, "wb+") as f:
|
||||
# Save the state for the current provider.
|
||||
if state_filename := self._state_file_location:
|
||||
state_filename.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(state_filename, "wb+") as f:
|
||||
pickle.dump(self.state, f)
|
||||
|
Reference in New Issue
Block a user