diff --git a/.gitignore b/.gitignore
index 0e46bbe..9ba370d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
flatpak/flatpak_build_dir/
flatpak/sublime-music.flatpak
+sublime/adapters/subsonic/api_specs/
# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.1.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.1.0.xsd
deleted file mode 100644
index 04349b2..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.1.0.xsd
+++ /dev/null
@@ -1,156 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.1.1.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.1.1.xsd
deleted file mode 100644
index d9f440a..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.1.1.xsd
+++ /dev/null
@@ -1,157 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.10.2.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.10.2.xsd
deleted file mode 100644
index a409c45..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.10.2.xsd
+++ /dev/null
@@ -1,500 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.11.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.11.0.xsd
deleted file mode 100644
index d2ccf24..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.11.0.xsd
+++ /dev/null
@@ -1,548 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.12.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.12.0.xsd
deleted file mode 100644
index 6104348..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.12.0.xsd
+++ /dev/null
@@ -1,563 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.13.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.13.0.xsd
deleted file mode 100644
index 30845f1..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.13.0.xsd
+++ /dev/null
@@ -1,588 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.14.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.14.0.xsd
deleted file mode 100644
index 47fbfdc..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.14.0.xsd
+++ /dev/null
@@ -1,632 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.15.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.15.0.xsd
deleted file mode 100644
index 44d5103..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.15.0.xsd
+++ /dev/null
@@ -1,638 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.16.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.16.0.xsd
deleted file mode 100644
index 590ff89..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.16.0.xsd
+++ /dev/null
@@ -1,638 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.16.1.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.16.1.xsd
deleted file mode 100644
index f610574..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.16.1.xsd
+++ /dev/null
@@ -1,640 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.2.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.2.0.xsd
deleted file mode 100644
index 5970c60..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.2.0.xsd
+++ /dev/null
@@ -1,199 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.3.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.3.0.xsd
deleted file mode 100644
index 1865afa..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.3.0.xsd
+++ /dev/null
@@ -1,214 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.4.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.4.0.xsd
deleted file mode 100644
index 65c9ea5..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.4.0.xsd
+++ /dev/null
@@ -1,226 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.5.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.5.0.xsd
deleted file mode 100644
index 756c578..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.5.0.xsd
+++ /dev/null
@@ -1,227 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.6.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.6.0.xsd
deleted file mode 100644
index 5f1f398..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.6.0.xsd
+++ /dev/null
@@ -1,306 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.7.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.7.0.xsd
deleted file mode 100644
index 070690c..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.7.0.xsd
+++ /dev/null
@@ -1,319 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.8.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.8.0.xsd
deleted file mode 100644
index ba404c5..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.8.0.xsd
+++ /dev/null
@@ -1,448 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_object_generator/api_specs/subsonic-rest-api-1.9.0.xsd b/api_object_generator/api_specs/subsonic-rest-api-1.9.0.xsd
deleted file mode 100644
index 1a80d96..0000000
--- a/api_object_generator/api_specs/subsonic-rest-api-1.9.0.xsd
+++ /dev/null
@@ -1,488 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/sublime/adapters/adapter_base.py b/sublime/adapters/adapter_base.py
index ce73450..921bf38 100644
--- a/sublime/adapters/adapter_base.py
+++ b/sublime/adapters/adapter_base.py
@@ -1,16 +1,16 @@
import abc
from dataclasses import dataclass
+from pathlib import Path
from typing import (
Any,
Dict,
Iterable,
- List,
+ Sequence,
Optional,
- Type,
Tuple,
+ Type,
Union,
)
-from pathlib import Path
from .api_objects import (
Playlist,
@@ -187,7 +187,7 @@ class Adapter(abc.ABC):
# These properties determine if what things the adapter can be used to do
# at the current moment.
# =========================================================================
- def get_playlists(self) -> List[Playlist]:
+ def get_playlists(self) -> Sequence[Playlist]:
"""
Gets a list of all of the :class:`sublime.adapter.api_objects.Playlist`
objects known to the adapter.
diff --git a/sublime/adapters/adapter_manager.py b/sublime/adapters/adapter_manager.py
index d0c812a..8308b32 100644
--- a/sublime/adapters/adapter_manager.py
+++ b/sublime/adapters/adapter_manager.py
@@ -156,6 +156,7 @@ class AdapterManager:
def can_get_playlists() -> bool:
# It only matters that the ground truth one can service the request.
return (
+ AdapterManager._instance is not None and
AdapterManager._instance.ground_truth_adapter.can_service_requests
and
AdapterManager._instance.ground_truth_adapter.can_get_playlists)
diff --git a/sublime/adapters/api_objects.py b/sublime/adapters/api_objects.py
index 6c19e90..c050b51 100644
--- a/sublime/adapters/api_objects.py
+++ b/sublime/adapters/api_objects.py
@@ -1,40 +1,37 @@
"""
Defines the objects that are returned by adapter methods.
"""
-from dataclasses import dataclass
+import abc
from datetime import datetime, timedelta
-from typing import List, Optional
+from typing import Optional, Sequence
-@dataclass(frozen=True)
-class Song:
+class Song(abc.ABC):
id: str
-@dataclass(frozen=True)
-class Playlist:
+class Playlist(abc.ABC):
id: str
name: str
- song_count: Optional[int] = None
- duration: Optional[timedelta] = None
- created: Optional[datetime] = None
- changed: Optional[datetime] = None
- comment: Optional[str] = None
- owner: Optional[str] = None
- public: Optional[bool] = None
- cover_art: Optional[str] = None
+ song_count: Optional[int]
+ duration: Optional[timedelta]
+ created: Optional[datetime]
+ changed: Optional[datetime]
+ comment: Optional[str]
+ owner: Optional[str]
+ public: Optional[bool]
+ cover_art: Optional[str]
-@dataclass(frozen=True)
-class PlaylistDetails:
+class PlaylistDetails(abc.ABC):
id: str
name: str
song_count: int
duration: timedelta
- songs: List[Song]
- created: Optional[datetime] = None
- changed: Optional[datetime] = None
- comment: Optional[str] = None
- owner: Optional[str] = None
- public: Optional[bool] = None
- cover_art: Optional[str] = None
+ songs: Sequence[Song]
+ created: Optional[datetime]
+ changed: Optional[datetime]
+ comment: Optional[str]
+ owner: Optional[str]
+ public: Optional[bool]
+ cover_art: Optional[str]
diff --git a/sublime/adapters/filesystem/adapter.py b/sublime/adapters/filesystem/adapter.py
index 77bf439..18d2066 100644
--- a/sublime/adapters/filesystem/adapter.py
+++ b/sublime/adapters/filesystem/adapter.py
@@ -1,7 +1,7 @@
import logging
from dataclasses import asdict
from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Dict, Sequence, Optional, Tuple
from playhouse.sqliteq import SqliteQueueDatabase
@@ -60,7 +60,7 @@ class FilesystemAdapter(CachingAdapter):
# =========================================================================
can_get_playlists: bool = True
- def get_playlists(self) -> List[Playlist]:
+ def get_playlists(self) -> Sequence[Playlist]:
playlists = list(database.Playlist.select())
if len(playlists) == 0: # TODO not necessarily a cache miss
raise CacheMissError()
diff --git a/sublime/adapters/subsonic/adapter.py b/sublime/adapters/subsonic/adapter.py
index c1c1283..ea23195 100644
--- a/sublime/adapters/subsonic/adapter.py
+++ b/sublime/adapters/subsonic/adapter.py
@@ -1,19 +1,16 @@
+import json
import logging
import os
import re
from datetime import datetime, timedelta
from pathlib import Path
from time import sleep
-from typing import Any, Dict, List, Optional, Union
+from typing import Any, Dict, Sequence, Optional, Tuple, Union
import requests
-from sublime.adapters.api_objects import (
- Playlist,
- PlaylistDetails,
- Song,
-)
-from .. import Adapter, ConfigParamDescriptor
+from .api_objects import Response
+from .. import Adapter, api_objects as API, ConfigParamDescriptor
class SubsonicAdapter(Adapter):
@@ -47,14 +44,20 @@ class SubsonicAdapter(Adapter):
self.hostname = config['server_address']
self.username = config['username']
self.password = config['password']
- self.disable_cert_verify = config['disable_cert_verify']
+ self.disable_cert_verify = config.get('disable_cert_verify')
+
+ # TODO support XML | JSON
# Availability Properties
# =========================================================================
@property
def can_service_requests(self) -> bool:
- # TODO: detect ping
- return True
+ try:
+ self._get_json('ping', timeout=2)
+ return True
+ except Exception:
+ logging.exception(f'Could not connect to {self.hostname}')
+ return False
# Helper mothods for making requests
# =========================================================================
@@ -74,8 +77,12 @@ class SubsonicAdapter(Adapter):
def _make_url(self, endpoint: str) -> str:
return f'{self.hostname}/rest/{endpoint}.view'
- # def _get(self, url, timeout=(3.05, 2), **params):
- def _get(self, url: str, **params) -> Any:
+ def _get(
+ self,
+ url: str,
+ timeout: Union[float, Tuple[float, float], None] = None,
+ **params,
+ ) -> Any:
params = {**self._get_params(), **params}
logging.info(f'[START] get: {url}')
@@ -90,12 +97,16 @@ class SubsonicAdapter(Adapter):
if type(v) == datetime:
params[k] = int(v.timestamp() * 1000)
+ if self._is_mock:
+ return self._get_mock_data()
+
result = requests.get(
url,
params=params,
verify=not self.disable_cert_verify,
- # timeout=timeout,
+ timeout=timeout,
)
+
# TODO (#122): make better
if result.status_code != 200:
raise Exception(f'[FAIL] get: {url} status={result.status_code}')
@@ -106,8 +117,8 @@ class SubsonicAdapter(Adapter):
def _get_json(
self,
url: str,
- **params: Union[None, str, datetime, int, List[int]],
- ) -> Dict[str, Any]:
+ **params: Union[None, str, datetime, int, Sequence[int]],
+ ) -> Response:
"""
Make a get request to a *Sonic REST API. Handle all types of errors
including *Sonic ```` responses.
@@ -129,47 +140,57 @@ class SubsonicAdapter(Adapter):
)
raise Exception(f'Subsonic API Error #{code}: {message}')
- return subsonic_response
+ logging.debug(f'Response from {url}', subsonic_response)
+ return Response.from_dict(subsonic_response)
- _snake_case_re = re.compile(r'(? Dict[str, Any]:
- return {
- self._snake_case_re.sub('_', k).lower(): v
- for k, v in obj.items()
- }
+ def _set_mock_data(self, data: Any):
+ class MockResult:
+ def __init__(self, content: Any):
+ self._content = content
+
+ def content(self) -> Any:
+ return self._content
+
+ def json(self) -> Any:
+ return json.loads(self._content)
+
+ def get_mock_data() -> Any:
+ if type(data) == Exception:
+ raise data
+ return MockResult(data)
+
+ self._get_mock_data = get_mock_data
# Data Retrieval Methods
# =========================================================================
can_get_playlists = True
- def get_playlists(self) -> List[Playlist]:
- result = [
- {
- **p,
- 'duration':
- timedelta(
- seconds=p['duration']) if p.get('duration') else None,
- } for p in self._get_json(self._make_url('getPlaylists')).get(
- 'playlists', {}).get('playlist')
- ]
- return [Playlist(**self._to_snake_case(p)) for p in result]
+ def get_playlists(self) -> Sequence[API.Playlist]:
+ response = self._get_json(self._make_url('getPlaylists')).playlists
+ if not response:
+ return []
+ return response.playlist
can_get_playlist_details = True
def get_playlist_details(
self,
playlist_id: str,
- ) -> PlaylistDetails:
+ ) -> API.PlaylistDetails:
result = self._get_json(
self._make_url('getPlaylist'),
id=playlist_id,
- ).get('playlist')
+ ).playlist
print(result)
- assert result
- result['duration'] = result.get('duration') or sum(
- s.get('duration') or 0 for s in result['entry'])
- result['songCount'] = result.get('songCount') or len(result['entry'])
- songs = [Song(id=s['id']) for s in result['entry']]
- del result['entry']
- return PlaylistDetails(**self._to_snake_case(result), songs=songs)
+ return result
+ # assert result
+ # result['duration'] = result.get('duration') or sum(
+ # s.get('duration') or 0 for s in result['entry'])
+ # result['songCount'] = result.get('songCount') or len(result['entry'])
+ # songs = [Song(id=s['id']) for s in result['entry']]
+ # del result['entry']
+ # return API.PlaylistDetails(**self._to_snake_case(result), songs=songs)
diff --git a/sublime/adapters/subsonic/api_objects.py b/sublime/adapters/subsonic/api_objects.py
new file mode 100644
index 0000000..e229a30
--- /dev/null
+++ b/sublime/adapters/subsonic/api_objects.py
@@ -0,0 +1,110 @@
+"""
+These are the API objects that are returned by Subsonic.
+"""
+
+from dataclasses import dataclass, field
+from datetime import datetime, timedelta
+from enum import Enum
+from typing import List, Optional, Sequence
+
+from dataclasses_json import config, dataclass_json, DataClassJsonMixin, LetterCase
+from dateutil import parser
+from marshmallow import fields
+
+from .. import api_objects as SublimeAPI
+
+datetime_metadata = config(
+ encoder=datetime.isoformat,
+ decoder=lambda d: parser.parse(d) if d else None,
+ mm_field=fields.DateTime(format='iso'),
+)
+
+timedelta_metadata = config(
+ encoder=datetime.isoformat,
+ decoder=lambda s: timedelta(seconds=s) if s else None,
+ mm_field=fields.TimeDelta(),
+)
+
+
+@dataclass_json(letter_case=LetterCase.CAMEL)
+@dataclass(frozen=True)
+class Child(SublimeAPI.Song, DataClassJsonMixin):
+ 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
+ content_type: Optional[str] = None
+ suffix: Optional[str] = None
+ transcoded_content_type: Optional[str] = None
+ transcoded_suffix: Optional[str] = None
+ duration: Optional[int] = None
+ bit_rate: Optional[int] = None
+ path: Optional[str] = None
+ isVideo: Optional[bool] = None
+ # userRating: Optional[UserRating] = None
+ # averageRating: Optional[AverageRating] = None
+ play_count: Optional[int] = None
+ disc_number: Optional[int] = None
+ created: Optional[datetime] = None
+ starred: Optional[datetime] = None
+ albumId: Optional[str] = None
+ artistId: Optional[str] = None
+ # type_: Optional[MediaType] = None
+ bookmark_position: Optional[int] = None
+ original_width: Optional[int] = None
+ original_height: Optional[int] = None
+
+
+@dataclass_json(letter_case=LetterCase.CAMEL)
+@dataclass
+class Playlist(SublimeAPI.Playlist):
+ id: str
+ name: str
+ song_count: Optional[int] = None
+ duration: Optional[timedelta] = field(
+ default=None, metadata=timedelta_metadata)
+ created: Optional[datetime] = field(
+ default=None, metadata=datetime_metadata)
+ changed: Optional[datetime] = field(
+ default=None, metadata=datetime_metadata)
+ comment: Optional[str] = None
+ owner: Optional[str] = None
+ public: Optional[bool] = None
+ cover_art: Optional[str] = None
+
+
+@dataclass_json(letter_case=LetterCase.CAMEL)
+@dataclass
+class PlaylistWithSongs(SublimeAPI.PlaylistDetails):
+ duration: timedelta = field(
+ default_factory=timedelta, metadata=timedelta_metadata)
+ songs: List[SublimeAPI.Song] = field(
+ default_factory=list,
+ metadata=config(field_name='entry'),
+ )
+ created: Optional[datetime] = field(
+ default=None, metadata=datetime_metadata)
+ changed: Optional[datetime] = field(
+ default=None, metadata=datetime_metadata)
+
+
+@dataclass(frozen=True)
+class Playlists(DataClassJsonMixin):
+ playlist: List[Playlist] = field(default_factory=list)
+
+
+@dataclass(frozen=True)
+class Response(DataClassJsonMixin):
+ """
+ The base Subsonic response object.
+ """
+ song: Optional[Child] = None
+ playlists: Optional[Playlists] = None
+ playlist: Optional[PlaylistWithSongs] = None
+ value: Optional[str] = None
diff --git a/tests/adapter_tests/adapter_api_object_tests.py b/tests/adapter_tests/adapter_api_object_tests.py
deleted file mode 100644
index b5786ac..0000000
--- a/tests/adapter_tests/adapter_api_object_tests.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import pytest
-
-from datetime import datetime, timedelta
-
-from sublime.adapters.api_objects import (Playlist, PlaylistDetails)
-
-
-def test_playlist_inheritance():
- Playlist('foo', 'Bar')
-
- PlaylistDetails('foo', 'bar', 3, timedelta(seconds=720))
diff --git a/tests/adapter_tests/adapter_base_tests.py b/tests/adapter_tests/adapter_base_tests.py
deleted file mode 100644
index ed4a2c3..0000000
--- a/tests/adapter_tests/adapter_base_tests.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import pytest
-
-from sublime.adapters import Adapter, AdapterManager
-
-
-def test_adapter_manager_singleton():
- AdapterManager.reset()
- AdapterManager.get_playlists()
-
-
-def test_functions_not_implemented():
- with pytest.raises(NotImplementedError):
- Adapter(None)
-
- class MyAdapter(Adapter):
- def __init__(self, s: dict, c: bool = False):
- pass
-
- can_be_cache: bool = True
-
- with pytest.raises(NotImplementedError):
- adapter = MyAdapter({})
- adapter.can_service_requests
-
- with pytest.raises(NotImplementedError):
- adapter = MyAdapter({})
- adapter.ingest_new_data()
-
-
-def test_override_bool():
- class MyAdapter(Adapter):
- def __init__(self, s: dict, c: bool = False):
- pass
-
- can_be_cache = True
- can_service_requests = True
-
- adapter = MyAdapter({})
- assert adapter.can_be_cache is True
- assert adapter.can_service_requests is True
-
-
-def test_override_bool_with_property():
- class MyAdapter(Adapter):
- def __init__(self, s: dict, c: bool = False):
- pass
-
- @property
- def can_be_cache(self) -> bool:
- return True
-
- @property
- def can_service_requests(self) -> bool:
- return True
-
- adapter = MyAdapter({})
- assert adapter.can_be_cache is True
- assert adapter.can_service_requests is True
diff --git a/tests/adapter_tests/sublime_adapter_tests.py b/tests/adapter_tests/sublime_adapter_tests.py
new file mode 100644
index 0000000..db547c6
--- /dev/null
+++ b/tests/adapter_tests/sublime_adapter_tests.py
@@ -0,0 +1,140 @@
+import json
+import re
+from datetime import datetime, timedelta
+from pathlib import Path
+from typing import Any, Callable, Dict, Tuple
+
+import pytest
+
+from dateutil import parser
+
+from sublime.adapters.subsonic import (
+ SubsonicAdapter, api_objects as SubsonicAPI)
+
+
+@pytest.fixture
+def subsonic_adapter(tmp_path: Path):
+ adapter = SubsonicAdapter(
+ {
+ 'server_address': 'http://localhost:4533',
+ 'username': 'test',
+ 'password': 'testpass',
+ },
+ tmp_path,
+ )
+ adapter._is_mock = True
+ yield adapter
+
+
+def mock_json(**obj: Any) -> str:
+ return json.dumps(
+ {
+ 'subsonic-response': {
+ 'status': 'ok',
+ 'version': '1.15.0',
+ **obj,
+ },
+ })
+
+
+def camel_to_snake(name: str) -> str:
+ name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
+ return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
+
+
+def test_request_making_methods(subsonic_adapter: SubsonicAdapter):
+ expected = {
+ 'u': 'test',
+ 'p': 'testpass',
+ 'c': 'Sublime Music',
+ 'f': 'json',
+ 'v': '1.15.0',
+ }
+ assert (
+ sorted(expected.items()) == sorted(
+ subsonic_adapter._get_params().items()))
+
+ assert subsonic_adapter._make_url(
+ 'foo') == 'http://localhost:4533/rest/foo.view'
+
+
+def test_can_service_requests(subsonic_adapter: SubsonicAdapter):
+ # Mock a connection error
+ subsonic_adapter._set_mock_data(Exception())
+ assert subsonic_adapter.can_service_requests is False
+
+ # Simulate some sort of ping error
+ subsonic_adapter._set_mock_data(
+ mock_json(
+ status='failed',
+ error={
+ 'code': '1',
+ 'message': 'Test message',
+ },
+ ))
+ assert subsonic_adapter.can_service_requests is False
+
+ # Simulate valid ping
+ subsonic_adapter._set_mock_data(mock_json())
+ assert subsonic_adapter.can_service_requests is True
+
+
+def test_get_playlists(subsonic_adapter: SubsonicAdapter):
+ playlists = [
+ {
+ "id": "6",
+ "name": "Playlist 1",
+ "comment": "Foo",
+ "owner": "test",
+ "public": True,
+ "songCount": 2,
+ "duration": 625,
+ "created": "2020-03-27T05:39:35.188Z",
+ "changed": "2020-04-08T00:07:01.748Z",
+ "coverArt": "pl-6"
+ },
+ {
+ "id": "7",
+ "name": "Playlist 2",
+ "comment": "",
+ "owner": "test",
+ "public": True,
+ "songCount": 3,
+ "duration": 952,
+ "created": "2020-03-27T05:39:43.327Z",
+ "changed": "2020-03-27T05:44:37.275Z",
+ "coverArt": "pl-7"
+ },
+ ]
+ subsonic_adapter._set_mock_data(
+ mock_json(playlists={
+ 'playlist': playlists,
+ }))
+
+ expected = [
+ SubsonicAPI.Playlist(
+ id='6',
+ name='Playlist 1',
+ song_count=2,
+ duration=timedelta(seconds=625),
+ created=parser.parse('2020-03-27T05:39:35.188Z'),
+ changed=parser.parse('2020-04-08T00:07:01.748Z'),
+ comment='Foo',
+ owner='test',
+ public=True,
+ cover_art='pl-6',
+ ),
+ SubsonicAPI.Playlist(
+ id='7',
+ name='Playlist 2',
+ song_count=3,
+ duration=timedelta(seconds=952),
+ created=parser.parse('2020-03-27T05:39:43.327Z'),
+ changed=parser.parse('2020-03-27T05:44:37.275Z'),
+ comment='',
+ owner='test',
+ public=True,
+ cover_art='pl-7',
+ ),
+ ]
+ assert subsonic_adapter.get_playlists() == expected