Repository: happyleavesaoc/python-snapcast

Issue:
Snapcast 0.16.x features
Hi, I just wanted to document that there's some new features in snapcast 0.16.0 that would be great to integrate into this project.  See https://github.com/badaix/snapcast/blob/master/changelog.md#version-0160

Notable changes:
* Streams can be added and removed
* Group names can be changed

Maybe the control interface could expose additional functionality if the sever version is higher than 0.16.0?  I may be able to write up a PR in the next few days.

Discussion:
luar123: Depends on #66. I will wait until this one is merged.
happyleavesaoc: Merged #66, will release after this one is merged too.

Patch:
diff --git a/snapcast/control/server.py b/snapcast/control/server.py
index 80bfdfe..0bfc537 100644
--- a/snapcast/control/server.py
+++ b/snapcast/control/server.py
@@ -44,6 +44,8 @@ STREAM_CONTROL = 'Stream.Control'  # not yet implemented
 STREAM_SETMETA = 'Stream.SetMeta'  # deprecated
 STREAM_ONUPDATE = 'Stream.OnUpdate'
 STREAM_ONMETA = 'Stream.OnMetadata'  # deprecated
+STREAM_ADDSTREAM = 'Stream.AddStream'
+STREAM_REMOVESTREAM = 'Stream.RemoveStream'
 
 SERVER_RECONNECT_DELAY = 5
 
@@ -55,12 +57,15 @@ _METHODS = [SERVER_GETSTATUS, SERVER_GETRPCVERSION, SERVER_DELETECLIENT,
             SERVER_DELETECLIENT, CLIENT_GETSTATUS, CLIENT_SETNAME,
             CLIENT_SETLATENCY, CLIENT_SETVOLUME,
             GROUP_GETSTATUS, GROUP_SETMUTE, GROUP_SETSTREAM, GROUP_SETCLIENTS,
-            GROUP_SETNAME, STREAM_SETMETA, STREAM_SETPROPERTY, STREAM_CONTROL]
+            GROUP_SETNAME, STREAM_SETMETA, STREAM_SETPROPERTY, STREAM_CONTROL,
+            STREAM_ADDSTREAM, STREAM_REMOVESTREAM]
 
 # server versions in which new methods were added
 _VERSIONS = {
     GROUP_SETNAME: '0.16.0',
     STREAM_SETPROPERTY: '0.26.0',
+    STREAM_ADDSTREAM: '0.16.0',
+    STREAM_REMOVESTREAM: '0.16.0',
 }
 
 
@@ -243,6 +248,21 @@ class Snapserver():
             'value': value
             })
 
+    async def stream_add_stream(self, stream_uri):
+        """Add a stream."""
+        params = {"streamUri": stream_uri}
+        result, error = await self._transact(STREAM_ADDSTREAM, params)
+        if (isinstance(result, dict) and ("id" in result)):
+            self.synchronize((await self.status())[0])
+        return result or error
+
+    async def stream_remove_stream(self, identifier):
+        """Remove a Stream."""
+        result = await self._request(STREAM_REMOVESTREAM, identifier)
+        if (isinstance(result, dict) and ("id" in result)):
+            self.synchronize((await self.status())[0])
+        return result
+
     def group(self, group_identifier):
         """Get a group."""
         return self._groups[group_identifier]
diff --git a/snapcast/control/stream.py b/snapcast/control/stream.py
index 25420e2..d9a663e 100644
--- a/snapcast/control/stream.py
+++ b/snapcast/control/stream.py
@@ -46,6 +46,11 @@ class Snapstream():
         """Get properties."""
         return self._stream.get('properties')
 
+    @property
+    def path(self):
+        """Get stream path."""
+        return self._stream.get('uri').get('path')
+
     def update(self, data):
         """Update stream."""
         self._stream = data


Test patch:
diff --git a/tests/test_server.py b/tests/test_server.py
index d3ae935..01ac701 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -101,11 +101,11 @@ return_values = {
               {
                   'clients': []
               }
-          ],
-          'server': SERVER_STATUS,  # DeleteClient calls synchronize
-          'streams': [
-          ]
-       }
+              ],
+            'server': SERVER_STATUS,  # DeleteClient calls synchronize
+            'streams': [
+                ]
+            }
     },
     'Group.GetStatus': {
         'group': {
@@ -124,7 +124,13 @@ return_values = {
     'Stream.SetMeta': {
         'foo': 'bar'
     },
-    'Stream.SetProperty': 'ok'
+    'Stream.SetProperty': 'ok',
+    'Stream.AddStream': {
+        'id': 'stream 2'
+    },
+    'Stream.RemoveStream': {
+        'id': 'stream 2'
+    },
 }
 
 
@@ -228,6 +234,18 @@ class TestSnapserver(unittest.TestCase):
         result = self._run(self.server.stream_setproperty('stream', 'foo', 'bar'))
         self.assertEqual(result, 'ok')
 
+    @mock.patch.object(Snapserver, '_transact', new=mock_transact('Stream.AddStream'))
+    @mock.patch.object(Snapserver, 'synchronize', new=MagicMock())
+    def test_stream_addstream(self):
+        result = self._run(self.server.stream_add_stream('pipe:///tmp/test?name=stream 2'))
+        self.assertDictEqual(result, {'id': 'stream 2'})
+
+    @mock.patch.object(Snapserver, '_transact', new=mock_transact('Stream.RemoveStream'))
+    @mock.patch.object(Snapserver, 'synchronize', new=MagicMock())
+    def test_stream_removestream(self):
+        result = self._run(self.server.stream_remove_stream('stream 2'))
+        self.assertDictEqual(result, {'id': 'stream 2'})
+
     def test_synchronize(self):
         status = copy.deepcopy(return_values.get('Server.GetStatus'))
         status['server']['server']['snapserver']['version'] = '0.12'
diff --git a/tests/test_stream.py b/tests/test_stream.py
index 8e20c91..d4f3623 100644
--- a/tests/test_stream.py
+++ b/tests/test_stream.py
@@ -21,6 +21,7 @@ class TestSnapstream(unittest.TestCase):
             'id': 'test',
             'status': 'playing',
             'uri': {
+                'path': '/tmp/snapfifo',
                 'query': {
                     'name': ''
                 }
@@ -40,9 +41,11 @@ class TestSnapstream(unittest.TestCase):
         self.assertEqual(self.stream.status, 'playing')
         self.assertEqual(self.stream.name, '')
         self.assertEqual(self.stream.friendly_name, 'test')
+        self.assertEqual(self.stream.path, '/tmp/snapfifo')
         self.assertDictEqual(self.stream_meta.meta, {'TITLE': 'Happy!'})
         self.assertDictEqual(self.stream.properties['metadata'], {'title': 'Happy!'})
-        self.assertDictEqual(self.stream.properties, {'canControl': False, 'metadata': {'title': 'Happy!',}})
+        self.assertDictEqual(self.stream.properties,
+                             {'canControl': False, 'metadata': {'title': 'Happy!'}})
         self.assertDictEqual(self.stream.metadata, {'title': 'Happy!'})
 
     def test_update(self):