From 6a32dbeb8357646f0e709e6866959f441ddf0265 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sat, 4 Jun 2016 15:37:29 -0700 Subject: [PATCH] Fix NVR communication by 'id' for version >= 3.2.x This makes us resolve names to the 'id' instead of 'uuid' for newer versions of the NVR server software. Fixes most of #1 --- tests/test_client.py | 63 ++++++++++++++++++++++++++++++++++++++++++++ uvcclient/nvr.py | 20 ++++++++++++-- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index b127d63..c720037 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,6 +23,13 @@ def setUp(self): http_mock = mock.patch('http.client.HTTPConnection') http_mock.start() self._patches.append(http_mock) + bootstrap_mock = mock.patch.object(nvr.UVCRemote, '_get_bootstrap', + side_effect=self._bootstrap) + bootstrap_mock.start() + self._patches.append(bootstrap_mock) + + def _bootstrap(self): + return {'systemInfo': {'version': '3.1.3'}} def cleanUp(self): for i in self._patches: @@ -95,7 +102,63 @@ def test_uvc_request_deflated(self): None, headers) +class TestClient32(unittest.TestCase): + @mock.patch.object(nvr.UVCRemote, '_get_bootstrap') + def test_bootstrap_server_version(self, mock_bootstrap): + mock_bootstrap.return_value = {'systemInfo': {'version': '3.4.5'}} + client = nvr.UVCRemote('foo', 7080, 'key') + self.assertEqual((3, 4, 5), client.server_version) + + @mock.patch.object(nvr.UVCRemote, '_get_bootstrap') + @mock.patch.object(nvr.UVCRemote, 'index') + def test_310_returns_uuid(self, mock_index, mock_bootstrap): + mock_index.return_value = [{ + 'name': mock.sentinel.name, + 'uuid': mock.sentinel.uuid, + 'id': mock.sentinel.id, + }] + mock_bootstrap.return_value = {'systemInfo': {'version': '3.1.0'}} + client = nvr.UVCRemote('foo', 7080, 'key') + self.assertEqual(mock.sentinel.uuid, client.name_to_uuid( + mock.sentinel.name)) + + @mock.patch.object(nvr.UVCRemote, '_get_bootstrap') + @mock.patch.object(nvr.UVCRemote, 'index') + def test_320_returns_uuid(self, mock_index, mock_bootstrap): + mock_index.return_value = [{ + 'name': mock.sentinel.name, + 'uuid': mock.sentinel.uuid, + 'id': mock.sentinel.id, + }] + mock_bootstrap.return_value = {'systemInfo': {'version': '3.2.0'}} + client = nvr.UVCRemote('foo', 7080, 'key') + self.assertEqual(mock.sentinel.id, client.name_to_uuid( + mock.sentinel.name)) + + class TestClient(unittest.TestCase): + def setUp(self): + super(TestClient, self).setUp() + self._patches = [] + try: + import httplib + http_mock = mock.patch('httplib.HTTPConnection') + except ImportError: + http_mock = mock.patch('http.client.HTTPConnection') + http_mock.start() + self._patches.append(http_mock) + bootstrap_mock = mock.patch.object(nvr.UVCRemote, '_get_bootstrap', + side_effect=self._bootstrap) + bootstrap_mock.start() + self._patches.append(bootstrap_mock) + + def _bootstrap(self): + return {'systemInfo': {'version': '3.1.3'}} + + def cleanUp(self): + for i in self._patches: + i.stop() + def test_set_recordmode(self): fake_resp1 = {'data': [{'recordingSettings': { 'fullTimeRecordEnabled': False, diff --git a/uvcclient/nvr.py b/uvcclient/nvr.py index d1acb2c..05d0661 100755 --- a/uvcclient/nvr.py +++ b/uvcclient/nvr.py @@ -59,6 +59,15 @@ def __init__(self, host, port, apikey, path='/'): raise Invalid('Path not supported yet') self._apikey = apikey self._log = logging.getLogger('UVC(%s:%s)' % (host, port)) + self._bootstrap = self._get_bootstrap() + version = '.'.join(str(x) for x in self.server_version) + self._log.debug('Server version is %s' % version) + + @property + def server_version(self): + return tuple( + int(x) for x in self._bootstrap['systemInfo']['version'].split('.') + ) def _safe_request(self, *args, **kwargs): try: @@ -110,6 +119,9 @@ def _uvc_request_safe(self, path, method='GET', data=None, data = zlib.decompress(data, 32 + zlib.MAX_WBITS) return json.loads(data.decode()) + def _get_bootstrap(self): + return self._uvc_request('/api/2.0/bootstrap')['data'][0] + def dump(self, uuid): """Dump information for a camera by UUID.""" data = self._uvc_request('/api/2.0/camera/%s' % uuid) @@ -187,6 +199,7 @@ def index(self): 'uuid': x['uuid'], 'state': x['state'], 'managed': x['managed'], + 'id': x['_id'], } for x in cams] def name_to_uuid(self, name): @@ -194,10 +207,13 @@ def name_to_uuid(self, name): :param name: Camera name :returns: The UUID of the first camera with the same name if found, - otherwise None + otherwise None. On v3.2.0 and later, returns id. """ cameras = self.index() - cams_by_name = {x['name']: x['uuid'] for x in cameras} + if self.server_version >= (3, 2, 0): + cams_by_name = {x['name']: x['id'] for x in cameras} + else: + cams_by_name = {x['name']: x['uuid'] for x in cameras} return cams_by_name.get(name) def get_camera(self, uuid):