Skip to content

Commit

Permalink
Merge branch 'dev' into dependabot/pip/flake8-6.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
fronzbot committed Aug 17, 2023
2 parents 4c91a58 + 4083815 commit e75fdf6
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 24 deletions.
3 changes: 1 addition & 2 deletions blinkpy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,15 +410,14 @@ async def http_get(
:param is_retry: Is this part of a re-auth attempt?
"""
_LOGGER.debug("Making GET request to %s", url)
response = await blink.auth.query(
return await blink.auth.query(
url=url,
headers=blink.auth.header,
reqtype="get",
stream=stream,
json_resp=json,
is_retry=is_retry,
)
return response


async def http_post(blink, url, is_retry=False, data=None, json=True, timeout=TIMEOUT):
Expand Down
17 changes: 8 additions & 9 deletions blinkpy/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import copy
import string
import os
from aiofiles import open
import logging
import datetime
from json import dumps
import aiohttp
import traceback
import aiohttp
from aiofiles import open
from requests.compat import urljoin
from blinkpy import api
from blinkpy.helpers.constants import TIMEOUT_MEDIA
Expand Down Expand Up @@ -148,7 +148,7 @@ async def async_set_night_vision(self, value):
product_type=self.product_type,
data=data,
)
if res.status == 200:
if res and res.status == 200:
return await res.json()
return None

Expand Down Expand Up @@ -186,14 +186,13 @@ async def get_video_clip(self, url=None):
if not url:
_LOGGER.warning(f"Video clip URL not available: self.clip={url}")
return None
response = await api.http_get(
return await api.http_get(
self.sync.blink,
url=url,
stream=True,
json=False,
timeout=TIMEOUT_MEDIA,
)
return response

async def snap_picture(self):
"""Take a picture with camera to create a new thumbnail."""
Expand Down Expand Up @@ -326,12 +325,12 @@ def timest(record):

if new_thumbnail is not None and (update_cached_image or force_cache):
response = await self.get_media()
if response.status == 200:
if response and response.status == 200:
self._cached_image = await response.read()

if clip_addr is not None and (update_cached_video or force_cache):
response = await self.get_media(media_type="video")
if response.status == 200:
if response and response.status == 200:
self._cached_video = await response.read()

# Don't let the recent clips list grow without bound.
Expand Down Expand Up @@ -374,7 +373,7 @@ async def image_to_file(self, path):
"""
_LOGGER.debug("Writing image from %s to %s", self.name, path)
response = await self.get_media()
if response.status == 200:
if response and response.status == 200:
async with open(path, "wb") as imgfile:
await imgfile.write(await response.read())
else:
Expand Down Expand Up @@ -418,7 +417,7 @@ async def save_recent_clips(
path = os.path.join(output_dir, file_name)
_LOGGER.debug(f"Saving {clip_addr} to {path}")
media = await self.get_video_clip(clip_addr)
if media.status == 200:
if media and media.status == 200:
async with open(path, "wb") as clip_file:
await clip_file.write(await media.read())
num_saved += 1
Expand Down
2 changes: 1 addition & 1 deletion blinkpy/helpers/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ async def throttle_method():
@wraps(method)
def wrapper(*args, **kwargs):
"""Wrap that checks for throttling."""
force = kwargs.get("force", False)
force = kwargs.pop("force", False)
now = int(time.time())
last_call_delta = now - self.last_call
if force or last_call_delta > self.throttle_time:
Expand Down
48 changes: 45 additions & 3 deletions blinkpy/sync_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import datetime
import traceback
import asyncio
import aiofiles
from sortedcontainers import SortedSet
from requests.structures import CaseInsensitiveDict
from blinkpy import api
Expand Down Expand Up @@ -217,8 +218,10 @@ def get_unique_info(self, name):

async def get_events(self, **kwargs):
"""Retrieve events from server."""
kwargs.pop("force", False)
response = await api.request_sync_events(self.blink, self.network_id)
force = kwargs.pop("force", False)
response = await api.request_sync_events(
self.blink, self.network_id, force=force
)
try:
return response["event"]
except (TypeError, KeyError):
Expand Down Expand Up @@ -370,7 +373,6 @@ async def check_new_videos(self):
self._local_storage["last_manifest_read"] = last_manifest_read
_LOGGER.debug(f"Updated last_manifest_read to {last_manifest_read}")
_LOGGER.debug(f"Last clip time was {last_clip_time}")

# We want to keep the last record when no new motion was detected.
for camera in self.cameras.keys():
# Check if there are no new records, indicating motion.
Expand Down Expand Up @@ -681,6 +683,46 @@ async def prepare_download(self, blink, max_retries=4):
await asyncio.sleep(seconds)
return response

async def delete_video(self, blink, max_retries=4) -> bool:
"""Delete video from sync module."""
delete_url = blink.urls.base_url + self.url()
delete_url = delete_url.replace("request", "delete")

for retry in range(max_retries):
delete = await api.http_post(
blink, delete_url, json=False
) # Delete the video
if delete.status == 200:
return True
seconds = backoff_seconds(retry=retry, default_time=3)
_LOGGER.debug("[retry=%d] Retrying in %d seconds", retry + 1, seconds)
await asyncio.sleep(seconds)
return False

async def download_video(self, blink, file_name, max_retries=4) -> bool:
"""Download a previously prepared video from sync module."""
for retry in range(max_retries):
url = blink.urls.base_url + self.url()
video = await api.http_get(blink, url, json=False)
if video.status == 200:
async with aiofiles.open(file_name, "wb") as vidfile:
await vidfile.write(await video.read()) # download the video
return True
seconds = backoff_seconds(retry=retry, default_time=3)
_LOGGER.debug(
"[retry=%d] Retrying in %d seconds: %s", retry + 1, seconds, url
)
await asyncio.sleep(seconds)
return False

async def download_video_delete(self, blink, file_name, max_retries=4) -> bool:
"""Initiate upload of media item from the sync module to Blink cloud servers then download to local filesystem and delete from sync."""
if await self.prepare_download(blink):
if await self.download_video(blink, file_name):
if await self.delete_video(blink):
return True
return False

def __repr__(self):
"""Create string representation."""
return (
Expand Down
22 changes: 13 additions & 9 deletions blinksync/blinksync.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
import aiohttp
import sys
from sortedcontainers import SortedSet
from forms import LoginDialog, VideosForm, DELAY,CLOSE, DELETE, DOWNLOAD, REFRESH
from forms import LoginDialog, VideosForm, DELAY, CLOSE, DELETE, DOWNLOAD, REFRESH
from blinkpy.blinkpy import Blink, BlinkSyncModule
from blinkpy.auth import Auth


async def main():
"""Main loop for blink test."""
session = aiohttp.ClientSession()
Expand All @@ -20,8 +21,8 @@ async def main():
path = dlg.GetPath()
else:
sys.exit(0)
with open(f"{path}/blink.json", "rt",encoding='ascii') as j:

with open(f"{path}/blink.json", "rt", encoding="ascii") as j:
blink.auth = Auth(json.loads(j.read()), session=session)

except (StopIteration, FileNotFoundError):
Expand All @@ -33,6 +34,9 @@ async def main():
userpass,
session=session,
)
await blink.save(f"{path}/blink.json")
else:
sys.exit(0)
with wx.BusyInfo("Blink is Working....") as working:
cursor = wx.BusyCursor()
if await blink.start():
Expand All @@ -44,7 +48,9 @@ async def main():
print(f"Sync :{blink.networks}")
if len(blink.networks) == 0:
exit()
my_sync: BlinkSyncModule = blink.sync[blink.networks[list(blink.networks)[0]]['name']]
my_sync: BlinkSyncModule = blink.sync[
blink.networks[list(blink.networks)[0]]["name"]
]
cursor = None
working = None

Expand All @@ -55,7 +61,7 @@ async def main():
print(name)
print(camera.attributes)

my_sync._local_storage['manifest'] = SortedSet()
my_sync._local_storage["manifest"] = SortedSet()
await my_sync.refresh()
if my_sync.local_storage and my_sync.local_storage_manifest_ready:
print("Manifest is ready")
Expand All @@ -80,7 +86,7 @@ async def main():
continue
# Download and delete all videos from sync module
for item in reversed(manifest):
if item.id in frame.ItemList:
if item.id in frame.ItemList:
if button == DOWNLOAD:
await item.prepare_download(blink)
await item.download_video(
Expand All @@ -94,12 +100,10 @@ async def main():
working = None
frame = None
await session.close()


# Run the program
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())


87 changes: 87 additions & 0 deletions tests/test_sync_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import datetime
from unittest import IsolatedAsyncioTestCase
from unittest import mock
import aiofiles
from blinkpy.blinkpy import Blink
from blinkpy.helpers.util import BlinkURLHandler, to_alphanumeric
from blinkpy.sync_module import (
Expand All @@ -12,6 +13,7 @@
)
from blinkpy.camera import BlinkCamera
from tests.test_blink_functions import MockCamera
import tests.mock_responses as mresp


@mock.patch("blinkpy.auth.Auth.query")
Expand Down Expand Up @@ -528,3 +530,88 @@ async def test_local_storage_media_item(self, mock_resp):
self.assertEquals(
await item.prepare_download(blink, max_retries=1), {"network_id": 123456}
)

with mock.patch("blinkpy.api.http_post", return_value=""):
self.assertIsNone(await item2.prepare_download(blink, max_retries=0))

async def test_poll_local_storage_manifest(self, mock_resp):
"""Test incorrect response."""
with mock.patch("blinkpy.api.request_local_storage_manifest", return_value=""):
self.assertIsNone(
await self.blink.sync["test"].poll_local_storage_manifest(max_retries=0)
)

async def test_delete_video(self, mock_resp):
"""Test item delete."""
blink: Blink = Blink(motion_interval=0, session=mock.AsyncMock())
blink.last_refresh = 0
blink.urls = BlinkURLHandler("test")
item = LocalStorageMediaItem(
"1234",
"Backdoor",
datetime.datetime.utcnow().isoformat(),
"432",
" manifest_id",
"url",
)
mock_resp.return_value = mresp.MockResponse({"status": 200}, 200)
self.assertTrue(await item.delete_video(blink))

mock_resp.return_value = mresp.MockResponse({"status": 400}, 400)
self.assertFalse(await item.delete_video(blink, 1))

async def test_download_video(self, mock_resp):
"""Test item download."""
blink: Blink = Blink(motion_interval=0, session=mock.AsyncMock())
blink.last_refresh = 0
blink.urls = BlinkURLHandler("test")
item = LocalStorageMediaItem(
"1234",
"Backdoor",
datetime.datetime.utcnow().isoformat(),
"432",
" manifest_id",
"url",
)
mock_file = mock.MagicMock()
aiofiles.threadpool.wrap.register(mock.MagicMock)(
lambda *args, **kwargs: aiofiles.threadpool.AsyncBufferedIOBase(
*args, **kwargs
)
)
with mock.patch("aiofiles.threadpool.sync_open", return_value=mock_file):
mock_resp.return_value = mresp.MockResponse({"status": 200}, 200)
self.assertTrue(await item.download_video(blink, "filename.mp4"))

mock_resp.return_value = mresp.MockResponse({"status": 400}, 400)
self.assertFalse(await item.download_video(blink, "filename.mp4", 1))

@mock.patch("blinkpy.sync_module.LocalStorageMediaItem.download_video")
@mock.patch("blinkpy.sync_module.LocalStorageMediaItem.delete_video")
@mock.patch("blinkpy.sync_module.LocalStorageMediaItem.prepare_download")
async def test_download_delete(self, mock_prepdl, mock_del, mock_dl, mock_resp):
"""Test download and delete."""
blink: Blink = Blink(motion_interval=0, session=mock.AsyncMock())
blink.last_refresh = 0
blink.urls = BlinkURLHandler("test")
item = LocalStorageMediaItem(
"1234",
"Backdoor",
datetime.datetime.utcnow().isoformat(),
"432",
" manifest_id",
"url",
)

self.assertTrue(await item.download_video_delete(self.blink, "filename.mp4"))

mock_prepdl.return_value = False
self.assertFalse(await item.download_video_delete(self.blink, "filename.mp4"))

mock_prepdl.return_value = mock.AsyncMock()
mock_del.return_value = False
self.assertFalse(await item.download_video_delete(self.blink, "filename.mp4"))

mock_del.return_value = mock.AsyncMock()
mock_dl.return_value = False
self.assertFalse(await item.download_video_delete(self.blink, "filename.mp4"))

0 comments on commit e75fdf6

Please sign in to comment.