diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100755 index 0000000..c53f53a --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,29 @@ +ARG PYTHON_IMAGE_VARIANT="3.11" +FROM mcr.microsoft.com/devcontainers/python:${PYTHON_IMAGE_VARIANT} + +# update path to include user's .local/bin +ENV PATH="${PATH}:/home/vscode/.local/bin" + +# set workdir +WORKDIR /IdeaProjects/unifi-protect-video-downloader + +# Poetry +ARG POETRY_VERSION="1.7.0" +#RUN su vscode -c "umask 0002 && pip3 --disable-pip-version-check install poetry==${POETRY_VERSION}" +#RUN su vscode -c "umask 0002 && poetry install" + +# Nox +#ARG NOX_VERSION="none" +#RUN if [ "${NOX_VERSION}" != "none" ]; then su vscode -c "umask 0002 && pip3 install nox-poetry nox==${NOX_VERSION}"; fi + +# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. +# COPY requirements.txt /tmp/pip-tmp/ +# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ +# && rm -rf /tmp/pip-tmp + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100755 index 0000000..e41593b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,54 @@ +{ + "name": "devcontainer unifi-protect-video-downloader", + "build": { + "dockerfile": "Dockerfile", + //"context": "..", + "args": { + // set Python version: 3, 3.8, 3.9, 3.10, 3.11, 3.12 + // see https://github.com/devcontainers/images/tree/main/src/python + "PYTHON_IMAGE_VARIANT": "3.11", + + // Options + //"NODE_VERSION": "lts/*", + "POETRY_VERSION": "1.7.0" + //"NOX_VERSION": "2022.1.7" + // TODO Add esbonio>=0.11.0 (required for RST support in VSC) + } + }, + "remoteUser": "vscode" + // Set *default* container specific settings.json values on container create. +// "settings": { +// "python.defaultInterpreterPath": "/usr/local/bin/python", +// "python.linting.enabled": true, +// "python.linting.pylintEnabled": true, +// "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", +// "python.formatting.blackPath": "/usr/local/py-utils/bin/black", +// "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", +// "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", +// "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", +// "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", +// "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", +// "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", +// "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" +// }, + // Add the IDs of extensions you want installed when the container is created. +// "extensions": [ +// "ms-python.python", +// "ms-python.vscode-pylance", +// "lextudio.restructuredtext-pack" +// ], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Install project dependencies + // "postCreateCommand": "poetry install", + //"postCreateCommand": "bash ./.devcontainer/post-install.sh", + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. +// "remoteUser": "vscode", +// "features": { +// "github-cli": "latest" +// }, +// "mounts": [ +// // Re-use local Git configuration +// "source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached" +// ] +} diff --git a/.idea/misc.xml b/.idea/misc.xml index 19f2f3c..6eb5df4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,12 @@ + + - - + + \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/0/0/00dab1a387c8006844f6c2a1bf21ed4ebbebbd77 b/.idea/sonarlint/issuestore/0/0/00dab1a387c8006844f6c2a1bf21ed4ebbebbd77 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/0/3/0355cc31ee81a7cccfa758d4503b58991436e784 b/.idea/sonarlint/issuestore/0/3/0355cc31ee81a7cccfa758d4503b58991436e784 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/1/a/1a0e89b539142513322131c537cacf496800539a b/.idea/sonarlint/issuestore/1/a/1a0e89b539142513322131c537cacf496800539a new file mode 100644 index 0000000..f4ea973 --- /dev/null +++ b/.idea/sonarlint/issuestore/1/a/1a0e89b539142513322131c537cacf496800539a @@ -0,0 +1,9 @@ + +J python:S1481"-Remove the unused local variable "retry_num".(ޣ +l python:S3776"TRefactor this function to reduce its Cognitive Complexity from 43 to the 15 allowed.(̐ +Q python:S1135"4Complete the task associated to this "TODO" comment.(Ͻ +Q python:S11358"4Complete the task associated to this "TODO" comment.(ڒ +Q python:S1135X"4Complete the task associated to this "TODO" comment.( +6 python:S125^"Remove this commented out code.( +; python:S125}"Remove this commented out code.( +Q python:S1135}"4Complete the task associated to this "TODO" comment.( \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/1/e/1e4f0373b798e1c815760b0d5b71eed49df13ea5 b/.idea/sonarlint/issuestore/1/e/1e4f0373b798e1c815760b0d5b71eed49df13ea5 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/2/a/2a43b9ad377cad0478b91abe4c2681c417e684c4 b/.idea/sonarlint/issuestore/2/a/2a43b9ad377cad0478b91abe4c2681c417e684c4 new file mode 100644 index 0000000..37f0f5c --- /dev/null +++ b/.idea/sonarlint/issuestore/2/a/2a43b9ad377cad0478b91abe4c2681c417e684c4 @@ -0,0 +1,2 @@ + +v python:S1186"YAdd a nested comment explaining why this method is empty, or complete the implementation.( \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/2/a/2a58c49b461eb694f3b26b889b1cd50bb4fd00df b/.idea/sonarlint/issuestore/2/a/2a58c49b461eb694f3b26b889b1cd50bb4fd00df new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/4/e/4e4336a100ab92aff37fc784daba7865edad7708 b/.idea/sonarlint/issuestore/4/e/4e4336a100ab92aff37fc784daba7865edad7708 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/5/4/546e45000b734ff4c55feaa21773529ab39d03e7 b/.idea/sonarlint/issuestore/5/4/546e45000b734ff4c55feaa21773529ab39d03e7 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/5/8/58b340640bb77bfe293f41270871d8bf66f7c8d6 b/.idea/sonarlint/issuestore/5/8/58b340640bb77bfe293f41270871d8bf66f7c8d6 new file mode 100644 index 0000000..696eb26 --- /dev/null +++ b/.idea/sonarlint/issuestore/5/8/58b340640bb77bfe293f41270871d8bf66f7c8d6 @@ -0,0 +1,3 @@ + +q python:S1192"TDefine a constant instead of duplicating this literal "01/01/1970 13:45:00" 3 times.( +q python:S1192 "TDefine a constant instead of duplicating this literal "01/01/1970 08:30:00" 8 times.( \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/6/0/60ad601f1205282a568087703ad5761b6d2c3e59 b/.idea/sonarlint/issuestore/6/0/60ad601f1205282a568087703ad5761b6d2c3e59 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/6/3/63dace1216d419d06b324902a30d8f38cdb950de b/.idea/sonarlint/issuestore/6/3/63dace1216d419d06b324902a30d8f38cdb950de new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/6/e/6e656a06857650151f28f253233ed97584044098 b/.idea/sonarlint/issuestore/6/e/6e656a06857650151f28f253233ed97584044098 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/8/0/80ef213b27a734a128422d163fd9cc2b5caac02c b/.idea/sonarlint/issuestore/8/0/80ef213b27a734a128422d163fd9cc2b5caac02c new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/8/c/8c91c0296909b5ea3c496fde69e9dc44a6c6ae5f b/.idea/sonarlint/issuestore/8/c/8c91c0296909b5ea3c496fde69e9dc44a6c6ae5f new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/9/6/96ba827297d0e4a0ac8b70c8e16034a819a8708e b/.idea/sonarlint/issuestore/9/6/96ba827297d0e4a0ac8b70c8e16034a819a8708e new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/9/6/96cff6697223ce6e7bdb3572459607aed02e09a0 b/.idea/sonarlint/issuestore/9/6/96cff6697223ce6e7bdb3572459607aed02e09a0 new file mode 100644 index 0000000..55b3d12 --- /dev/null +++ b/.idea/sonarlint/issuestore/9/6/96cff6697223ce6e7bdb3572459607aed02e09a0 @@ -0,0 +1,4 @@ + +j python:S107"MFunction "events" has 18 parameters, which is greater than the 13 authorized.(ꈕ +L python:S1135z"4Complete the task associated to this "TODO" comment.(Ѡ +M python:S1135"4Complete the task associated to this "TODO" comment.(Ѡ \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/a/3/a36e355ddb203d7d4133221f339dc406cb9f480f b/.idea/sonarlint/issuestore/a/3/a36e355ddb203d7d4133221f339dc406cb9f480f new file mode 100644 index 0000000..43eef31 --- /dev/null +++ b/.idea/sonarlint/issuestore/a/3/a36e355ddb203d7d4133221f339dc406cb9f480f @@ -0,0 +1,3 @@ + +< python:S125"Remove this commented out code.( +7 python:S125"Remove this commented out code.( \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/b/3/b34062a34eb177d6796c9ce334de4930d86f38c1 b/.idea/sonarlint/issuestore/b/3/b34062a34eb177d6796c9ce334de4930d86f38c1 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/b/c/bc812ef5f4b68dc4beabaf0d35c4e72ae2f55071 b/.idea/sonarlint/issuestore/b/c/bc812ef5f4b68dc4beabaf0d35c4e72ae2f55071 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/c/1/c19b4caacef2f9b53d36e2e1d4e5e21bf66ee40f b/.idea/sonarlint/issuestore/c/1/c19b4caacef2f9b53d36e2e1d4e5e21bf66ee40f new file mode 100644 index 0000000..74e56f7 --- /dev/null +++ b/.idea/sonarlint/issuestore/c/1/c19b4caacef2f9b53d36e2e1d4e5e21bf66ee40f @@ -0,0 +1,2 @@ + +x python:S1186"[Add a nested comment explaining why this function is empty, or complete the implementation.( \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/c/3/c35af09099360a209222620622472ef5a0e1c902 b/.idea/sonarlint/issuestore/c/3/c35af09099360a209222620622472ef5a0e1c902 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/c/3/c3aa8b7f1c613e43d3bb9d5ac52ac6603d945e89 b/.idea/sonarlint/issuestore/c/3/c3aa8b7f1c613e43d3bb9d5ac52ac6603d945e89 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/d/0/d07a661ba0e0629828545744bc0abe3084383a06 b/.idea/sonarlint/issuestore/d/0/d07a661ba0e0629828545744bc0abe3084383a06 new file mode 100644 index 0000000..f56194e --- /dev/null +++ b/.idea/sonarlint/issuestore/d/0/d07a661ba0e0629828545744bc0abe3084383a06 @@ -0,0 +1,2 @@ + +; python:S125`"Remove this commented out code.(Ԉ \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/d/2/d20297f793a7716a1f5dc643da1d64c3dd657693 b/.idea/sonarlint/issuestore/d/2/d20297f793a7716a1f5dc643da1d64c3dd657693 new file mode 100644 index 0000000..fc9fe1a --- /dev/null +++ b/.idea/sonarlint/issuestore/d/2/d20297f793a7716a1f5dc643da1d64c3dd657693 @@ -0,0 +1,4 @@ + +i python:S107"MMethod "__init__" has 15 parameters, which is greater than the 13 authorized.( +f python:S3358$"IExtract this nested conditional expression into an independent statement.( +Q python:S1135b"4Complete the task associated to this "TODO" comment.( \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/d/2/d215ab7254ebbcd0e3a7cae9a15efddd5391374b b/.idea/sonarlint/issuestore/d/2/d215ab7254ebbcd0e3a7cae9a15efddd5391374b new file mode 100644 index 0000000..471bf6c --- /dev/null +++ b/.idea/sonarlint/issuestore/d/2/d215ab7254ebbcd0e3a7cae9a15efddd5391374b @@ -0,0 +1,5 @@ + +l python:S107"OFunction "download" has 20 parameters, which is greater than the 13 authorized.(ꈕ +L python:S1135z"4Complete the task associated to this "TODO" comment.(Ѡ +M python:S1135"4Complete the task associated to this "TODO" comment.(Ѡ +R python:S1135"4Complete the task associated to this "TODO" comment.( \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/d/a/da04ff408536214a2c765daefab125a9d1d31247 b/.idea/sonarlint/issuestore/d/a/da04ff408536214a2c765daefab125a9d1d31247 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/e/4/e483b328b0b58798ab6df381864e6a274ca21cc3 b/.idea/sonarlint/issuestore/e/4/e483b328b0b58798ab6df381864e6a274ca21cc3 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/f/2/f20929c25d7a858763a2ba93d6abb7be56207a31 b/.idea/sonarlint/issuestore/f/2/f20929c25d7a858763a2ba93d6abb7be56207a31 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/f/3/f383d0ef4dc38dcc30a2972d2e912890fd338c83 b/.idea/sonarlint/issuestore/f/3/f383d0ef4dc38dcc30a2972d2e912890fd338c83 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/f/5/f567b76f114de1bd21adf94e7e2303297670332a b/.idea/sonarlint/issuestore/f/5/f567b76f114de1bd21adf94e7e2303297670332a new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/f/7/f71a1c944128f38c816a29a5cb51902a00769f52 b/.idea/sonarlint/issuestore/f/7/f71a1c944128f38c816a29a5cb51902a00769f52 new file mode 100644 index 0000000..49a60bd --- /dev/null +++ b/.idea/sonarlint/issuestore/f/7/f71a1c944128f38c816a29a5cb51902a00769f52 @@ -0,0 +1,2 @@ + +v python:S1186"YAdd a nested comment explaining why this method is empty, or complete the implementation.( \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/f/9/f9f858f9bca5b0721bc4dd82ea481315acca9747 b/.idea/sonarlint/issuestore/f/9/f9f858f9bca5b0721bc4dd82ea481315acca9747 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/index.pb b/.idea/sonarlint/issuestore/index.pb new file mode 100644 index 0000000..4f5d774 --- /dev/null +++ b/.idea/sonarlint/issuestore/index.pb @@ -0,0 +1,67 @@ + +O +.devcontainer/devcontainer.json,b/3/b34062a34eb177d6796c9ce334de4930d86f38c1 +H +.devcontainer/Dockerfile,6/e/6e656a06857650151f28f253233ed97584044098 +S +#.github/workflows/pythonpackage.yml,0/0/00dab1a387c8006844f6c2a1bf21ed4ebbebbd77 +9 + README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d +_ +/protect_archiver/downloader/download_footage.py,b/c/bc812ef5f4b68dc4beabaf0d35c4e72ae2f55071 +N +protect_archiver/test_utils.py,5/8/58b340640bb77bfe293f41270871d8bf66f7c8d6 +S +#protect_archiver/client/unifi_os.py,0/3/0355cc31ee81a7cccfa758d4503b58991436e784 +J +protect_archiver/errors.py,2/a/2a43b9ad377cad0478b91abe4c2681c417e684c4 +S +#protect_archiver/client/__init__.py,d/2/d20297f793a7716a1f5dc643da1d64c3dd657693 +H +protect_archiver/sync.py,2/a/2a58c49b461eb694f3b26b889b1cd50bb4fd00df +L +protect_archiver/__init__.py,5/4/546e45000b734ff4c55feaa21773529ab39d03e7 +O +protect_archiver/test_client.py,e/4/e483b328b0b58798ab6df381864e6a274ca21cc3 +\ +,protect_archiver/downloader/download_file.py,1/a/1a0e89b539142513322131c537cacf496800539a +d +4protect_archiver/downloader/download_motion_event.py,f/9/f9f858f9bca5b0721bc4dd82ea481315acca9747 +` +0protect_archiver/downloader/download_snapshot.py,f/3/f383d0ef4dc38dcc30a2972d2e912890fd338c83 +^ +.protect_archiver/downloader/get_camera_list.py,6/0/60ad601f1205282a568087703ad5761b6d2c3e59 +P + protect_archiver/cli/download.py,d/2/d215ab7254ebbcd0e3a7cae9a15efddd5391374b +N +protect_archiver/cli/events.py,9/6/96cff6697223ce6e7bdb3572459607aed02e09a0 +L +protect_archiver/cli/base.py,c/1/c19b4caacef2f9b53d36e2e1d4e5e21bf66ee40f +L +protect_archiver/cli/sync.py,f/5/f567b76f114de1bd21adf94e7e2303297670332a +d +4protect_archiver/downloader/get_motion_event_list.py,9/6/96ba827297d0e4a0ac8b70c8e16034a819a8708e +W +'protect_archiver/downloader/__init__.py,f/2/f20929c25d7a858763a2ba93d6abb7be56207a31 +P + protect_archiver/cli/__init__.py,6/3/63dace1216d419d06b324902a30d8f38cdb950de +O +protect_archiver/dataclasses.py,8/0/80ef213b27a734a128422d163fd9cc2b5caac02c +I +protect_archiver/utils.py,d/0/d07a661ba0e0629828545744bc0abe3084383a06 +J +protect_archiver/config.py,f/7/f71a1c944128f38c816a29a5cb51902a00769f52 +Q +!protect_archiver/client/legacy.py,d/a/da04ff408536214a2c765daefab125a9d1d31247 +U +%.github/workflows/codeql-analysis.yml,c/3/c3aa8b7f1c613e43d3bb9d5ac52ac6603d945e89 +Q +!.github/workflows/dockerbuild.yml,c/3/c35af09099360a209222620622472ef5a0e1c902 +; + conftest.py,1/e/1e4f0373b798e1c815760b0d5b71eed49df13ea5 +7 +test.py,a/3/a36e355ddb203d7d4133221f339dc406cb9f480f +9 + setup.cfg,8/c/8c91c0296909b5ea3c496fde69e9dc44a6c6ae5f +8 +mypy.ini,4/e/4e4336a100ab92aff37fc784daba7865edad7708 \ No newline at end of file diff --git a/.idea/sonarlint/securityhotspotstore/0/0/00dab1a387c8006844f6c2a1bf21ed4ebbebbd77 b/.idea/sonarlint/securityhotspotstore/0/0/00dab1a387c8006844f6c2a1bf21ed4ebbebbd77 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/0/3/0355cc31ee81a7cccfa758d4503b58991436e784 b/.idea/sonarlint/securityhotspotstore/0/3/0355cc31ee81a7cccfa758d4503b58991436e784 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/1/a/1a0e89b539142513322131c537cacf496800539a b/.idea/sonarlint/securityhotspotstore/1/a/1a0e89b539142513322131c537cacf496800539a new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/1/e/1e4f0373b798e1c815760b0d5b71eed49df13ea5 b/.idea/sonarlint/securityhotspotstore/1/e/1e4f0373b798e1c815760b0d5b71eed49df13ea5 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/2/a/2a43b9ad377cad0478b91abe4c2681c417e684c4 b/.idea/sonarlint/securityhotspotstore/2/a/2a43b9ad377cad0478b91abe4c2681c417e684c4 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/2/a/2a58c49b461eb694f3b26b889b1cd50bb4fd00df b/.idea/sonarlint/securityhotspotstore/2/a/2a58c49b461eb694f3b26b889b1cd50bb4fd00df new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/4/e/4e4336a100ab92aff37fc784daba7865edad7708 b/.idea/sonarlint/securityhotspotstore/4/e/4e4336a100ab92aff37fc784daba7865edad7708 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/5/4/546e45000b734ff4c55feaa21773529ab39d03e7 b/.idea/sonarlint/securityhotspotstore/5/4/546e45000b734ff4c55feaa21773529ab39d03e7 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/5/8/58b340640bb77bfe293f41270871d8bf66f7c8d6 b/.idea/sonarlint/securityhotspotstore/5/8/58b340640bb77bfe293f41270871d8bf66f7c8d6 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/6/0/60ad601f1205282a568087703ad5761b6d2c3e59 b/.idea/sonarlint/securityhotspotstore/6/0/60ad601f1205282a568087703ad5761b6d2c3e59 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/6/3/63dace1216d419d06b324902a30d8f38cdb950de b/.idea/sonarlint/securityhotspotstore/6/3/63dace1216d419d06b324902a30d8f38cdb950de new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/6/e/6e656a06857650151f28f253233ed97584044098 b/.idea/sonarlint/securityhotspotstore/6/e/6e656a06857650151f28f253233ed97584044098 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/8/0/80ef213b27a734a128422d163fd9cc2b5caac02c b/.idea/sonarlint/securityhotspotstore/8/0/80ef213b27a734a128422d163fd9cc2b5caac02c new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/8/c/8c91c0296909b5ea3c496fde69e9dc44a6c6ae5f b/.idea/sonarlint/securityhotspotstore/8/c/8c91c0296909b5ea3c496fde69e9dc44a6c6ae5f new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/9/6/96ba827297d0e4a0ac8b70c8e16034a819a8708e b/.idea/sonarlint/securityhotspotstore/9/6/96ba827297d0e4a0ac8b70c8e16034a819a8708e new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/9/6/96cff6697223ce6e7bdb3572459607aed02e09a0 b/.idea/sonarlint/securityhotspotstore/9/6/96cff6697223ce6e7bdb3572459607aed02e09a0 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/a/3/a36e355ddb203d7d4133221f339dc406cb9f480f b/.idea/sonarlint/securityhotspotstore/a/3/a36e355ddb203d7d4133221f339dc406cb9f480f new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/b/3/b34062a34eb177d6796c9ce334de4930d86f38c1 b/.idea/sonarlint/securityhotspotstore/b/3/b34062a34eb177d6796c9ce334de4930d86f38c1 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/b/c/bc812ef5f4b68dc4beabaf0d35c4e72ae2f55071 b/.idea/sonarlint/securityhotspotstore/b/c/bc812ef5f4b68dc4beabaf0d35c4e72ae2f55071 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/c/1/c19b4caacef2f9b53d36e2e1d4e5e21bf66ee40f b/.idea/sonarlint/securityhotspotstore/c/1/c19b4caacef2f9b53d36e2e1d4e5e21bf66ee40f new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/c/3/c35af09099360a209222620622472ef5a0e1c902 b/.idea/sonarlint/securityhotspotstore/c/3/c35af09099360a209222620622472ef5a0e1c902 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/c/3/c3aa8b7f1c613e43d3bb9d5ac52ac6603d945e89 b/.idea/sonarlint/securityhotspotstore/c/3/c3aa8b7f1c613e43d3bb9d5ac52ac6603d945e89 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/d/0/d07a661ba0e0629828545744bc0abe3084383a06 b/.idea/sonarlint/securityhotspotstore/d/0/d07a661ba0e0629828545744bc0abe3084383a06 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/d/2/d20297f793a7716a1f5dc643da1d64c3dd657693 b/.idea/sonarlint/securityhotspotstore/d/2/d20297f793a7716a1f5dc643da1d64c3dd657693 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/d/2/d215ab7254ebbcd0e3a7cae9a15efddd5391374b b/.idea/sonarlint/securityhotspotstore/d/2/d215ab7254ebbcd0e3a7cae9a15efddd5391374b new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/d/a/da04ff408536214a2c765daefab125a9d1d31247 b/.idea/sonarlint/securityhotspotstore/d/a/da04ff408536214a2c765daefab125a9d1d31247 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/e/4/e483b328b0b58798ab6df381864e6a274ca21cc3 b/.idea/sonarlint/securityhotspotstore/e/4/e483b328b0b58798ab6df381864e6a274ca21cc3 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/f/2/f20929c25d7a858763a2ba93d6abb7be56207a31 b/.idea/sonarlint/securityhotspotstore/f/2/f20929c25d7a858763a2ba93d6abb7be56207a31 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/f/3/f383d0ef4dc38dcc30a2972d2e912890fd338c83 b/.idea/sonarlint/securityhotspotstore/f/3/f383d0ef4dc38dcc30a2972d2e912890fd338c83 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/f/5/f567b76f114de1bd21adf94e7e2303297670332a b/.idea/sonarlint/securityhotspotstore/f/5/f567b76f114de1bd21adf94e7e2303297670332a new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/f/7/f71a1c944128f38c816a29a5cb51902a00769f52 b/.idea/sonarlint/securityhotspotstore/f/7/f71a1c944128f38c816a29a5cb51902a00769f52 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/f/9/f9f858f9bca5b0721bc4dd82ea481315acca9747 b/.idea/sonarlint/securityhotspotstore/f/9/f9f858f9bca5b0721bc4dd82ea481315acca9747 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/index.pb b/.idea/sonarlint/securityhotspotstore/index.pb new file mode 100644 index 0000000..4f5d774 --- /dev/null +++ b/.idea/sonarlint/securityhotspotstore/index.pb @@ -0,0 +1,67 @@ + +O +.devcontainer/devcontainer.json,b/3/b34062a34eb177d6796c9ce334de4930d86f38c1 +H +.devcontainer/Dockerfile,6/e/6e656a06857650151f28f253233ed97584044098 +S +#.github/workflows/pythonpackage.yml,0/0/00dab1a387c8006844f6c2a1bf21ed4ebbebbd77 +9 + README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d +_ +/protect_archiver/downloader/download_footage.py,b/c/bc812ef5f4b68dc4beabaf0d35c4e72ae2f55071 +N +protect_archiver/test_utils.py,5/8/58b340640bb77bfe293f41270871d8bf66f7c8d6 +S +#protect_archiver/client/unifi_os.py,0/3/0355cc31ee81a7cccfa758d4503b58991436e784 +J +protect_archiver/errors.py,2/a/2a43b9ad377cad0478b91abe4c2681c417e684c4 +S +#protect_archiver/client/__init__.py,d/2/d20297f793a7716a1f5dc643da1d64c3dd657693 +H +protect_archiver/sync.py,2/a/2a58c49b461eb694f3b26b889b1cd50bb4fd00df +L +protect_archiver/__init__.py,5/4/546e45000b734ff4c55feaa21773529ab39d03e7 +O +protect_archiver/test_client.py,e/4/e483b328b0b58798ab6df381864e6a274ca21cc3 +\ +,protect_archiver/downloader/download_file.py,1/a/1a0e89b539142513322131c537cacf496800539a +d +4protect_archiver/downloader/download_motion_event.py,f/9/f9f858f9bca5b0721bc4dd82ea481315acca9747 +` +0protect_archiver/downloader/download_snapshot.py,f/3/f383d0ef4dc38dcc30a2972d2e912890fd338c83 +^ +.protect_archiver/downloader/get_camera_list.py,6/0/60ad601f1205282a568087703ad5761b6d2c3e59 +P + protect_archiver/cli/download.py,d/2/d215ab7254ebbcd0e3a7cae9a15efddd5391374b +N +protect_archiver/cli/events.py,9/6/96cff6697223ce6e7bdb3572459607aed02e09a0 +L +protect_archiver/cli/base.py,c/1/c19b4caacef2f9b53d36e2e1d4e5e21bf66ee40f +L +protect_archiver/cli/sync.py,f/5/f567b76f114de1bd21adf94e7e2303297670332a +d +4protect_archiver/downloader/get_motion_event_list.py,9/6/96ba827297d0e4a0ac8b70c8e16034a819a8708e +W +'protect_archiver/downloader/__init__.py,f/2/f20929c25d7a858763a2ba93d6abb7be56207a31 +P + protect_archiver/cli/__init__.py,6/3/63dace1216d419d06b324902a30d8f38cdb950de +O +protect_archiver/dataclasses.py,8/0/80ef213b27a734a128422d163fd9cc2b5caac02c +I +protect_archiver/utils.py,d/0/d07a661ba0e0629828545744bc0abe3084383a06 +J +protect_archiver/config.py,f/7/f71a1c944128f38c816a29a5cb51902a00769f52 +Q +!protect_archiver/client/legacy.py,d/a/da04ff408536214a2c765daefab125a9d1d31247 +U +%.github/workflows/codeql-analysis.yml,c/3/c3aa8b7f1c613e43d3bb9d5ac52ac6603d945e89 +Q +!.github/workflows/dockerbuild.yml,c/3/c35af09099360a209222620622472ef5a0e1c902 +; + conftest.py,1/e/1e4f0373b798e1c815760b0d5b71eed49df13ea5 +7 +test.py,a/3/a36e355ddb203d7d4133221f339dc406cb9f480f +9 + setup.cfg,8/c/8c91c0296909b5ea3c496fde69e9dc44a6c6ae5f +8 +mypy.ini,4/e/4e4336a100ab92aff37fc784daba7865edad7708 \ No newline at end of file diff --git a/.idea/unifi-protect-video-downloader.iml b/.idea/unifi-protect-video-downloader.iml index 21a31ba..a2b3e9e 100644 --- a/.idea/unifi-protect-video-downloader.iml +++ b/.idea/unifi-protect-video-downloader.iml @@ -9,10 +9,13 @@ - + + + - + \ No newline at end of file diff --git a/compose-dev.yaml b/compose-dev.yaml new file mode 100644 index 0000000..a92f701 --- /dev/null +++ b/compose-dev.yaml @@ -0,0 +1,12 @@ +services: + app: + entrypoint: + - sleep + - infinity + image: docker/dev-environments-default:stable-1 + init: true + volumes: + - type: bind + source: /var/run/docker.sock + target: /var/run/docker.sock + diff --git a/poetry.lock b/poetry.lock index d5adbc0..d781e9b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -709,13 +709,13 @@ files = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.2" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"}, + {file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"}, ] [package.dependencies] @@ -836,4 +836,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.8.2" -content-hash = "734389a91fcc42f0ed953eb6b459a444bc256bfd51531248b6a45253731a9706" +content-hash = "d7286391b51abd60020adee22a61e4ed4a6db94e9d5d8ec6377c75cb4ee35375" diff --git a/protect_archiver/downloader/download_footage.py b/protect_archiver/downloader/download_footage.py index 2db80c2..ea63809 100644 --- a/protect_archiver/downloader/download_footage.py +++ b/protect_archiver/downloader/download_footage.py @@ -42,8 +42,8 @@ def download_footage( time.sleep(int(client.download_wait)) # start and end time of the video segment to be downloaded - js_timestamp_range_start = int(interval_start.timestamp()) * 1000 - js_timestamp_range_end = int(interval_end.timestamp()) * 1000 + js_timestamp_range_start = int(interval_start.timestamp() * 1e3) + js_timestamp_range_end = int(interval_end.timestamp() * 1e3) # support selection between local time zone and UTC for file names interval_start_tz = ( diff --git a/protect_archiver/downloader/download_motion_event.py b/protect_archiver/downloader/download_motion_event.py index 10b5e52..1d6b771 100644 --- a/protect_archiver/downloader/download_motion_event.py +++ b/protect_archiver/downloader/download_motion_event.py @@ -17,8 +17,8 @@ def download_motion_event( camera_name_fs_safe = make_camera_name_fs_safe(camera) # start and end time of the video segment to be downloaded - js_timestamp_start = int(motion_event.start.timestamp()) * 1000 - js_timestamp_end = int(motion_event.end.timestamp()) * 1000 + js_timestamp_start = int(motion_event.start.timestamp() * 1e3) + js_timestamp_end = int(motion_event.end.timestamp() * 1e3) # support selection between local time zone and UTC for file names interval_start_tz = ( diff --git a/protect_archiver/downloader/download_snapshot.py b/protect_archiver/downloader/download_snapshot.py index 697adde..6568922 100644 --- a/protect_archiver/downloader/download_snapshot.py +++ b/protect_archiver/downloader/download_snapshot.py @@ -39,7 +39,7 @@ def download_snapshot(client: Any, start: datetime, camera: Camera) -> None: logging.debug(f"Argument '--touch-files' is present. Creating file at {filename}") open(filename, "a").close() - js_timestamp_start = int(start.timestamp()) * 1000 + js_timestamp_start = int(start.timestamp() * 1e3) # build snapshot export API address snapshot_export_query = f"/cameras/{camera.id}/snapshot?ts={js_timestamp_start}" diff --git a/pyproject.toml b/pyproject.toml index 132ac04..38b233d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ classifiers = [ python = "^3.8.2" pip = "*" python-dateutil = "^2.9.0" -requests = {extras = ["security"], version = "^2.31.0"} +requests = {extras = ["security"], version = "^2.32.2"} click = "^8.1.7" types-python-dateutil = "^2.9.0" types-requests = "^2.31.0" diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 0000000..84e3e49 --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,29 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-python:latest diff --git a/test.py b/test.py new file mode 100644 index 0000000..fc9f32a --- /dev/null +++ b/test.py @@ -0,0 +1,189 @@ +import logging +import os + +from datetime import datetime +from datetime import timedelta +from typing import Any +from typing import Iterable +from typing import Tuple + + +import dateutil.parser + + +# return time difference between given date_time_object and next full hour +def diff_round_up_to_full_hour(date_time_object: datetime) -> datetime: + if date_time_object.minute != 0 or date_time_object.second != 0: + return date_time_object.replace( + second=0, microsecond=0, minute=0, hour=date_time_object.hour + ) + timedelta(hours=1, minutes=0) + else: + return date_time_object.replace( + second=0, microsecond=0, minute=0, hour=date_time_object.hour + ) + timedelta(hours=0, minutes=0) + + +# return time difference between given date_time_object and past full hour +def diff_round_down_to_full_hour(date_time_object: datetime) -> datetime: + return date_time_object.replace( + second=0, microsecond=0, minute=0, hour=date_time_object.hour + ) + timedelta(hours=0, minutes=0) + + +def calculate_intervals( + start: datetime, + end: datetime, + disable_alignment: bool = False, + disable_splitting: bool = False, +) -> Iterable[Tuple[datetime, datetime]]: + # if true, do not split into 1-hour segments + # Caution: this can cause the Protect application to crash and restart! + if disable_splitting: + yield start, end - timedelta(milliseconds=1) # yield everything at once + return # exit early + + # if true, disable alignment to absolute hours + if disable_alignment: + # divide total duration by 60 min, yield 1-hour segments + for _ in range(int((end - start).seconds / 3600)): + yield start, start + timedelta(minutes=59, seconds=59, milliseconds=999) + start = start + timedelta(hours=1) + yield start, end - timedelta(milliseconds=1) # yield remaining segment + return # exit early + + ##### + # if none of the options above were used, calculate 1-hour segments and align them with absolute hours + ##### + + # calculate time differences to next or past full hour + start_diff_to_next_full_hour = diff_round_up_to_full_hour(start) - start + end_diff_to_past_full_hour = end - diff_round_down_to_full_hour(end) + + # save original start and end for later + original_start = start + original_end = end + + # yield interval from start to first full hour and align start to full hours + # but only if the end datetime is past the next full hour after start datetime + if ( + start_diff_to_next_full_hour.seconds != 0 + and (original_end - original_start) >= start_diff_to_next_full_hour + ): + yield start, start + (start_diff_to_next_full_hour - timedelta(milliseconds=1)) + start = start + start_diff_to_next_full_hour # update start time + + # yield all remaining full-hour intervals + for _ in range(int((end - start).total_seconds() / 3600)): + yield start, start + timedelta(minutes=59, seconds=59, milliseconds=999) + start = start + timedelta(minutes=60) # update start time + + # if end is not on full hour, yield remaining segment + if end_diff_to_past_full_hour.seconds != 0: + yield start, original_end - timedelta(milliseconds=1) + + + +print( + list( + calculate_intervals( + dateutil.parser.parse("01.01.1970 08:56:00"), + dateutil.parser.parse("01.01.1970 13:21:00"), + disable_splitting=True, + ) + ) +) + +# 15 min within the hour: OK +print( + list( + calculate_intervals( + dateutil.parser.parse("01.01.1970 08:30:00"), + dateutil.parser.parse("01.01.1970 08:45:00"), + False, + ) + ) +) + +# 30 min crossing the hour: OK +print( + list( + calculate_intervals( + dateutil.parser.parse("01.01.1970 08:30:00"), + dateutil.parser.parse("01.01.1970 09:15:00"), + False, + ) + ) +) + +# 15 min to full hour: OK +print( + list( + calculate_intervals( + dateutil.parser.parse("01.01.1970 08:30:00"), + dateutil.parser.parse("01.01.1970 09:00:00"), + False, + ) + ) +) + +# 15 min and 1 sec after the hour: FAIL +print( + list( + calculate_intervals( + dateutil.parser.parse("01.01.1970 08:30:00"), + dateutil.parser.parse("01.01.1970 09:00:01"), + False, + ) + ) +) +print( + list( + calculate_intervals( + dateutil.parser.parse("01.01.1970 08:30:00"), + dateutil.parser.parse("01.01.1970 09:00:08"), + False, + ) + ) +) + + +print( + list( + calculate_intervals( + dateutil.parser.parse("01.01.1970 08:30:00"), + dateutil.parser.parse("01.01.1970 13:45:00"), + False, + ) + ) +) + +print( + list( + calculate_intervals( + dateutil.parser.parse("01.01.1970 21:30:00"), + dateutil.parser.parse("01.02.1970 02:45:00"), + False, + ) + ) +) + +print( + list( + calculate_intervals( + dateutil.parser.parse("1/8/2020 23:00:00"), + dateutil.parser.parse("1/8/2020 23:59:00"), + False, + ) + ) +) + +print(int(dateutil.parser.parse("01.01.1970 08:30:00.995").timestamp() * 1e3)) +# print(diff_round_up_to_full_hour(dateutil.parser.parse('01.01.1970 08:30:00'))) +# print(diff_round_up_to_full_hour(dateutil.parser.parse('01.01.1970 08:59:00'))) +# print(diff_round_up_to_full_hour(dateutil.parser.parse('01.01.1970 08:59:52'))) +# print(diff_round_up_to_full_hour(dateutil.parser.parse('01.01.1970 08:59:59'))) + +# print(diff_round_down_to_full_hour(dateutil.parser.parse('01.01.1970 08:30:00'))) +# print(diff_round_down_to_full_hour(dateutil.parser.parse('01.01.1970 08:59:00'))) +# print(diff_round_down_to_full_hour(dateutil.parser.parse('01.01.1970 08:59:52'))) +# print(diff_round_down_to_full_hour(dateutil.parser.parse('01.01.1970 08:59:59'))) diff --git a/testresults.xml b/testresults.xml new file mode 100644 index 0000000..af1fec1 --- /dev/null +++ b/testresults.xml @@ -0,0 +1 @@ + \ No newline at end of file