Add Makefile and refactor package structure

- Replace shell script with Makefile for build/install tasks
- Move main logic into main() function for proper packaging
- Configure package entrypoint in pyproject.toml
This commit is contained in:
Kris Lamoureux 2024-10-12 22:40:18 -04:00
parent 4ab46ee2fc
commit b23ca49a83
Signed by: kris
GPG Key ID: 3EDA9C3441EDA925
5 changed files with 140 additions and 116 deletions

25
Makefile Normal file
View File

@ -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

View File

@ -1,4 +1,7 @@
[project] [project]
name = "tarc" name = "tarc"
version = "0.0.1dev2" version = "0.0.1dev3"
description = "Manage BT archives" description = "Manage BT archives"
[project.scripts]
tarc = "tarc:main"

20
tarc.sh
View File

@ -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

View File

@ -0,0 +1,5 @@
"""
tarc - Manage BT archives
"""
from .main import main

View File

@ -154,109 +154,120 @@ def list_clients(conn):
return rows return rows
parser = argparse.ArgumentParser(description="Manage BT archives", prog="tarc") def main():
subparsers = parser.add_subparsers( """
dest="command", required=True, help="Available commands" Entrypoint of the program.
) """
scan_parser = subparsers.add_parser("scan", help="Scan command") parser = argparse.ArgumentParser(description="Manage BT archives", prog="tarc")
scan_parser.add_argument("--debug", action="store_true", help="Enable debug mode") subparsers = parser.add_subparsers(
scan_parser.add_argument( dest="command", required=True, help="Available commands"
"--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")
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": args = parser.parse_args()
if args.storage is None:
STORAGE = os.path.expanduser("~/.tarch.db") if args.command == "scan":
else: if args.storage is None:
STORAGE = args.storage STORAGE = os.path.expanduser("~/.tarch.db")
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: else:
print( STORAGE = args.storage
f'[INFO]: Found qbittorrent {qb.qbittorrent_version} at "{args.endpoint}"' try:
) sqlitedb = sqlite3.connect(STORAGE)
clients = find_client(sqlitedb, args.endpoint) tables = list_tables(sqlitedb)
if args.confirm_add: except sqlite3.DatabaseError as e:
if len(clients) == 0: print(f'[ERROR]: Database Error "{STORAGE}" ({str(e)})')
if not args.name is None: sys.exit(1)
now = datetime.now(timezone.utc).isoformat( if len(tables) == 0:
sep=" ", timespec="seconds" print(f"[INFO]: Initializing database at {STORAGE}")
) init_db(sqlitedb)
add_client(sqlitedb, args.name, args.endpoint, now) cursor = sqlitedb.cursor()
print(f"[INFO]: Added client {args.name} ({args.endpoint})") cursor.execute("PRAGMA user_version;")
else: SCHEMA_FOUND = cursor.fetchone()[0]
print("[ERROR]: Must specify --name for a new client") cursor.close()
sys.exit(1) if not SCHEMA == SCHEMA_FOUND:
elif len(clients) == 1: print(f"[ERROR]: SCHEMA {SCHEMA_FOUND}, expected {SCHEMA}")
print(f"[ERROR]: {clients[0][1]} ({clients[0][2]}) already exists") 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) sys.exit(1)
else: else:
print( 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) 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: else:
print(f'[ERROR]: Multiple clients ({len(clients)}) using "{args.endpoint}"') print("[ERROR]: Must specify directory OR client endpoint")
sys.exit(1) sys.exit(1)
else:
print("[ERROR]: Must specify directory OR client endpoint")
sys.exit(1) if __name__ == "__main__":
main()