mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2025-10-26 05:56:58 +08:00
4.3.0 (#814)
# Breaking Change - `requirements.txt` is now replaced with `pyproject.toml` meaning that **local installs** will need to replace their update command `pip install -r requirements.txt` with `pip install .` - Those that are running qbit-manage in docker don't need to do anything and things will continue to work as is # Requirements Updated qbittorrent-api==2025.5.0 humanize==4.12.3 # New Updates - Added user defined stalled_tag. Configurable through config.yml. (Closes #802 Thanks to @Patchy3767) ## Bug Fixes - Fixed max_seeding time of 0 for share_limits (Fixes #790 Thanks to @glau-bd) - Fixed Upload Limit not reset when LastActive/MinSeedsNotMet (Fixes #804) - Fixed Share limits not showing in logs when 0 torrents are in the group(Fixes #789) - Fixes bug where it tries to remove root_dir when not using category (Fixes #777) **Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.2.2...v4.3.0 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Actionbot <actions@github.com> Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Gerald Lau <glau@bitdefender.com> Co-authored-by: Patchy3767 <birabinowitz+github@gmail.com>
This commit is contained in:
parent
2259d82845
commit
06abe3cfb0
30 changed files with 412 additions and 164 deletions
|
|
@ -24,3 +24,8 @@ test.py
|
|||
qbit_manage.egg-info/
|
||||
.tox
|
||||
*.env
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.env
|
||||
|
|
|
|||
10
.flake8
Normal file
10
.flake8
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[flake8]
|
||||
extend-ignore =
|
||||
# E722 Do not use bare except, specify exception instead
|
||||
E722,
|
||||
# E402 module level import not at top of file
|
||||
E402,
|
||||
# E501 line too long
|
||||
E501,
|
||||
max-line-length = 130
|
||||
exclude = .git,__pycache__,build,dist
|
||||
6
.github/dependabot.yml
vendored
Executable file → Normal file
6
.github/dependabot.yml
vendored
Executable file → Normal file
|
|
@ -12,6 +12,12 @@ updates:
|
|||
target-branch: "develop"
|
||||
assignees:
|
||||
- "bobokun"
|
||||
# Specify the file to check for dependencies
|
||||
# Dependabot will now look at pyproject.toml instead of requirements.txt
|
||||
allow:
|
||||
- dependency-type: "direct"
|
||||
# Specify the file to update
|
||||
versioning-strategy: increase-if-necessary
|
||||
- package-ecosystem: github-actions
|
||||
directory: '/'
|
||||
schedule:
|
||||
|
|
|
|||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
|
|
@ -21,12 +21,20 @@ jobs:
|
|||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Install uv
|
||||
run: |
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install pre-commit
|
||||
uv venv .venv
|
||||
source .venv/bin/activate
|
||||
uv pip install pre-commit
|
||||
|
||||
- name: Run pre-commit version check
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pre-commit run increase-version --all-files
|
||||
|
||||
ruff:
|
||||
|
|
|
|||
44
.github/workflows/update-supported-versions.yml
vendored
44
.github/workflows/update-supported-versions.yml
vendored
|
|
@ -6,7 +6,7 @@ on:
|
|||
- master
|
||||
- develop
|
||||
paths:
|
||||
- "requirements.txt"
|
||||
- "pyproject.toml"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
targetBranch:
|
||||
|
|
@ -32,32 +32,54 @@ jobs:
|
|||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install dependencies from requirements.txt
|
||||
- name: Install uv
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install dependencies with uv
|
||||
run: |
|
||||
uv venv .venv
|
||||
source .venv/bin/activate
|
||||
uv pip install .
|
||||
|
||||
- name: Run update script
|
||||
run: python scripts/update-readme-version.py ${{ github.event.inputs.targetBranch || github.ref_name }}
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
python scripts/update-readme-version.py ${{ github.event.inputs.targetBranch || github.ref_name }}
|
||||
|
||||
- name: Update develop versions
|
||||
if: ${{ github.event.inputs.targetBranch || github.ref_name == 'develop' }}
|
||||
id: get-develop-version
|
||||
run: |
|
||||
# Run the script and capture its output
|
||||
output=$(bash scripts/pre-commit/update_develop_version.sh)
|
||||
# Extract the last line which contains the version
|
||||
version=$(echo "$output" | tail -n 1)
|
||||
# Set the version as an output parameter for later steps
|
||||
echo "version=$version" >> $GITHUB_OUTPUT
|
||||
# Debug info
|
||||
echo "Script output: $output"
|
||||
echo "Captured Version: $version"
|
||||
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
commit-message: Update SUPPORTED_VERSIONS.json
|
||||
title: "Update SUPPORTED_VERSIONS.json for ${{ github.event.inputs.targetBranch || github.ref_name }}"
|
||||
title: "Update SUPPORTED_VERSIONS.json for ${{ steps.get-develop-version.outputs.version || github.event.inputs.targetBranch || github.ref_name }}"
|
||||
branch: update-supported-versions-${{ github.event.inputs.targetBranch || github.ref_name }}
|
||||
base: develop
|
||||
body: "This PR updates the SUPPORTED_VERSIONS.json to reflect new versions."
|
||||
|
||||
- name: Approve the Pull Request
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
run: gh pr review ${{ steps.cpr.outputs.pull-request-number }} --approve
|
||||
if: ${{ steps.create-pr.outputs.pull-request-number }}
|
||||
run: gh pr review ${{ steps.create-pr.outputs.pull-request-number }} --approve
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Merge the Pull Request
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
run: gh pr merge ${{ steps.cpr.outputs.pull-request-number }} --auto --squash
|
||||
if: ${{ steps.create-pr.outputs.pull-request-number }}
|
||||
run: gh pr merge ${{ steps.create-pr.outputs.pull-request-number }} --auto --squash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -12,3 +12,4 @@ __pycache__/
|
|||
qbit_manage.egg-info/
|
||||
.tox
|
||||
*.env
|
||||
**/build
|
||||
|
|
|
|||
|
|
@ -8,13 +8,12 @@ repos:
|
|||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-yaml
|
||||
- id: requirements-txt-fixer
|
||||
- id: check-added-large-files
|
||||
- id: fix-byte-order-marker
|
||||
- id: pretty-format-json
|
||||
args: [--autofix, --indent, '4', --no-sort-keys]
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.37.0 # or higher tag
|
||||
rev: v1.37.1 # or higher tag
|
||||
hooks:
|
||||
- id: yamllint
|
||||
args: [--format, parsable, --strict]
|
||||
|
|
@ -26,7 +25,7 @@ repos:
|
|||
exclude: ^.github/
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.11.6
|
||||
rev: v0.11.8
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
|
|
|||
19
CHANGELOG
19
CHANGELOG
|
|
@ -1,9 +1,18 @@
|
|||
# Breaking Change
|
||||
- `requirements.txt` is now replaced with `pyproject.toml` meaning that **local installs** will need to replace their update command `pip install -r requirements.txt` with `pip install .`
|
||||
- Those that are running qbit-manage in docker don't need to do anything and things will continue to work as is
|
||||
|
||||
# Requirements Updated
|
||||
qbittorrent-api==2025.4.1
|
||||
humanize==4.12.2
|
||||
qbittorrent-api==2025.5.0
|
||||
humanize==4.12.3
|
||||
|
||||
# New Updates
|
||||
- Adds warning to share_limits not being applied in dry-run (closes #786)
|
||||
- Adds credit to remove_scross-seed_tag.py script (Thanks to @zakkarry)
|
||||
- Added user defined stalled_tag. Configurable through config.yml. (Closes #802 Thanks to @Patchy3767)
|
||||
|
||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.2.1...v4.2.2
|
||||
## Bug Fixes
|
||||
- Fixed max_seeding time of 0 for share_limits (Fixes #790 Thanks to @glau-bd)
|
||||
- Fixed Upload Limit not reset when LastActive/MinSeedsNotMet (Fixes #804)
|
||||
- Fixed Share limits not showing in logs when 0 torrents are in the group(Fixes #789)
|
||||
- Fixes bug where it tries to remove root_dir when not using category (Fixes #777)
|
||||
|
||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.2.2...v4.3.0
|
||||
|
|
|
|||
54
Dockerfile
Executable file → Normal file
54
Dockerfile
Executable file → Normal file
|
|
@ -1,21 +1,49 @@
|
|||
FROM python:3.11-alpine
|
||||
# Use a multi-stage build to minimize final image size
|
||||
FROM python:3.13-alpine as builder
|
||||
|
||||
ARG BRANCH_NAME=master
|
||||
ENV BRANCH_NAME=${BRANCH_NAME}
|
||||
|
||||
# Install build-time dependencies only
|
||||
RUN apk add --no-cache \
|
||||
gcc \
|
||||
g++ \
|
||||
libxml2-dev \
|
||||
libxslt-dev \
|
||||
zlib-dev \
|
||||
curl \
|
||||
bash
|
||||
|
||||
# Install UV (fast pip alternative)
|
||||
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
# Copy only dependency files first (better layer caching)
|
||||
COPY pyproject.toml setup.py VERSION /app/
|
||||
WORKDIR /app
|
||||
|
||||
# Install project in a virtual env (lightweight & reproducible)
|
||||
RUN /root/.local/bin/uv pip install --system .
|
||||
|
||||
# Final stage: minimal runtime image
|
||||
FROM python:3.13-alpine
|
||||
|
||||
ENV TINI_VERSION=v0.19.0
|
||||
ENV QBM_DOCKER=True
|
||||
|
||||
COPY requirements.txt /
|
||||
|
||||
# install packages
|
||||
RUN echo "**** install system packages ****" \
|
||||
&& apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add --no-cache tzdata gcc g++ libxml2-dev libxslt-dev zlib-dev bash curl wget jq grep sed coreutils findutils unzip p7zip ca-certificates tini\
|
||||
&& pip3 install --no-cache-dir --upgrade --requirement /requirements.txt \
|
||||
&& apk del gcc g++ libxml2-dev libxslt-dev zlib-dev \
|
||||
&& rm -rf /requirements.txt /tmp/* /var/tmp/* /var/cache/apk/*
|
||||
# Runtime dependencies (smaller than build stage)
|
||||
RUN apk add --no-cache \
|
||||
tzdata \
|
||||
bash \
|
||||
curl \
|
||||
jq \
|
||||
tini \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Copy installed packages and scripts from builder
|
||||
COPY --from=builder /usr/local/lib/python3.13/site-packages/ /usr/local/lib/python3.13/site-packages/
|
||||
COPY --from=builder /app /app
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
VOLUME /config
|
||||
ENTRYPOINT ["/sbin/tini", "-s", "python3", "qbit_manage.py"]
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "-s", "--"]
|
||||
CMD ["python3", "qbit_manage.py"]
|
||||
|
|
|
|||
95
Makefile
95
Makefile
|
|
@ -1,24 +1,89 @@
|
|||
.PHONY: minimal
|
||||
minimal: venv
|
||||
# Define the path to uv
|
||||
UV_PATH := $(shell which uv 2>/dev/null || echo "")
|
||||
UV_LOCAL_PATH := $(HOME)/.local/bin/uv
|
||||
UV_CARGO_PATH := $(HOME)/.cargo/bin/uv
|
||||
|
||||
venv: requirements.txt setup.py tox.ini
|
||||
tox -e venv
|
||||
# Check if uv is installed, if not set UV_INSTALL to 1
|
||||
ifeq ($(UV_PATH),)
|
||||
ifeq ($(wildcard $(UV_LOCAL_PATH)),)
|
||||
ifeq ($(wildcard $(UV_CARGO_PATH)),)
|
||||
UV_INSTALL := 1
|
||||
else
|
||||
UV_PATH := $(UV_CARGO_PATH)
|
||||
endif
|
||||
else
|
||||
UV_PATH := $(UV_LOCAL_PATH)
|
||||
endif
|
||||
endif
|
||||
|
||||
# Define the virtual environment path
|
||||
VENV := .venv
|
||||
VENV_ACTIVATE := $(VENV)/bin/activate
|
||||
VENV_PYTHON := $(VENV)/bin/python
|
||||
VENV_UV := $(VENV)/bin/uv
|
||||
VENV_PIP := $(VENV)/bin/pip
|
||||
VENV_PRE_COMMIT := $(VENV)/bin/pre-commit
|
||||
VENV_RUFF := $(VENV)/bin/ruff
|
||||
|
||||
.PHONY: all
|
||||
all: venv
|
||||
|
||||
.PHONY: install-uv
|
||||
install-uv:
|
||||
ifdef UV_INSTALL
|
||||
@echo "Installing uv..."
|
||||
@curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
@echo "uv installed to $(HOME)/.local/bin/uv"
|
||||
$(eval UV_PATH := $(HOME)/.local/bin/uv)
|
||||
endif
|
||||
|
||||
.PHONY: venv
|
||||
venv: install-uv
|
||||
@echo "Creating virtual environment..."
|
||||
@$(UV_PATH) venv $(VENV)
|
||||
@echo "Installing project dependencies..."
|
||||
@$(UV_PATH) pip install -e .
|
||||
@echo "Installing development dependencies..."
|
||||
@$(UV_PATH) pip install pre-commit ruff
|
||||
@echo "Virtual environment created and dependencies installed."
|
||||
@echo "To activate the virtual environment, run: source $(VENV_ACTIVATE)"
|
||||
|
||||
.PHONY: sync
|
||||
sync: venv
|
||||
@echo "Syncing dependencies from pyproject.toml..."
|
||||
@$(UV_PATH) pip sync pyproject.toml
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
tox -e tests
|
||||
test: venv
|
||||
@echo "Running tests..."
|
||||
@. $(VENV_ACTIVATE) && $(VENV_PYTHON) -m pytest
|
||||
|
||||
.PHONY: pre-commit
|
||||
pre-commit:
|
||||
tox -e pre-commit
|
||||
pre-commit: venv
|
||||
@echo "Running pre-commit hooks..."
|
||||
@. $(VENV_ACTIVATE) && $(VENV_PRE_COMMIT) run --all-files
|
||||
|
||||
.PHONY: install-hooks
|
||||
install-hooks: venv
|
||||
@echo "Installing pre-commit hooks..."
|
||||
@. $(VENV_ACTIVATE) && $(VENV_PRE_COMMIT) install -f --install-hooks
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
find -name '*.pyc' -delete
|
||||
find -name '__pycache__' -delete
|
||||
rm -rf .tox
|
||||
rm -rf venv
|
||||
@echo "Cleaning up..."
|
||||
@find -name '*.pyc' -delete
|
||||
@find -name '__pycache__' -delete
|
||||
@rm -rf $(VENV)
|
||||
@rm -rf .pytest_cache
|
||||
@rm -rf .ruff_cache
|
||||
@echo "Cleanup complete."
|
||||
|
||||
.PHONY: install-hooks
|
||||
install-hooks:
|
||||
tox -e install-hooks
|
||||
.PHONY: lint
|
||||
lint: venv
|
||||
@echo "Running linter..."
|
||||
@. $(VENV_ACTIVATE) && $(VENV_RUFF) check --fix .
|
||||
|
||||
.PHONY: format
|
||||
format: venv
|
||||
@echo "Running formatter..."
|
||||
@. $(VENV_ACTIVATE) && $(VENV_RUFF) format .
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"master": {
|
||||
"qbit": "v5.0.4",
|
||||
"qbitapi": "2025.2.0"
|
||||
},
|
||||
"develop": {
|
||||
"qbit": "v5.0.5",
|
||||
"qbitapi": "2025.4.1"
|
||||
},
|
||||
"develop": {
|
||||
"qbit": "v5.1.0",
|
||||
"qbitapi": "2025.5.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
4.2.2
|
||||
4.3.0
|
||||
|
|
|
|||
3
activate.sh
Normal file → Executable file
3
activate.sh
Normal file → Executable file
|
|
@ -1 +1,2 @@
|
|||
venv/bin/activate
|
||||
#!/bin/bash
|
||||
source .venv/bin/activate
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ settings:
|
|||
- Upload
|
||||
tracker_error_tag: issue # Will set the tag of any torrents that do not have a working tracker.
|
||||
nohardlinks_tag: noHL # Will set the tag of any torrents with no hardlinks.
|
||||
stalled_tag: stalledDL # Will set the tag of any torrents stalled downloading.
|
||||
share_limits_tag: ~share_limit # Will add this tag when applying share limits to provide an easy way to filter torrents by share limit group/priority for each torrent
|
||||
share_limits_min_seeding_time_tag: MinSeedTimeNotReached # Tag to be added to torrents that have not yet reached the minimum seeding time
|
||||
share_limits_min_num_seeds_tag: MinSeedsNotMet # Tag to be added to torrents that have not yet reached the minimum number of seeds
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ This section defines any settings defined in the configuration.
|
|||
| `tag_nohardlinks_filter_completed` | When running `--tag-nohardlinks` function, , it will filter for completed torrents only. | True | <center>❌</center> |
|
||||
| `cat_update_all` | When running `--cat-update` function, it will check and update all torrents categories, otherwise it will only update uncategorized torrents. | True | <center>❌</center> |
|
||||
| `disable_qbt_default_share_limits` | When running `--share-limits` function, it allows QBM to handle share limits by disabling qBittorrents default Share limits. | True | <center>❌</center> |
|
||||
| `tag_stalled_torrents` | Tags any downloading torrents that are stalled with the `stalledDL` tag when running the tag_update command | True | <center>❌</center> |
|
||||
| `tag_stalled_torrents` | Tags any downloading torrents that are stalled with the user defined `stalledDL` tag when running the tag_update command | True | <center>❌</center> |
|
||||
| `rem_unregistered_ignore_list` | Ignores a list of words found in the status of the tracker when running rem_unregistered command and will not remove the torrent if matched | | <center>❌</center> |
|
||||
|
||||
## **directory:**
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ git clone https://github.com/StuffAnThings/qbit_manage
|
|||
Install requirements
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
pip install .
|
||||
```
|
||||
|
||||
If there are issues installing dependencies try:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt --ignore-installed
|
||||
pip install . --ignore-installed
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ chmod +x qbit_manage.py
|
|||
* Get & Install Requirements
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
pip install .
|
||||
```
|
||||
|
||||
* Create Config
|
||||
|
|
@ -35,26 +35,73 @@ nano qbm-update.sh
|
|||
* Paste the below into the update script and update the Paths and Service Name (if using systemd)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
qbmPath="/home/bakerboy448/QbitManage"
|
||||
qbmVenvPath="$qbmPath"/"qbit-venv/"
|
||||
qbmServiceName="qbm"
|
||||
cd "$qbmPath" || exit
|
||||
currentVersion=$(cat VERSION)
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
git fetch
|
||||
if [ "$(git rev-parse HEAD)" = "$(git rev-parse @'{u}')" ]; then
|
||||
echo "=== Already up to date $currentVersion on $branch ==="
|
||||
exit 0
|
||||
fi
|
||||
git pull
|
||||
newVersion=$(cat VERSION)
|
||||
"$qbmVenvPath"/bin/python -m pip install -r requirements.txt
|
||||
echo "=== Updated from $currentVersion to $newVersion on $branch ==="
|
||||
echo "=== Restarting qbm Service ==="
|
||||
sudo systemctl restart "$qbmServiceName"
|
||||
exit 0
|
||||
force_update=${1:-false}
|
||||
|
||||
# Constants
|
||||
QBM_PATH="/opt/qbit_manage"
|
||||
QBM_VENV_PATH="/opt/.venv/qbm-venv"
|
||||
QBM_SERVICE_NAME="qbmanage"
|
||||
QBM_UPSTREAM_GIT_REMOTE="origin"
|
||||
QBM_VERSION_FILE="$QBM_PATH/VERSION"
|
||||
QBM_REQUIREMENTS_FILE="$QBM_PATH/pyproject.toml"
|
||||
CURRENT_UID=$(id -un)
|
||||
|
||||
# Check if QBM is installed and if the current user owns it
|
||||
check_qbm_installation() {
|
||||
if [ -d "$QBM_PATH" ]; then
|
||||
qbm_repo_owner=$(stat --format='%U' "$QBM_PATH")
|
||||
qbm_repo_group=$(stat --format='%G' "$QBM_PATH")
|
||||
if [ "$qbm_repo_owner" != "$CURRENT_UID" ]; then
|
||||
echo "You do not own the QbitManage repo. Please run this script as the user that owns the repo [$qbm_repo_owner]."
|
||||
echo "use 'sudo -u $qbm_repo_owner -g $qbm_repo_group /path/to/qbm-update.sh'"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "QbitManage folder does not exist. Please install QbitManage before running this script."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Update QBM if necessary
|
||||
update_qbm() {
|
||||
current_branch=$(git -C "$QBM_PATH" rev-parse --abbrev-ref HEAD)
|
||||
echo "Current Branch: $current_branch. Checking for updates..."
|
||||
git -C "$QBM_PATH" fetch
|
||||
if [ "$(git -C "$QBM_PATH" rev-parse HEAD)" = "$(git -C "$QBM_PATH" rev-parse @'{u}')" ] && [ "$force_update" != true ]; then
|
||||
current_version=$(cat "$QBM_VERSION_FILE")
|
||||
echo "=== Already up to date $current_version on $current_branch ==="
|
||||
exit 0
|
||||
fi
|
||||
current_requirements=$(sha1sum "$QBM_REQUIREMENTS_FILE" | awk '{print $1}')
|
||||
git -C "$QBM_PATH" reset --hard "$QBM_UPSTREAM_GIT_REMOTE/$current_branch"
|
||||
}
|
||||
|
||||
# Update virtual environment if requirements have changed
|
||||
update_venv() {
|
||||
new_requirements=$(sha1sum "$QBM_REQUIREMENTS_FILE" | awk '{print $1}')
|
||||
if [ "$current_requirements" != "$new_requirements" ] || [ "$force_update" = true ]; then
|
||||
echo "=== Requirements changed, updating venv ==="
|
||||
"$QBM_VENV_PATH/bin/python" -m pip install --upgrade "$QBM_PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
# Restart the QBM service
|
||||
restart_service() {
|
||||
echo "=== Restarting QBM Service ==="
|
||||
sudo systemctl restart "$QBM_SERVICE_NAME"
|
||||
new_version=$(cat "$QBM_VERSION_FILE")
|
||||
echo "=== Updated to $new_version on $current_branch"
|
||||
}
|
||||
|
||||
# Main script execution
|
||||
check_qbm_installation
|
||||
update_qbm
|
||||
update_venv
|
||||
restart_service
|
||||
```
|
||||
|
||||
* Make the update script executable
|
||||
|
|
|
|||
|
|
@ -56,11 +56,11 @@ In the new text field you'll need to place:
|
|||
```bash
|
||||
#!/bin/bash
|
||||
echo "Installing required packages"
|
||||
python3 -m pip install -r /mnt/user/path/to/requirements.txt
|
||||
python3 -m pip install /mnt/user/path/to/qbit
|
||||
echo "Required packages installed"
|
||||
```
|
||||
|
||||
Replace `path/to/` with your path example mines `/data/scripts/qbit/` or `/mnt/user/data/scripts/qbit/requirements.txt`
|
||||
Replace `path/to/` with your path example mines `/data/scripts/qbit/`
|
||||
|
||||
Now click **Save Changes**
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import os
|
|||
import re
|
||||
import stat
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
|
||||
import requests
|
||||
from retrying import retry
|
||||
|
|
@ -202,6 +201,7 @@ class Config:
|
|||
self.data, "tracker_error_tag", parent="settings", default="issue"
|
||||
),
|
||||
"nohardlinks_tag": self.util.check_for_attribute(self.data, "nohardlinks_tag", parent="settings", default="noHL"),
|
||||
"stalled_tag": self.util.check_for_attribute(self.data, "stalled_tag", parent="settings", default="stalledDL"),
|
||||
"share_limits_tag": self.util.check_for_attribute(
|
||||
self.data, "share_limits_tag", parent="settings", default=share_limits_tag
|
||||
),
|
||||
|
|
@ -245,6 +245,7 @@ class Config:
|
|||
|
||||
self.tracker_error_tag = self.settings["tracker_error_tag"]
|
||||
self.nohardlinks_tag = self.settings["nohardlinks_tag"]
|
||||
self.stalled_tag = self.settings["stalled_tag"]
|
||||
self.share_limits_tag = self.settings["share_limits_tag"]
|
||||
self.share_limits_custom_tags = []
|
||||
self.share_limits_min_seeding_time_tag = self.settings["share_limits_min_seeding_time_tag"]
|
||||
|
|
@ -424,10 +425,12 @@ class Config:
|
|||
save=True,
|
||||
)
|
||||
priorities.add(priority)
|
||||
return OrderedDict(sorted_limits)
|
||||
return dict(sorted_limits)
|
||||
|
||||
self.share_limits = OrderedDict()
|
||||
self.share_limits = dict()
|
||||
sorted_share_limits = _sort_share_limits(self.data["share_limits"])
|
||||
logger.trace(f"Unsorted Share Limits: {self.data['share_limits']}")
|
||||
logger.trace(f"Sorted Share Limits: {sorted_share_limits}")
|
||||
for group in sorted_share_limits:
|
||||
self.share_limits[group] = {}
|
||||
self.share_limits[group]["priority"] = sorted_share_limits[group]["priority"]
|
||||
|
|
@ -637,6 +640,7 @@ class Config:
|
|||
self.notify(err, "Config")
|
||||
raise Failed(err)
|
||||
|
||||
logger.trace(f"Share_limits config: {self.share_limits}")
|
||||
# Add RecycleBin
|
||||
self.recyclebin = {}
|
||||
self.recyclebin["enabled"] = self.util.check_for_attribute(
|
||||
|
|
|
|||
|
|
@ -55,6 +55,14 @@ class ShareLimits:
|
|||
torrents = group_config["torrents"]
|
||||
self.torrents_updated = []
|
||||
self.tdel_dict = {}
|
||||
group_priority = group_config.get("priority", "Unknown")
|
||||
num_torrents = len(torrents) if torrents else 0
|
||||
|
||||
logger.separator(
|
||||
f"Updating Share Limits for [Group {group_name}] [Priority {group_priority}] [Torrents ({num_torrents})]",
|
||||
space=False,
|
||||
border=False,
|
||||
)
|
||||
if torrents:
|
||||
self.update_share_limits_for_group(group_name, group_config, torrents)
|
||||
attr = {
|
||||
|
|
@ -183,9 +191,6 @@ class ShareLimits:
|
|||
|
||||
def update_share_limits_for_group(self, group_name, group_config, torrents):
|
||||
"""Updates share limits for torrents in a group"""
|
||||
logger.separator(
|
||||
f"Updating Share Limits for [Group {group_name}] [Priority {group_config['priority']}]", space=False, border=False
|
||||
)
|
||||
group_upload_speed = group_config["limit_upload_speed"]
|
||||
|
||||
for torrent in torrents:
|
||||
|
|
@ -488,6 +493,7 @@ class ShareLimits:
|
|||
torrent.add_tags(self.min_seeding_time_tag)
|
||||
torrent_tags += f", {self.min_seeding_time_tag}"
|
||||
torrent.set_share_limits(ratio_limit=-1, seeding_time_limit=-1, inactive_seeding_time_limit=-1)
|
||||
torrent.set_upload_limit(-1)
|
||||
if resume_torrent:
|
||||
torrent.resume()
|
||||
return False
|
||||
|
|
@ -520,6 +526,7 @@ class ShareLimits:
|
|||
torrent.add_tags(self.min_num_seeds_tag)
|
||||
torrent_tags += f", {self.min_num_seeds_tag}"
|
||||
torrent.set_share_limits(ratio_limit=-1, seeding_time_limit=-1, inactive_seeding_time_limit=-1)
|
||||
torrent.set_upload_limit(-1)
|
||||
if resume_torrent:
|
||||
torrent.resume()
|
||||
return True
|
||||
|
|
@ -554,6 +561,7 @@ class ShareLimits:
|
|||
torrent.add_tags(self.last_active_tag)
|
||||
torrent_tags += f", {self.last_active_tag}"
|
||||
torrent.set_share_limits(ratio_limit=-1, seeding_time_limit=-1, inactive_seeding_time_limit=-1)
|
||||
torrent.set_upload_limit(-1)
|
||||
if resume_torrent:
|
||||
torrent.resume()
|
||||
return False
|
||||
|
|
@ -570,7 +578,7 @@ class ShareLimits:
|
|||
else:
|
||||
_remove_min_seeding_time_tag()
|
||||
return False
|
||||
if seeding_time_limit:
|
||||
if seeding_time_limit is not None:
|
||||
if (torrent.seeding_time >= seeding_time_limit * 60) and _has_reached_min_seeding_time_limit():
|
||||
body += logger.insert_space(
|
||||
f"Seeding Time vs Max Seed Time: {str(timedelta(seconds=torrent.seeding_time))} >= "
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class Tags:
|
|||
self.share_limits_tag = qbit_manager.config.share_limits_tag
|
||||
self.torrents_updated = [] # List of torrents updated
|
||||
self.notify_attr = [] # List of single torrent attributes to send to notifiarr
|
||||
self.stalled_tag = "stalledDL"
|
||||
self.stalled_tag = qbit_manager.config.stalled_tag
|
||||
self.tag_stalled_torrents = self.config.settings["tag_stalled_torrents"]
|
||||
|
||||
self.tags()
|
||||
|
|
|
|||
|
|
@ -423,6 +423,8 @@ class Qbt:
|
|||
save_path = categories[cat].savePath.replace(self.config.root_dir, self.config.remote_dir)
|
||||
if save_path:
|
||||
save_paths.add(save_path)
|
||||
# Also add root_dir to the list
|
||||
save_paths.add(self.config.remote_dir)
|
||||
return list(save_paths)
|
||||
|
||||
def tor_delete_recycle(self, torrent, info):
|
||||
|
|
|
|||
64
pyproject.toml
Normal file
64
pyproject.toml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=42", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
# Keep using setup.py for version handling
|
||||
# Dependencies are specified here for uv to use
|
||||
|
||||
[project]
|
||||
name = "qbit_manage"
|
||||
# Version is dynamically determined from setup.py
|
||||
dynamic = ["version"]
|
||||
description = "This tool will help manage tedious tasks in qBittorrent and automate them. Tag, categorize, remove Orphaned data, remove unregistered torrents and much much more."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "bobokun"},
|
||||
]
|
||||
dependencies = [
|
||||
"bencodepy==0.9.5",
|
||||
"croniter==6.0.0",
|
||||
"GitPython==3.1.44",
|
||||
"humanize==4.12.3",
|
||||
"pytimeparse2==1.7.1",
|
||||
"qbittorrent-api==2025.5.0",
|
||||
"requests==2.32.3",
|
||||
"retrying==1.3.4",
|
||||
"ruamel.yaml==0.18.10",
|
||||
"schedule==1.2.2",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/StuffAnThings"
|
||||
Repository = "https://github.com/StuffAnThings/qbit_manage"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pre-commit==4.2.0",
|
||||
"ruff==0.11.8",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 130
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"I", # isort - import order
|
||||
"UP", # pyupgrade
|
||||
"T10", # debugger
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # pyflakes
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"E722", # E722 Do not use bare except, specify exception instead
|
||||
"E402", # E402 module level import not at top of file
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
force-single-line = true
|
||||
|
||||
[tool.ruff.format]
|
||||
line-ending = "auto"
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
pre-commit==4.2.0
|
||||
ruff==0.11.7
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
bencodepy==0.9.5
|
||||
croniter==6.0.0
|
||||
GitPython==3.1.44
|
||||
humanize==4.12.2
|
||||
pytimeparse2==1.7.1
|
||||
qbittorrent-api==2025.4.1
|
||||
requests==2.32.3
|
||||
retrying==1.3.4
|
||||
ruamel.yaml==0.18.10
|
||||
schedule==1.2.2
|
||||
|
|
@ -13,28 +13,8 @@ if git diff --cached --name-only | grep -q "VERSION"; then
|
|||
elif git diff --name-only | grep -q "VERSION"; then
|
||||
echo "The VERSION file has unstaged changes. Please stage them before committing."
|
||||
exit 0
|
||||
elif ! git show --name-only HEAD | grep -q "VERSION"; then
|
||||
source "$(dirname "$0")/update_develop_version.sh"
|
||||
fi
|
||||
|
||||
# Read the current version from the VERSION file
|
||||
current_version=$(<VERSION)
|
||||
echo "Current version: $current_version"
|
||||
|
||||
# Check if "develop" is not present in the version string
|
||||
if [[ $current_version != *"develop"* ]]; then
|
||||
echo "The word 'develop' is not present in the version string."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract the version number after "develop"
|
||||
version_number=$(echo "$current_version" | sed -n 's/.*develop\([0-9]*\).*/\1/p')
|
||||
|
||||
# Increment the version number
|
||||
new_version_number=$((version_number + 1))
|
||||
|
||||
# Replace the old version number with the new one
|
||||
new_version=$(echo "$current_version" | sed "s/develop$version_number/develop$new_version_number/")
|
||||
|
||||
# Update the VERSION file
|
||||
sed -i "s/$current_version/$new_version/" VERSION
|
||||
|
||||
echo "Version updated to: $new_version"
|
||||
source "$(dirname "$0")/update_develop_version.sh"
|
||||
|
|
|
|||
26
scripts/pre-commit/update_develop_version.sh
Executable file
26
scripts/pre-commit/update_develop_version.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Read the current version from the VERSION file
|
||||
current_version=$(<VERSION)
|
||||
echo "Current version: $current_version"
|
||||
|
||||
# Check if "develop" is present in the version string
|
||||
if [[ $current_version == *"develop"* ]]; then
|
||||
# Extract the version number after "develop"
|
||||
version_number=$(echo "$current_version" | sed -n 's/.*develop\([0-9]*\).*/\1/p')
|
||||
|
||||
# Increment the version number
|
||||
new_version_number=$((version_number + 1))
|
||||
|
||||
# Replace the old version number with the new one
|
||||
new_version=$(echo "$current_version" | sed "s/develop$version_number/develop$new_version_number/")
|
||||
|
||||
# Update the VERSION file
|
||||
sed -i "s/$current_version/$new_version/" VERSION
|
||||
|
||||
echo "Version updated to: $new_version"
|
||||
echo "$new_version"
|
||||
else
|
||||
echo "The word 'develop' is not present in the version string."
|
||||
exit 0
|
||||
fi
|
||||
|
|
@ -25,11 +25,15 @@ try:
|
|||
except FileNotFoundError:
|
||||
supported_versions = {}
|
||||
|
||||
# Extract the current qbittorrent-api version from requirements.txt
|
||||
print("Reading requirements.txt...")
|
||||
with open("requirements.txt", encoding="utf-8") as file:
|
||||
requirements = file.read()
|
||||
qbittorrent_api_version = re.search(r"qbittorrent-api==(.+)", requirements).group(1)
|
||||
# Extract the current qbittorrent-api version from pyproject.toml
|
||||
print("Reading pyproject.toml...")
|
||||
with open("pyproject.toml", encoding="utf-8") as file:
|
||||
content = file.read()
|
||||
match = re.search(r"qbittorrent-api==([\d.]+)", content)
|
||||
if match:
|
||||
qbittorrent_api_version = match.group(1)
|
||||
else:
|
||||
raise ValueError("qbittorrent-api version not found in pyproject.toml")
|
||||
|
||||
print(f"Current qbittorrent-api version: {qbittorrent_api_version}")
|
||||
|
||||
|
|
|
|||
11
setup.py
Executable file → Normal file
11
setup.py
Executable file → Normal file
|
|
@ -1,9 +1,13 @@
|
|||
import os
|
||||
from distutils.core import setup
|
||||
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
from modules import __version__
|
||||
# Read version from VERSION file
|
||||
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "VERSION")) as f:
|
||||
version_str = f.read().strip()
|
||||
# Get only the first part (without develop suffix)
|
||||
version = version_str.rsplit("-", 1)[0]
|
||||
|
||||
# User-friendly description from README.md
|
||||
current_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
|
|
@ -13,7 +17,6 @@ try:
|
|||
except Exception:
|
||||
long_description = ""
|
||||
|
||||
|
||||
setup(
|
||||
# Name of the package
|
||||
name="qbit_manage",
|
||||
|
|
@ -23,7 +26,7 @@ setup(
|
|||
include_package_data=True,
|
||||
# Start with a small number and increase it with
|
||||
# every change you make https://semver.org
|
||||
version=__version__,
|
||||
version=version,
|
||||
# Chose a license from here: https: //
|
||||
# help.github.com / articles / licensing - a -
|
||||
# repository. For example: MIT
|
||||
|
|
|
|||
32
tox.ini
32
tox.ini
|
|
@ -1,32 +0,0 @@
|
|||
[tox]
|
||||
envlist = py39,py310,py311,py312,py313,pre-commit
|
||||
skip_missing_interpreters = true
|
||||
tox_pip_extensions_ext_pip_custom_platform = true
|
||||
tox_pip_extensions_ext_venv_update = true
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/requirements-dev.txt
|
||||
passenv = HOME,SSH_AUTH_SOCK,USER
|
||||
|
||||
[testenv:venv]
|
||||
envdir = venv
|
||||
commands =
|
||||
|
||||
[testenv:install-hooks]
|
||||
deps = pre-commit
|
||||
commands = pre-commit install -f --install-hooks
|
||||
|
||||
[testenv:pre-commit]
|
||||
deps = pre-commit
|
||||
commands = pre-commit run --all-files
|
||||
|
||||
[testenv:tests]
|
||||
commands =
|
||||
pre-commit install -f --install-hooks
|
||||
pre-commit run --all-files
|
||||
|
||||
[testenv:ruff]
|
||||
deps = ruff
|
||||
commands = ruff check .
|
||||
Loading…
Add table
Reference in a new issue