diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dbb23b4 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +.PHONY: default venv build install clean + +default: install + +venv: + @[ ! -d ./venv ] && python3 -m venv venv && bash -c \ + "source venv/bin/activate && \ + pip install --upgrade pip && \ + pip install -r requirements.txt" || true + +build: venv + @if [ -n "$$(git status --porcelain)" ]; then \ + echo "[ERROR]: There are uncommitted changes or untracked files."; \ + exit 1; \ + fi + @bash -c \ + "source venv/bin/activate && \ + pip install build twine && \ + python -m build" + +install: venv + @bash -c "source venv/bin/activate && pip install -e ." + +clean: + rm -rf venv dist tarc.egg-info diff --git a/pyproject.toml b/pyproject.toml index 68dceb1..3155ae8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,7 @@ [project] name = "tarc" -version = "0.0.1dev2" +version = "0.0.1dev3" description = "Manage BT archives" + +[project.scripts] +tarc = "tarc:main" diff --git a/tarc.sh b/tarc.sh deleted file mode 100755 index 7a9e6e3..0000000 --- a/tarc.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# Create a virtual environment if it does not exist -if [ ! -d "venv" ]; then - # shellcheck disable=SC1091 - python3 -m venv venv && \ - source venv/bin/activate && \ - pip install -r requirements.txt && \ - deactivate -fi - -# Activate the virtual environment -# shellcheck disable=SC1091 -source venv/bin/activate - -# Run the Python script -python tarc/main.py "$@" - -# Deactivate the virtual environment -deactivate diff --git a/tarc/__init__.py b/tarc/__init__.py index e69de29..9e96405 100644 --- a/tarc/__init__.py +++ b/tarc/__init__.py @@ -0,0 +1,5 @@ +""" +tarc - Manage BT archives +""" + +from .main import main diff --git a/tarc/main.py b/tarc/main.py index a24e381..1432e5e 100644 --- a/tarc/main.py +++ b/tarc/main.py @@ -154,109 +154,120 @@ def list_clients(conn): return rows -parser = argparse.ArgumentParser(description="Manage BT archives", prog="tarc") -subparsers = parser.add_subparsers( - dest="command", required=True, help="Available commands" -) +def main(): + """ + Entrypoint of the program. + """ -scan_parser = subparsers.add_parser("scan", help="Scan command") -scan_parser.add_argument("--debug", action="store_true", help="Enable debug mode") -scan_parser.add_argument( - "--confirm-add", action="store_true", help="Confirm adding a new client" -) -scan_parser.add_argument("-n", "--name", help="Name of client") -scan_parser.add_argument("-d", "--directory", help="Directory to scan") -scan_parser.add_argument("-t", "--type", help="Scan type") -scan_parser.add_argument("-e", "--endpoint", help="Endpoint URL") -scan_parser.add_argument("-u", "--username", help="Username") -scan_parser.add_argument("-p", "--password", help="Password") -scan_parser.add_argument("-s", "--storage", help="Path of sqlite3 database") + parser = argparse.ArgumentParser(description="Manage BT archives", prog="tarc") + subparsers = parser.add_subparsers( + dest="command", required=True, help="Available commands" + ) -args = parser.parse_args() + scan_parser = subparsers.add_parser("scan", help="Scan command") + scan_parser.add_argument("--debug", action="store_true", help="Enable debug mode") + scan_parser.add_argument( + "--confirm-add", action="store_true", help="Confirm adding a new client" + ) + scan_parser.add_argument("-n", "--name", help="Name of client") + scan_parser.add_argument("-d", "--directory", help="Directory to scan") + scan_parser.add_argument("-t", "--type", help="Scan type") + scan_parser.add_argument("-e", "--endpoint", help="Endpoint URL") + scan_parser.add_argument("-u", "--username", help="Username") + scan_parser.add_argument("-p", "--password", help="Password") + scan_parser.add_argument("-s", "--storage", help="Path of sqlite3 database") -if args.command == "scan": - if args.storage is None: - STORAGE = os.path.expanduser("~/.tarch.db") - else: - STORAGE = args.storage - try: - sqlitedb = sqlite3.connect(STORAGE) - tables = list_tables(sqlitedb) - except sqlite3.DatabaseError as e: - print(f'[ERROR]: Database Error "{STORAGE}" ({str(e)})') - sys.exit(1) - if len(tables) == 0: - print(f"[INFO]: Initializing database at {STORAGE}") - init_db(sqlitedb) - cursor = sqlitedb.cursor() - cursor.execute("PRAGMA user_version;") - SCHEMA_FOUND = cursor.fetchone()[0] - cursor.close() - if not SCHEMA == SCHEMA_FOUND: - print(f"[ERROR]: SCHEMA {SCHEMA_FOUND}, expected {SCHEMA}") - sys.exit(1) - if not args.directory is None: - print("[INFO]: --directory is not implemented") - sys.exit(0) - elif not args.endpoint is None: - qb = qbittorrent.Client(args.endpoint) - if qb.qbittorrent_version is None: - print(f'[ERROR]: Couldn\'t find client version at "{args.endpoint}"') - sys.exit(1) - elif not re.match(r"^v?\d+(\.\d+)*$", qb.qbittorrent_version): - print(f'[ERROR]: Invalid version found at "{args.endpoint}"') - if args.debug: - print(f"[DEBUG]: {qb.qbittorrent_version}") - sys.exit(1) + args = parser.parse_args() + + if args.command == "scan": + if args.storage is None: + STORAGE = os.path.expanduser("~/.tarch.db") else: - print( - f'[INFO]: Found qbittorrent {qb.qbittorrent_version} at "{args.endpoint}"' - ) - clients = find_client(sqlitedb, args.endpoint) - if args.confirm_add: - if len(clients) == 0: - if not args.name is None: - now = datetime.now(timezone.utc).isoformat( - sep=" ", timespec="seconds" - ) - add_client(sqlitedb, args.name, args.endpoint, now) - print(f"[INFO]: Added client {args.name} ({args.endpoint})") - else: - print("[ERROR]: Must specify --name for a new client") - sys.exit(1) - elif len(clients) == 1: - print(f"[ERROR]: {clients[0][1]} ({clients[0][2]}) already exists") + STORAGE = args.storage + try: + sqlitedb = sqlite3.connect(STORAGE) + tables = list_tables(sqlitedb) + except sqlite3.DatabaseError as e: + print(f'[ERROR]: Database Error "{STORAGE}" ({str(e)})') + sys.exit(1) + if len(tables) == 0: + print(f"[INFO]: Initializing database at {STORAGE}") + init_db(sqlitedb) + cursor = sqlitedb.cursor() + cursor.execute("PRAGMA user_version;") + SCHEMA_FOUND = cursor.fetchone()[0] + cursor.close() + if not SCHEMA == SCHEMA_FOUND: + print(f"[ERROR]: SCHEMA {SCHEMA_FOUND}, expected {SCHEMA}") + sys.exit(1) + if not args.directory is None: + print("[INFO]: --directory is not implemented") + sys.exit(0) + elif not args.endpoint is None: + qb = qbittorrent.Client(args.endpoint) + if qb.qbittorrent_version is None: + print(f'[ERROR]: Couldn\'t find client version at "{args.endpoint}"') + sys.exit(1) + elif not re.match(r"^v?\d+(\.\d+)*$", qb.qbittorrent_version): + print(f'[ERROR]: Invalid version found at "{args.endpoint}"') + if args.debug: + print(f"[DEBUG]: {qb.qbittorrent_version}") sys.exit(1) else: print( - f"[ERROR]: Multiple clients with the same endpoint: {args.endpoint}" + f'[INFO]: Found qbittorrent {qb.qbittorrent_version} at "{args.endpoint}"' + ) + clients = find_client(sqlitedb, args.endpoint) + if args.confirm_add: + if len(clients) == 0: + if not args.name is None: + now = datetime.now(timezone.utc).isoformat( + sep=" ", timespec="seconds" + ) + add_client(sqlitedb, args.name, args.endpoint, now) + print(f"[INFO]: Added client {args.name} ({args.endpoint})") + else: + print("[ERROR]: Must specify --name for a new client") + sys.exit(1) + elif len(clients) == 1: + print(f"[ERROR]: {clients[0][1]} ({clients[0][2]}) already exists") + sys.exit(1) + else: + print( + f"[ERROR]: Multiple clients with the same endpoint: {args.endpoint}" + ) + sys.exit(1) + elif len(clients) == 0: + print(f'[ERROR]: Client using endpoint "{args.endpoint}" not found') + print("[ERROR]: Use --confirm-add to add a new endpoint") + sys.exit(1) + elif len(clients) == 1: + torrents = qb.torrents() + print(f"[INFO]: There are {len(torrents)} torrents\n") + for torrent in torrents[:2]: + files = qb.get_torrent_files(torrent["hash"]) + trackers = qb.get_torrent_trackers(torrent["hash"]) + print(f"[name]: {torrent['name']}") + print(f"[infohash_v1]: {torrent['infohash_v1']}") + print(f"[content_path]: {torrent['content_path']}") + print(f"[magent_uri]: {torrent['magnet_uri'][0:80]}") + print(f"[completed_on]: {torrent['completed']}") + print(f"[trackers]: {len(trackers)}") + print(f"[file_count]: {len(files)}\n") + if args.debug: + print(f"[DEBUG]: {repr(torrent)}") + for elem in trackers: + print(f"[DEBUG]: Tracker {repr(elem)}") + print("\n", end="") + else: + print( + f'[ERROR]: Multiple clients ({len(clients)}) using "{args.endpoint}"' ) sys.exit(1) - elif len(clients) == 0: - print(f'[ERROR]: Client using endpoint "{args.endpoint}" not found') - print("[ERROR]: Use --confirm-add to add a new endpoint") - sys.exit(1) - elif len(clients) == 1: - torrents = qb.torrents() - print(f"[INFO]: There are {len(torrents)} torrents\n") - for torrent in torrents[:2]: - files = qb.get_torrent_files(torrent["hash"]) - trackers = qb.get_torrent_trackers(torrent["hash"]) - print(f"[name]: {torrent['name']}") - print(f"[infohash_v1]: {torrent['infohash_v1']}") - print(f"[content_path]: {torrent['content_path']}") - print(f"[magent_uri]: {torrent['magnet_uri'][0:80]}") - print(f"[completed_on]: {torrent['completed']}") - print(f"[trackers]: {len(trackers)}") - print(f"[file_count]: {len(files)}\n") - if args.debug: - print(f"[DEBUG]: {repr(torrent)}") - for elem in trackers: - print(f"[DEBUG]: Tracker {repr(elem)}") - print("\n", end="") else: - print(f'[ERROR]: Multiple clients ({len(clients)}) using "{args.endpoint}"') + print("[ERROR]: Must specify directory OR client endpoint") sys.exit(1) - else: - print("[ERROR]: Must specify directory OR client endpoint") - sys.exit(1) + + +if __name__ == "__main__": + main()