Working on config saving

This commit is contained in:
Sumner Evans
2019-06-05 22:54:27 -06:00
parent 3eaa7ada25
commit 4a79aab3bd
6 changed files with 187 additions and 91 deletions

View File

@@ -1,4 +1,19 @@
from typing import Any, Dict, List, Optional
import json
from libremsonic.from_json import from_json
class ServerConfiguration:
name: str
server_address: str
local_network_address: str
local_network_ssid: str
username: str
password: str
browse_by_tags: bool
sync_enabled: bool
def __init__(self,
name='Default',
server_address='http://yourhost',
@@ -17,3 +32,35 @@ class ServerConfiguration:
self.password = password
self.browse_by_tags = browse_by_tags
self.sync_enabled = sync_enabled
class AppConfiguration:
servers: List[ServerConfiguration]
current_server: Optional[int]
def to_json(self):
return {
'servers': [s.__dict__ for s in self.servers],
'current_server': self.current_server,
}
def get_config(filename: str) -> AppConfiguration:
with open(filename, 'r') as f:
try:
response_json = json.load(f)
except json.decoder.JSONDecodeError:
response_json = None
if not response_json:
default_configuration = AppConfiguration()
default_configuration.servers = []
default_configuration.current_server = None
return default_configuration
return from_json(AppConfiguration, response_json)
def save_config(config: AppConfiguration, filename: str):
with open(filename, 'w+') as f:
f.write(json.dumps(config.to_json(), indent=2, sort_keys=True))

71
libremsonic/from_json.py Normal file
View File

@@ -0,0 +1,71 @@
from datetime import datetime
import typing
from typing import Dict, List, Type
from dateutil import parser
def from_json(cls, data):
"""
Converts data from a JSON parse into Python data structures.
Arguments:
cls: the template class to deserialize into
data: the data to deserialize to the class
"""
# Approach for deserialization here:
# https://stackoverflow.com/a/40639688/2319844
# If it's a forward reference, evaluate it to figure out the actual
# type. This allows for types that have to be put into a string.
if isinstance(cls, typing.ForwardRef):
cls = cls._evaluate(globals(), locals())
annotations: Dict[str, Type] = getattr(cls, '__annotations__', {})
# Handle primitive of objects
if data is None:
instance = None
elif cls == str:
instance = data
elif cls == int:
instance = int(data)
elif cls == bool:
instance = bool(data)
elif cls == datetime:
instance = parser.parse(data)
# Handle generics. List[*], Dict[*, *] in particular.
elif type(cls) == typing._GenericAlias:
# Having to use this because things changed in Python 3.7.
class_name = cls._name
# TODO: this is not very elegant since it doesn't allow things which
# sublass from List or Dict.
if class_name == 'List':
list_type = cls.__args__[0]
instance: List[list_type] = list()
for value in data:
instance.append(from_json(list_type, value))
elif class_name == 'Dict':
key_type, val_type = cls.__args__
instance: Dict[key_type, val_type] = dict()
for key, value in data.items():
key = from_json(key_type, key)
value = from_json(val_type, value)
instance[key] = value
else:
raise Exception(
'Trying to deserialize an unsupported type: {cls._name}')
# Handle everything else by first instantiating the class, then adding
# all of the sub-elements, recursively calling from_json on them.
else:
instance: cls = cls()
for field, field_type in annotations.items():
value = data.get(field)
setattr(instance, field, from_json(field_type, value))
return instance

View File

@@ -2,3 +2,5 @@
This module defines a stateless server which interops with the Subsonic API.
"""
from .server import Server
__all__ = ('Server', )

View File

@@ -1,73 +1,7 @@
import typing
from typing import Dict, List, Any, Type
from datetime import datetime
from dateutil import parser
from typing import Any, Dict, List
def _from_json(cls, data):
"""
Converts data from a JSON parse into Python data structures.
Arguments:
cls: the template class to deserialize into
data: the data to deserialize to the class
"""
# Approach for deserialization here:
# https://stackoverflow.com/a/40639688/2319844
# If it's a forward reference, evaluate it to figure out the actual
# type. This allows for types that have to be put into a string.
if isinstance(cls, typing.ForwardRef):
cls = cls._evaluate(globals(), locals())
annotations: Dict[str, Type] = getattr(cls, '__annotations__', {})
# Handle primitive of objects
if data is None:
instance = None
elif cls == str:
instance = data
elif cls == int:
instance = int(data)
elif cls == bool:
instance = bool(data)
elif cls == datetime:
instance = parser.parse(data)
# Handle generics. List[*], Dict[*, *] in particular.
elif type(cls) == typing._GenericAlias:
# Having to use this because things changed in Python 3.7.
class_name = cls._name
# TODO: this is not very elegant since it doesn't allow things which
# sublass from List or Dict.
if class_name == 'List':
list_type = cls.__args__[0]
instance: List[list_type] = list()
for value in data:
instance.append(_from_json(list_type, value))
elif class_name == 'Dict':
key_type, val_type = cls.__args__
instance: Dict[key_type, val_type] = dict()
for key, value in data.items():
key = _from_json(key_type, key)
value = _from_json(val_type, value)
instance[key] = value
else:
raise Exception(
'Trying to deserialize an unsupported type: {cls._name}')
# Handle everything else by first instantiating the class, then adding
# all of the sub-elements, recursively calling from_json on them.
else:
instance: cls = cls()
for field, field_type in annotations.items():
value = data.get(field)
setattr(instance, field, _from_json(field_type, value))
return instance
from libremsonic.from_json import from_json as _from_json
class APIObject:

View File

@@ -1,7 +1,11 @@
import os
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gio, Gtk
from libremsonic.config import get_config, save_config
from .main import MainWindow
from .configure_servers import ConfigureServersDialog
@@ -16,7 +20,7 @@ class LibremsonicApp(Gtk.Application):
self.window = None
# TODO load this from the config file
self.current_server = None
self.config = None
def do_startup(self):
Gtk.Application.do_startup(self)
@@ -35,13 +39,29 @@ class LibremsonicApp(Gtk.Application):
self.window.show_all()
self.window.present()
if not self.current_server:
self.load_settings()
if self.config.current_server is None:
self.show_configure_servers_dialog()
print('current config', self.config)
def on_configure_servers(self, action, param):
self.show_configure_servers_dialog()
def on_server_list_changed(self, action, params):
server_config, *_ = params
self.save_settings()
def show_configure_servers_dialog(self):
dialog = ConfigureServersDialog(self.window)
dialog = ConfigureServersDialog(self.window, self.config.servers)
dialog.connect('server-list-changed', self.on_server_list_changed)
dialog.run()
dialog.destroy()
def load_settings(self):
self.config = get_config(os.path.expanduser('~/tmp/test.json'))
def save_settings(self):
save_config(self.config, os.path.expanduser('~/tmp/test.json'))

View File

@@ -1,7 +1,9 @@
import gi
import subprocess
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gtk, GObject
from libremsonic.server import Server
from libremsonic.config import ServerConfiguration
@@ -31,26 +33,29 @@ class EditServerDialog(Gtk.Dialog):
# Create all of the text entry fields for the server configuration.
text_fields = [
('Name', existing_config.name),
('Server address', existing_config.server_address),
('Local network address', existing_config.local_network_address),
('Local network SSID', existing_config.local_network_ssid),
('Username', existing_config.username),
('Password', existing_config.password),
('Name', existing_config.name, False),
('Server address', existing_config.server_address, False),
('Local network address', existing_config.local_network_address,
False),
('Local network SSID', existing_config.local_network_ssid, False),
('Username', existing_config.username, False),
('Password', existing_config.password, True),
]
for label, value in text_fields:
for label, value, is_password in text_fields:
entry_label = Gtk.Label(label + ':')
entry_label.set_halign(Gtk.Align.START)
label_box.pack_start(entry_label, True, True, 0)
entry = Gtk.Entry(text=value)
if is_password:
entry.set_visibility(False)
entry_box.pack_start(entry, True, True, 0)
self.data[label] = entry
# Create all of the check box fields for the server configuration.
boolean_fields = [
('Browse by tags', existing_config.browse_by_tags),
('Sync Enabled', existing_config.sync_enabled),
('Sync enabled', existing_config.sync_enabled),
]
for label, value in boolean_fields:
entry_label = Gtk.Label(label + ':')
@@ -67,28 +72,41 @@ class EditServerDialog(Gtk.Dialog):
button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
test_server = Gtk.Button('Test Connection to Server')
# TODO: connect to ping
test_server.connect('clicked', self.on_test_server_clicked)
button_box.pack_start(test_server, False, True, 5)
open_in_browser = Gtk.Button('Open in Browser')
# TODO: connect to open in browser
open_in_browser.connect('clicked', self.on_open_in_browser_clicked)
button_box.pack_start(open_in_browser, False, True, 5)
content_area.pack_start(button_box, True, True, 10)
self.show_all()
def on_test_server_clicked(self, event):
server = Server(
self.data['Name'].get_text(),
self.data['Server address'].get_text(),
self.data['Username'].get_text(),
self.data['Password'].get_text(),
)
server.ping()
def on_open_in_browser_clicked(self, event):
subprocess.call(['xdg-open', self.data['Server address'].get_text()])
class ConfigureServersDialog(Gtk.Dialog):
def __init__(self, parent):
__gsignals__ = {
'server-list-changed': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE,
(object, ))
}
def __init__(self, parent, server_configs):
Gtk.Dialog.__init__(self, 'Configure Servers', parent, 0, ())
# TODO: DEBUG DATA
self.server_configs = [
ServerConfiguration(name='ohea'),
ServerConfiguration()
]
self.server_configs = server_configs
self.set_default_size(400, 250)
flowbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -126,6 +144,8 @@ class ConfigureServersDialog(Gtk.Dialog):
content_area = self.get_content_area()
content_area.pack_start(flowbox, True, True, 10)
self.server_list_on_selected_rows_changed(None)
self.show_all()
def refresh_server_list(self):
@@ -139,7 +159,6 @@ class ConfigureServersDialog(Gtk.Dialog):
row.add(server_name_label)
self.server_list.add(row)
print(self.server_list, self.server_configs)
self.show_all()
def on_remove_clicked(self, event):
@@ -147,6 +166,7 @@ class ConfigureServersDialog(Gtk.Dialog):
if selected:
del self.server_configs[selected.get_index()]
self.refresh_server_list()
self.emit('server-list-changed', self.server_configs)
def on_edit_clicked(self, event, add):
if add:
@@ -167,6 +187,8 @@ class ConfigureServersDialog(Gtk.Dialog):
),
username=dialog.data['Username'].get_text(),
password=dialog.data['Password'].get_text(),
browse_by_tags=dialog.data['Browse by tags'].get_active(),
sync_enabled=dialog.data['Sync enabled'].get_active(),
)
if add:
@@ -174,9 +196,9 @@ class ConfigureServersDialog(Gtk.Dialog):
else:
self.server_configs[selected_index] = new_config
print([x.name for x in self.server_configs])
self.refresh_server_list()
self.emit('server-list-changed', self.server_configs)
dialog.destroy()
def server_list_on_selected_rows_changed(self, event):