diff --git a/CHANGELOG.md b/CHANGELOG.md index ad0cf90..cbbb46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ 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.1] - 2023-04-21 +### Added +- Added optional argument string to pass directly to the `rclone delete` command used to purge video files + ## [0.9.0] - 2023-03-24 ### Added - The ability to send logging out via apprise notifications diff --git a/README.md b/README.md index 22fb478..5e32a92 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,10 @@ Options: --rclone-args TEXT Optional extra arguments to pass to `rclone rcat` directly. Common usage for this would be to set a bandwidth limit, for example. + --rclone-purge-args TEXT Optional extra arguments to pass to `rclone delete` directly. + Common usage for this would be to execute a permanent delete + instead of using the recycle bin on a destination. + Google Drive example: `--drive-use-trash=false` --detection-types TEXT A comma separated list of which types of detections to backup. Valid options are: `motion`, `person`, `vehicle`, `ring` [default: motion,person,vehicle,ring] @@ -198,6 +202,7 @@ always take priority over environment variables): - `RCLONE_RETENTION` - `RCLONE_DESTINATION` - `RCLONE_ARGS` +- `RCLONE_PURGE_ARGS` - `IGNORE_CAMERAS` - `DETECTION_TYPES` - `FILE_STRUCTURE_FORMAT` diff --git a/unifi_protect_backup/cli.py b/unifi_protect_backup/cli.py index c08e3d5..3380de9 100644 --- a/unifi_protect_backup/cli.py +++ b/unifi_protect_backup/cli.py @@ -57,6 +57,14 @@ def _parse_detection_types(ctx, param, value): help="Optional extra arguments to pass to `rclone rcat` directly. Common usage for this would " "be to set a bandwidth limit, for example.", ) +@click.option( + '--rclone-purge-args', + default='', + envvar='RCLONE_PURGE_ARGS', + help="Optional extra arguments to pass to `rclone delete` directly. Common usage for this would " + "be to execute a permanent delete instead of using the recycle bin on a destination. " + "Google Drive example: `--drive-use-trash=false`", +) @click.option( '--detection-types', envvar='DETECTION_TYPES', diff --git a/unifi_protect_backup/purge.py b/unifi_protect_backup/purge.py index 43d678d..713166d 100644 --- a/unifi_protect_backup/purge.py +++ b/unifi_protect_backup/purge.py @@ -12,9 +12,9 @@ from unifi_protect_backup.utils import run_command, wait_until logger = logging.getLogger(__name__) -async def delete_file(file_path): +async def delete_file(file_path, rclone_purge_args): """Deletes `file_path` via rclone.""" - returncode, stdout, stderr = await run_command(f'rclone delete -vv "{file_path}"') + returncode, stdout, stderr = await run_command(f'rclone delete -vv "{file_path}" {rclone_purge_args}') if returncode != 0: logger.error(f" Failed to delete file: '{file_path}'") @@ -35,6 +35,7 @@ class Purge: retention: relativedelta, rclone_destination: str, interval: relativedelta = relativedelta(days=1), + rclone_purge_args: str = "", ): """Init. @@ -43,11 +44,13 @@ class Purge: retention (relativedelta): How long clips should be kept rclone_destination (str): What rclone destination the clips are stored in interval (relativedelta): How often to purge old clips + rclone_purge_args (str): Optional extra arguments to pass to `rclone delete` directly. """ self._db: aiosqlite.Connection = db self.retention: relativedelta = retention self.rclone_destination: str = rclone_destination self.interval: relativedelta = interval + self.rclone_purge_args: str = rclone_purge_args async def start(self): """Main loop - runs forever.""" @@ -68,7 +71,7 @@ class Purge: async with self._db.execute(f"SELECT * FROM backups WHERE id = '{event_id}'") as backup_cursor: async for _, remote, file_path in backup_cursor: logger.debug(f" Deleted: {remote}:{file_path}") - await delete_file(f"{remote}:{file_path}") + await delete_file(f"{remote}:{file_path}", self.rclone_purge_args) deleted_a_file = True # delete event from database diff --git a/unifi_protect_backup/unifi_protect_backup_core.py b/unifi_protect_backup/unifi_protect_backup_core.py index 4e61cf3..bf3d984 100644 --- a/unifi_protect_backup/unifi_protect_backup_core.py +++ b/unifi_protect_backup/unifi_protect_backup_core.py @@ -59,6 +59,7 @@ class UnifiProtectBackup: rclone_destination: str, retention: str, rclone_args: str, + rclone_purge_args: str, detection_types: List[str], ignore_cameras: List[str], file_structure_format: str, @@ -87,6 +88,7 @@ class UnifiProtectBackup: (https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this) rclone_args (str): A bandwidth limit which is passed to the `--bwlimit` argument of `rclone` (https://rclone.org/docs/#bwlimit-bandwidth-spec) + rclone_purge_args (str): Optional extra arguments to pass to `rclone delete` directly. detection_types (List[str]): List of which detection types to backup. ignore_cameras (List[str]): List of camera IDs for which to not backup events. file_structure_format (str): A Python format string for output file path. @@ -121,6 +123,7 @@ class UnifiProtectBackup: logger.debug(f" {rclone_destination=}") logger.debug(f" {retention=}") logger.debug(f" {rclone_args=}") + logger.debug(f" {rclone_purge_args=}") logger.debug(f" {ignore_cameras=}") logger.debug(f" {verbose=}") logger.debug(f" {detection_types=}") @@ -134,6 +137,7 @@ class UnifiProtectBackup: self.rclone_destination = rclone_destination self.retention = parse_rclone_retention(retention) self.rclone_args = rclone_args + self.rclone_purge_args = rclone_purge_args self.file_structure_format = file_structure_format self.address = address @@ -238,7 +242,7 @@ class UnifiProtectBackup: # Create purge task # This will, every midnight, purge old backups from the rclone remotes and database - purge = Purge(self._db, self.retention, self.rclone_destination, self._purge_interval) + purge = Purge(self._db, self.retention, self.rclone_destination, self._purge_interval, self.rclone_purge_args) tasks.append(purge.start()) # Create missing event task