mirror of
https://github.com/ep1cman/unifi-protect-backup.git
synced 2025-12-05 23:53:30 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f2a48f95e | ||
|
|
5d2391e005 | ||
|
|
c4e9a42c1a | ||
|
|
6c719c0162 | ||
|
|
498f72a09b | ||
|
|
d0080a569b | ||
|
|
f89388327f |
@@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.9.1
|
||||
current_version = 0.9.3
|
||||
commit = True
|
||||
tag = True
|
||||
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
2
makefile
2
makefile
@@ -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
17
poetry.lock
generated
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}]'})
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user