Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(qbtools): refactor a bit #72

Merged
merged 2 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
FROM docker.io/library/python:3.12-alpine as base

Check warning on line 1 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-image

The 'as' keyword should match the case of the 'from' keyword

FromAsCasing: 'as' and 'FROM' keywords' casing do not match More info: https://docs.docker.com/go/dockerfile/rule/from-as-casing/

FROM base as pip

Check warning on line 3 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-image

The 'as' keyword should match the case of the 'from' keyword

FromAsCasing: 'as' and 'FROM' keywords' casing do not match More info: https://docs.docker.com/go/dockerfile/rule/from-as-casing/
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --no-cache-dir --prefix=/install -r /requirements.txt \
&& python3 -c "import compileall; compileall.compile_path(maxlevels=10)"

FROM base as app

Check warning on line 9 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-image

The 'as' keyword should match the case of the 'from' keyword

FromAsCasing: 'as' and 'FROM' keywords' casing do not match More info: https://docs.docker.com/go/dockerfile/rule/from-as-casing/
WORKDIR /app
COPY qbtools/ .
RUN python3 -m compileall qbtools.py commands/

FROM base as final

Check warning on line 14 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-image

The 'as' keyword should match the case of the 'from' keyword

FromAsCasing: 'as' and 'FROM' keywords' casing do not match More info: https://docs.docker.com/go/dockerfile/rule/from-as-casing/
WORKDIR /app
COPY --from=pip /install /usr/local
COPY --from=app /app .
COPY config.yaml /config/config.yaml
ENTRYPOINT ["python3", "qbtools.py"]
19 changes: 11 additions & 8 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
---
trackers:
- name: iptorrents
urls:
- bgp.technology
- empirehost.me
- stackoverflow.tech
required_seed_ratio: 1.05
required_seed_days: 14.5
trackers: {}

# Example:
# trackers:
# - name: iptorrents
# urls:
# - bgp.technology
# - empirehost.me
# - stackoverflow.tech
# required_seed_ratio: 1.05
# required_seed_days: 14.5
77 changes: 37 additions & 40 deletions qbtools/qbtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

logger = logging.getLogger(__name__)


def add_default_args(parser):
parser.add_argument(
"-c", "--config",
Expand All @@ -33,9 +34,26 @@ def add_default_args(parser):
help="Password for qBittorrent"
)


def load_commands(subparsers):
for cmd in os.listdir(f"{os.path.dirname(__file__)}/commands"):
if cmd.startswith("__") or not cmd.endswith(".py"):
continue

name = cmd[:-3]
try:
mod = importlib.import_module(f"commands.{name}")
mod.add_arguments(subparsers)
except ImportError:
logger.error(f"Error loading module: {name}", exc_info=True)
sys.exit(1)
else:
globals()[name] = mod


def qbit_client(app):
if not app.server or not app.port:
logger.error("Server and port must be specified.")
logger.error("Server and port must be specified")
sys.exit(1)

client = qbittorrentapi.Client(
Expand All @@ -46,27 +64,31 @@ def qbit_client(app):

try:
client.auth_log_in()
except qbittorrentapi.LoginFailed as e:
logger.error(f"Login failed: {e}")
except qbittorrentapi.APIConnectionError:
logger.error("Error connecting to qBittorrent", exc_info=True)
sys.exit(1)
except qbittorrentapi.LoginFailed:
logger.error("Login failed to qBittorrent", exc_info=True)
sys.exit(1)
else:
return client

return client

def get_config(app, key=None, default=None):
def get_config(app):
try:
with open(app.config, "r") as stream:
config = yaml.safe_load(stream)
except FileNotFoundError:
logger.warning(f"Configuration file not found: {app.config}")
config = {}
except yaml.YAMLError as e:
logger.error(f"Error parsing configuration file: {e}")
logger.error(f"Configuration file not found: {app.config}", exc_info=True)
sys.exit(1)
except yaml.YAMLError:
logger.error(f"Error parsing configuration file: {app.config}", exc_info=True)
sys.exit(1)
else:
return config

return config.get(key, default) if key else config

def main():
# Configure logging
logging.getLogger("filelock").setLevel(logging.ERROR) # Suppress lock messages
logging.basicConfig(
stream=sys.stdout,
Expand All @@ -75,50 +97,25 @@ def main():
datefmt="%I:%M:%S %p",
)

# Parse command-line arguments
parser = argparse.ArgumentParser(description="qBittorrent API Client")
add_default_args(parser)
subparsers = parser.add_subparsers(dest="command")

# Dynamically load command modules
commands_dir = "commands"
if not os.path.isdir(commands_dir):
logger.error(f"Commands directory not found: {commands_dir}")
sys.exit(1)

for cmd in os.listdir(commands_dir):
if cmd.endswith(".py"):
name = cmd[:-3]
try:
mod = importlib.import_module(f"commands.{name}")
mod.add_arguments(subparsers)
except ImportError as e:
logger.error(f"Error loading module '{name}': {e}")
sys.exit(1)
load_commands(subparsers) # Load all commands

app = parser.parse_args()

# If no command is specified, display help
if not app.command:
parser.print_help()
sys.exit(1)

# Initialize qBittorrent client
app.client = qbit_client(app)

# Load configuration
app.config = get_config(app)

# Execute the specified command
mod = globals().get(app.command)
if not mod:
logger.error(f"Command not found: {app.command}")
sys.exit(1)

try:
mod = globals()[app.command]
mod.__init__(app, logger)
except Exception as e:
logger.error(f"Error executing command '{app.command}': {e}")
except Exception:
logger.error(f"Error executing command: {app.command}", exc_info=True)
sys.exit(1)
finally:
app.client.auth_log_out()
Expand Down