Compare commits

...

7 Commits

Author SHA1 Message Date
Sebastian Goscik
1f2a48f95e Bump version: 0.9.2 → 0.9.3 2023-07-08 16:56:23 +01:00
Sebastian Goscik
5d2391e005 Remove Arm v7 docker builds
See: https://www.linuxserver.io/blog/a-farewell-to-arm-hf
2023-07-08 16:55:12 +01:00
Sebastian Goscik
c4e9a42c1a Block all calls to protect client when the connection is
dropped and we are awaiting a reconnect
2023-07-08 16:30:09 +01:00
Sebastian Goscik
6c719c0162 Cache camera names
so an active protect connection is not need to perform actions like
uploads which don't rely on protect.
2023-07-08 15:32:47 +01:00
Sebastian Goscik
498f72a09b Bump version: 0.9.1 → 0.9.2 2023-05-24 00:45:00 +01:00
Sebastian Goscik
d0080a569b Changelog 2023-05-24 00:44:54 +01:00
Sebastian Goscik
f89388327f Fix missing event checker not ignoring unwanted cameras 2023-05-22 23:22:41 +01:00
13 changed files with 53 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.9.1
current_version = 0.9.3
commit = True
tag = True

View File

@@ -71,7 +71,7 @@ jobs:
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:${{ steps.tag_name.outputs.current_version }}, ghcr.io/${{ github.repository }}:latest

View File

@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.9.3] - 2023-07-08
### Fixed
- Queued up downloads etc now wait for dropped connections to be re-established.
## [0.9.2] - 2023-04-21
### Fixed
- Missing event checker ignoring the "ignored cameras" list
## [0.9.1] - 2023-04-21
### Added
- Added optional argument string to pass directly to the `rclone delete` command used to purge video files

View File

@@ -7,7 +7,7 @@ LABEL maintainer="ep1cman"
WORKDIR /app
COPY dist/unifi_protect_backup-0.9.1.tar.gz sdist.tar.gz
COPY dist/unifi_protect_backup-0.9.3.tar.gz sdist.tar.gz
# https://github.com/rust-lang/cargo/issues/2808
ENV CARGO_NET_GIT_FETCH_WITH_CLI=true

View File

@@ -1,6 +1,6 @@
sources = unifi_protect_backup
container_name ?= ghcr.io/ep1cman/unifi-protect-backup
container_arches ?= linux/amd64,linux/arm64,linux/arm/v7
container_arches ?= linux/amd64,linux/arm64
.PHONY: test format lint unittest coverage pre-commit clean
test: format lint unittest

17
poetry.lock generated
View File

@@ -201,6 +201,21 @@ PyYAML = "*"
requests = "*"
requests-oauthlib = "*"
[[package]]
name = "async-lru"
version = "2.0.3"
description = "Simple LRU cache for asyncio"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "async-lru-2.0.3.tar.gz", hash = "sha256:b714c9d1415fca4e264da72a9e2abc66880ce7430e03a973341f88ea4c0d4869"},
{file = "async_lru-2.0.3-py3-none-any.whl", hash = "sha256:00c0a8899c20b9c88663a47732689ff98189c9fa08ad9f734d7722f934d250b1"},
]
[package.dependencies]
typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""}
[[package]]
name = "async-timeout"
version = "4.0.2"
@@ -2421,4 +2436,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "2.0"
python-versions = ">=3.9.0,<4.0"
content-hash = "5deb339fab9bbf61b295ef5ee811d233f573f6473d83aab5e3951c7216fb2897"
content-hash = "42a25af6210e11892bc9e94fb4a4565e06f9dfe9239a8f277f4970e85de72218"

View File

@@ -1,7 +1,7 @@
[tool]
[tool.poetry]
name = "unifi_protect_backup"
version = "0.9.1"
version = "0.9.3"
homepage = "https://github.com/ep1cman/unifi-protect-backup"
description = "Python tool to backup unifi event clips in realtime."
authors = ["sebastian.goscik <sebastian@goscik.com>"]
@@ -29,6 +29,7 @@ aiosqlite = "^0.17.0"
python-dateutil = "^2.8.2"
apprise = "^1.3.0"
expiring-dict = "^1.1.0"
async-lru = "^2.0.3"
[tool.poetry.group.dev]
optional = true

View File

@@ -2,7 +2,7 @@
__author__ = """sebastian.goscik"""
__email__ = 'sebastian@goscik.com'
__version__ = '0.9.1'
__version__ = '0.9.3'
from .downloader import VideoDownloader
from .event_listener import EventListener

View File

@@ -82,6 +82,9 @@ class VideoDownloader:
self.logger.info("Starting Downloader")
while True:
try:
# Wait for unifi protect to be connected
await self._protect.connect_event.wait()
event = await self.download_queue.get()
self.current_event = event
self.logger = logging.LoggerAdapter(self.base_logger, {'event': f' [{event.id}]'})

View File

@@ -100,6 +100,7 @@ class EventListener:
if self._protect.check_ws():
logger.extra_debug("Websocket is connected.")
else:
self._protect.connect_event.clear()
logger.warning("Lost connection to Unifi Protect.")
# Unsubscribe, close the session.
@@ -125,4 +126,5 @@ class EventListener:
# Back off for a little while
await asyncio.sleep(10)
self._protect.connect_event.set()
logger.info("Re-established connection to Unifi Protect and to the websocket.")

View File

@@ -87,6 +87,8 @@ class MissingEventChecker:
event = unifi_events[event_id]
if event.start is None or event.end is None:
return False # This event is still on-going
if event.camera_id in self.ignore_cameras:
return False
if event.type is EventType.MOTION and "motion" not in self.detection_types:
return False
if event.type is EventType.RING and "ring" not in self.detection_types:
@@ -121,6 +123,9 @@ class MissingEventChecker:
logger.info("Starting Missing Event Checker")
while True:
try:
# Wait for unifi protect to be connected
await self._protect.connect_event.wait()
logger.extra_debug("Running check for missing events...")
wanted_events = await self._get_missing_events()

View File

@@ -193,6 +193,11 @@ class UnifiProtectBackup:
else:
raise ConnectionError("Failed to connect to UniFi Protect after 10 attempts")
# Add a lock to the protect client that can be used to prevent code accessing the client when it has
# lost connection
self._protect.connect_event = asyncio.Event()
self._protect.connect_event.set()
# Get a mapping of camera ids -> names
logger.info("Found cameras:")
for camera in self._protect.bootstrap.cameras.values():

View File

@@ -10,6 +10,7 @@ from apprise import NotifyType
from dateutil.relativedelta import relativedelta
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data.nvr import Event
from async_lru import alru_cache
from unifi_protect_backup import notifications
@@ -277,11 +278,17 @@ def human_readable_to_float(num: str):
return value * multiplier
# Cached so that actions like uploads can continue when the connection to the api is lost
# No max size, and a 6 hour ttl
@alru_cache(None, ttl=60 * 60 * 6)
async def get_camera_name(protect: ProtectApiClient, id: str):
"""Returns the name for the camera with the given ID.
If the camera ID is not know, it tries refreshing the cached data
"""
# Wait for unifi protect to be connected
await protect.connect_event.wait()
try:
return protect.bootstrap.cameras[id].name
except KeyError: