mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2024-09-20 15:26:02 +08:00
5061883b1f
* Fixes #388 * Bump docker/setup-buildx-action from 2 to 3 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Bump docker/login-action from 2 to 3 Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Bump docker/build-push-action from 4 to 5 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v4...v5) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Bump gitpython from 3.1.35 to 3.1.36 Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.35 to 3.1.36. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.35...3.1.36) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.11.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.11.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.11.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.11.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) * Error handling when BHD API doesn't respond * add BHD specific announce related issues * handle JSONDecodeError * Special mapping to leave torrents uncategorized on cat-update (#398) Special mapping to leave torrents uncategorized on cat-update (closes #395) * Bump gitpython from 3.1.36 to 3.1.37 Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.36 to 3.1.37. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.36...3.1.37) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.32 to 0.17.33 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.32 to 0.17.33. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * last_active flag for share_limits (#397) Added a last_active flag for share_limits to resume torrents and avoid cleanup if there was activity in the last X minutes. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#405) updates: - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.11.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.11.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.13.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * Bump schedule from 1.2.0 to 1.2.1 Bumps [schedule](https://github.com/dbader/schedule) from 1.2.0 to 1.2.1. - [Changelog](https://github.com/dbader/schedule/blob/master/HISTORY.rst) - [Commits](https://github.com/dbader/schedule/compare/1.2.0...1.2.1) --- updated-dependencies: - dependency-name: schedule dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.33 to 0.17.34 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.33 to 0.17.34. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * fix exit codes when program fails (#411) Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * Bump ruamel-yaml from 0.17.34 to 0.17.35 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.34 to 0.17.35. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * [pre-commit.ci] pre-commit autoupdate (#409) updates: - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.12.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.12.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.14.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.14.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * New option cat in trackers (#400) * New option cat in trackers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * update config.sample for #200 * add additional script to edit trackers * clarify remote_dir usage (#417) * Bump gitpython from 3.1.35 to 3.1.37 (#414) Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.35 to 3.1.37. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.35...3.1.37) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#413) updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.12.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.12.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.15.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * 4.0.5 * Fixes #419 * Bump pre-commit from 3.4.0 to 3.5.0 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.4.0...v3.5.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump gitpython from 3.1.37 to 3.1.38 Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.37 to 3.1.38. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.37...3.1.38) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Remove duplicates from when processing cleanup_dirs (#422) remove duplicates from cleanup_dirs * Bump gitpython from 3.1.38 to 3.1.40 Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.38 to 3.1.40. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.38...3.1.40) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.35 to 0.17.39 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.35 to 0.17.39. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * fixes #426 * Bump qbittorrent-api from 2023.9.53 to 2023.10.54 Bumps [qbittorrent-api](https://github.com/rmartin16/qbittorrent-api) from 2023.9.53 to 2023.10.54. - [Release notes](https://github.com/rmartin16/qbittorrent-api/releases) - [Changelog](https://github.com/rmartin16/qbittorrent-api/blob/main/CHANGELOG.md) - [Commits](https://github.com/rmartin16/qbittorrent-api/compare/v2023.9.53...v2023.10.54) --- updated-dependencies: - dependency-name: qbittorrent-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.39 to 0.17.40 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.39 to 0.17.40. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.40 to 0.18.0 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.40 to 0.18.0. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Fixes #429 * Fixes bug in edit_tracker * Bump ruamel-yaml from 0.18.0 to 0.18.2 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.18.0 to 0.18.2. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * 4.0.6 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Esteban Thilliez <77675611+estebanthi@users.noreply.github.com> Co-authored-by: Fabricio Silva <hi@fabricio.dev> Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Co-authored-by: garypiner <36236331+garypiner@users.noreply.github.com>
196 lines
7.3 KiB
Python
Executable file
196 lines
7.3 KiB
Python
Executable file
"""This script deletes torrents once your drive space drops below a certain threshold.
|
|
You can set a min torrent age and share ratio for a torrent to be deleted.
|
|
You can also allow incomplete torrents to be deleted.
|
|
Torrents will be deleted starting with the ones with the most seeds, only torrents with a single hardlink will be deleted.
|
|
Only torrents on configured drive path will be deleted. To monitor multiple drives, use multiple copies of this script.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import time
|
|
|
|
import qbittorrentapi
|
|
|
|
|
|
"""===Config==="""
|
|
# qBittorrent WebUi Login
|
|
qbt_login = {"host": "localhost", "port": 8080, "username": "???", "password": "???"}
|
|
PATH = "M:" # Path of drive to monitor. Only torrents with paths that start with this may be deleted.
|
|
MIN_FREE_SPACE = 10 # In GB. Min free space on drive.
|
|
MIN_FREE_USAGE = 0 # In decimal percentage, 0 to 1. Min % free space on drive.
|
|
MIN_TORRENT_SHARE_RATIO = 0 # In decimal percentage, 0 to inf. Min seeding ratio of torrent to delete.
|
|
MIN_TORRENT_AGE = 30 # In days, min age of torrent to delete. Uses seeding time.
|
|
ALLOW_INCOMPLETE_TORRENT_DELETIONS = (
|
|
False # Also delete torrents that haven't finished downloading. MIN_TORRENT_AGE now based on time active.
|
|
)
|
|
PREFER_PRIVATE_TORRENTS = (
|
|
True # Will delete public torrents before private ones regardless of seed difference. See is_torrent_public().
|
|
)
|
|
"""===End Config==="""
|
|
|
|
# Services
|
|
qbt_client: qbittorrentapi.Client = None
|
|
|
|
|
|
def quit_program(code=0):
|
|
"""Quits program with info"""
|
|
print("Exiting...")
|
|
import sys
|
|
|
|
sys.exit(code)
|
|
|
|
|
|
def setup_services(qbt=False):
|
|
"""Setup required services"""
|
|
global qbt_client
|
|
|
|
if qbt:
|
|
qbt_client = qbittorrentapi.Client(
|
|
host=qbt_login["host"], port=qbt_login["port"], username=qbt_login["username"], password=qbt_login["password"]
|
|
)
|
|
try:
|
|
qbt_client.auth_log_in()
|
|
print("Successfully connected to qBittorrent!")
|
|
except:
|
|
print("Error: Could not log into qBittorrent. Please verify login details are correct and Web Ui is available.")
|
|
quit_program(1)
|
|
|
|
|
|
def bytes_to_gb(data):
|
|
"""Converts bytes to GB."""
|
|
return data / 1024**3
|
|
|
|
|
|
def seconds_to_days(seconds):
|
|
"""Converts seconds to days."""
|
|
return seconds / 60 / 60 / 24
|
|
|
|
|
|
def get_disk_usage():
|
|
"""Gets the free space and free usage of disk."""
|
|
stat = shutil.disk_usage(PATH)
|
|
free_space = bytes_to_gb(stat.free)
|
|
free_usage = stat.free / stat.total
|
|
return free_space, free_usage
|
|
|
|
|
|
def is_storage_full():
|
|
"""Checks if free space are below user threshold."""
|
|
free_space, free_usage = get_disk_usage()
|
|
if free_space < MIN_FREE_SPACE or free_usage < MIN_FREE_USAGE:
|
|
return True
|
|
return False
|
|
|
|
|
|
def print_free_space():
|
|
"""Prints free space and user threshold."""
|
|
free_space, free_usage = get_disk_usage()
|
|
print(f"Free space: {free_space:.2f} GB ({free_usage:.2%}) - Thresholds: {MIN_FREE_SPACE:.2f} GB ({MIN_FREE_USAGE:.2%}) ")
|
|
|
|
|
|
def is_torrent_public(torrent_hash, setup=True):
|
|
"""Checks if torrent is public or private by word 'private' in tracker messages."""
|
|
setup_services(qbt=setup)
|
|
torrent_trackers = qbt_client.torrents_trackers(torrent_hash)
|
|
for tracker in torrent_trackers:
|
|
if "private" in tracker["msg"].lower():
|
|
return False
|
|
return True
|
|
|
|
|
|
def has_single_hard_link(path):
|
|
"""Check if file has a single hard link. False if any file in directory has multiple."""
|
|
# Check all files if path is directory
|
|
if os.path.isfile(path):
|
|
if os.stat(path).st_nlink > 1:
|
|
return False
|
|
else:
|
|
for dirpath, _, filenames in os.walk(path):
|
|
for file in filenames:
|
|
file_path = os.path.join(dirpath, file)
|
|
if os.stat(file_path).st_nlink > 1:
|
|
return False
|
|
return True
|
|
|
|
|
|
def torrent_on_monitored_drive(torrent):
|
|
"""Check if torrent path is within monitored drive"""
|
|
return torrent["content_path"].startswith(PATH)
|
|
|
|
|
|
def torrent_age_satisfied(torrent):
|
|
"""Gets the age of the torrent based on config"""
|
|
if ALLOW_INCOMPLETE_TORRENT_DELETIONS:
|
|
return seconds_to_days(torrent["time_active"]) >= MIN_TORRENT_AGE
|
|
else:
|
|
return seconds_to_days(torrent["seeding_time"]) >= MIN_TORRENT_AGE
|
|
|
|
|
|
def main():
|
|
# If free space above requirements, terminate
|
|
print_free_space()
|
|
if is_storage_full():
|
|
print("Drive space low, will be deleting torrents...")
|
|
else:
|
|
print("Free space already above threshold, no torrents were deleted!")
|
|
quit_program(0)
|
|
|
|
setup_services(qbt=True)
|
|
|
|
# Get all torrents older than threshold
|
|
print("Getting all torrents above age and seeding threshold...")
|
|
torrent_hashes_raw = []
|
|
torrent_privacy_raw = []
|
|
torrent_num_seeds_raw = []
|
|
for torrent in qbt_client.torrents_info():
|
|
torrent_share_ratio = qbt_client.torrents_properties(torrent["hash"])["share_ratio"]
|
|
if (
|
|
torrent_on_monitored_drive(torrent)
|
|
and torrent_age_satisfied(torrent)
|
|
and torrent_share_ratio >= MIN_TORRENT_SHARE_RATIO
|
|
):
|
|
torrent_hashes_raw.append(torrent["hash"])
|
|
torrent_privacy_raw.append(is_torrent_public(torrent["hash"], setup=False) if PREFER_PRIVATE_TORRENTS else True)
|
|
torrent_num_seeds_raw.append(torrent["num_complete"])
|
|
|
|
# Sort so most available torrent is last.
|
|
torrent_hashes = []
|
|
for *_, torrent_hash in sorted(zip(torrent_privacy_raw, torrent_num_seeds_raw, torrent_hashes_raw)):
|
|
torrent_hashes.append(torrent_hash)
|
|
|
|
# Delete torrents until storage is above threshold
|
|
deleted_torrents = []
|
|
if torrent_hashes:
|
|
print("Deleting torrents with a single hard link...")
|
|
while is_storage_full() and torrent_hashes:
|
|
torrent_hash = torrent_hashes.pop()
|
|
torrent_info = qbt_client.torrents_info(torrent_hashes=torrent_hash)[0]
|
|
torrent_name = torrent_info["name"]
|
|
torrent_path = torrent_info["content_path"]
|
|
# Only delete torrents with a single hard link as ones with multiple won't free any space
|
|
if has_single_hard_link(torrent_path):
|
|
qbt_client.torrents_delete(torrent_hashes=torrent_hash, delete_files=True)
|
|
deleted_torrents.append(torrent_name)
|
|
print(f"--- {torrent_name}")
|
|
time.sleep(1) # Sleep a bit after each deletion to make sure disk usage is updated.
|
|
|
|
# Print results
|
|
print_free_space()
|
|
if not is_storage_full():
|
|
print(f"Free space now above threshold, {len(deleted_torrents)} torrents were deleted!")
|
|
else: # No more torrents to delete but still low on space
|
|
print(
|
|
f"WARNING... Free space still below threshold after deleting all {len(deleted_torrents)} eligible torrents! Either:"
|
|
)
|
|
print(
|
|
f"--- Torrent ages are below threshold of '{MIN_TORRENT_AGE} days'\n"
|
|
f"--- Torrent seed ratios are below threshold of '{MIN_TORRENT_SHARE_RATIO}'\n"
|
|
"--- Torrents have multiple hard links\n"
|
|
"--- No torrents exists!"
|
|
)
|
|
|
|
quit_program(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|