Compare commits
1 Commits
a68bea95d2
...
aitest
Author | SHA1 | Date | |
---|---|---|---|
998872cb4f |
@@ -1,2 +1,2 @@
|
|||||||
qbittorrent-api==2025.2.0
|
python-qbittorrent==0.4.3
|
||||||
SQLAlchemy==2.0.38
|
sqlalchemy==2.0.27
|
||||||
|
305
tarc/main.py
305
tarc/main.py
@@ -15,225 +15,194 @@ import uuid
|
|||||||
import argparse
|
import argparse
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import qbittorrentapi
|
import qbittorrent
|
||||||
from sqlalchemy import create_engine, inspect
|
from sqlalchemy import create_engine, event
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
from .models import Base, SchemaVersion, Client
|
from .models import Base, Client
|
||||||
|
|
||||||
# SCHEMA format is YYYYMMDDX
|
# SCHEMA format is YYYYMMDDX
|
||||||
SCHEMA = 202503100
|
SCHEMA = 202410060
|
||||||
|
|
||||||
|
|
||||||
def init_db(engine):
|
def init_db(engine):
|
||||||
"""
|
"""
|
||||||
Initialize database
|
Initialize database
|
||||||
"""
|
"""
|
||||||
|
# Create all tables first
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
|
|
||||||
with Session(engine) as session:
|
# Set the schema version using SQLAlchemy primitives
|
||||||
if not session.query(SchemaVersion).first():
|
@event.listens_for(engine, 'connect')
|
||||||
now = datetime.now(timezone.utc)
|
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||||
version = SchemaVersion(version=SCHEMA, applied_at=now)
|
cursor = dbapi_connection.cursor()
|
||||||
session.add(version)
|
cursor.execute(f"PRAGMA user_version = {SCHEMA}")
|
||||||
session.commit()
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
def get_schema_version(engine):
|
|
||||||
"""
|
|
||||||
Get current schema version from database
|
|
||||||
"""
|
|
||||||
with Session(engine) as session:
|
|
||||||
version = session.query(SchemaVersion).order_by(SchemaVersion.id.desc()).first()
|
|
||||||
return version.version if version else None
|
|
||||||
|
|
||||||
|
|
||||||
def list_tables(engine):
|
def list_tables(engine):
|
||||||
"""
|
"""
|
||||||
List all tables in database
|
List all tables in database
|
||||||
"""
|
"""
|
||||||
inspector = inspect(engine)
|
return Base.metadata.tables.keys()
|
||||||
return inspector.get_table_names()
|
|
||||||
|
|
||||||
|
|
||||||
def add_client(engine, name, endpoint, last_seen):
|
def add_client(session, name, endpoint, last_seen):
|
||||||
"""
|
"""
|
||||||
Add a new client endpoint to database
|
Add a new client endpoint to database
|
||||||
"""
|
"""
|
||||||
with Session(engine) as session:
|
new_client = Client(
|
||||||
client = Client(
|
uuid=str(uuid.uuid4()),
|
||||||
uuid=str(uuid.uuid4()), name=name, endpoint=endpoint, last_seen=last_seen
|
name=name,
|
||||||
)
|
endpoint=endpoint,
|
||||||
session.add(client)
|
last_seen=last_seen
|
||||||
session.commit()
|
)
|
||||||
|
session.add(new_client)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
def find_client(engine, endpoint):
|
def find_client(session, endpoint):
|
||||||
"""
|
"""
|
||||||
Find existing client
|
Find existing client
|
||||||
"""
|
"""
|
||||||
with Session(engine) as session:
|
clients = session.query(Client.id, Client.name, Client.uuid).filter(Client.endpoint == endpoint).all()
|
||||||
clients = (
|
return clients
|
||||||
session.query(Client.id, Client.name, Client.uuid)
|
|
||||||
.filter_by(endpoint=endpoint)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
return clients
|
|
||||||
|
|
||||||
|
|
||||||
def list_clients(engine):
|
def list_clients(session):
|
||||||
"""
|
"""
|
||||||
List all stored clients
|
List all stored clients
|
||||||
"""
|
"""
|
||||||
with Session(engine) as session:
|
return session.query(Client).all()
|
||||||
return session.query(Client).all()
|
|
||||||
|
|
||||||
|
|
||||||
def authenticate_qbittorrent(endpoint, username, password):
|
|
||||||
"""
|
|
||||||
Authenticate with the qBittorrent client
|
|
||||||
"""
|
|
||||||
qb = qbittorrentapi.Client(host=endpoint, username=username, password=password)
|
|
||||||
try:
|
|
||||||
qb.auth_log_in()
|
|
||||||
except qbittorrentapi.LoginFailed as e:
|
|
||||||
raise ValueError(f'Login failed for endpoint "{endpoint}": {e}') from e
|
|
||||||
if not re.match(r"^v?\d+(\.\d+)*$", qb.app.version):
|
|
||||||
raise ValueError(f'Invalid version "{qb.app.version}" found at "{endpoint}"')
|
|
||||||
return qb
|
|
||||||
|
|
||||||
|
|
||||||
def scan_torrents(qb_client, debug=False):
|
|
||||||
"""
|
|
||||||
Scan torrents using the provided qBittorrent client.
|
|
||||||
"""
|
|
||||||
torrents = qb_client.torrents_info()
|
|
||||||
print(f"[INFO]: There are {len(torrents)} torrents\n")
|
|
||||||
for torrent in torrents[:2]:
|
|
||||||
files = qb_client.torrents_files(torrent.hash)
|
|
||||||
trackers = qb_client.torrents_trackers(torrent.hash)
|
|
||||||
print(f"[name]: {torrent.name}")
|
|
||||||
print(f"[infohash_v1]: {torrent.hash}")
|
|
||||||
print(f"[content_path]: {torrent.content_path}")
|
|
||||||
print(f"[magnet_uri]: {torrent.magnet_uri[:80]}")
|
|
||||||
print(f"[completed_on]: {torrent.completed}\n")
|
|
||||||
print(f"[trackers]: {len(trackers)}")
|
|
||||||
print(f"[file_count]: {len(files)}\n")
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG]: {repr(torrent)}")
|
|
||||||
for elem in trackers:
|
|
||||||
print(f"[DEBUG]: Tracker {repr(elem)}")
|
|
||||||
print("\n", end="")
|
|
||||||
|
|
||||||
|
|
||||||
def handle_scan(args, engine):
|
|
||||||
"""
|
|
||||||
Handle the scan command to authenticate with the qBittorrent client and scan torrents.
|
|
||||||
"""
|
|
||||||
if args.endpoint:
|
|
||||||
qb_client = authenticate_qbittorrent(args.endpoint, args.username, args.password)
|
|
||||||
clients = find_client(engine, args.endpoint)
|
|
||||||
if args.confirm_add:
|
|
||||||
if len(clients) == 0:
|
|
||||||
if args.name:
|
|
||||||
now = datetime.now(timezone.utc)
|
|
||||||
add_client(engine, args.name, args.endpoint, now)
|
|
||||||
print(f"[INFO]: Added client {args.name} ({args.endpoint})")
|
|
||||||
else:
|
|
||||||
raise ValueError("Must specify --name for a new client")
|
|
||||||
elif len(clients) == 1:
|
|
||||||
raise ValueError(f"{clients[0][1]} ({clients[0][2]}) already exists")
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Multiple clients with the same endpoint: {args.endpoint}")
|
|
||||||
elif len(clients) == 0:
|
|
||||||
raise ValueError(
|
|
||||||
f'Client using endpoint "{args.endpoint}" not found. '
|
|
||||||
'Use --confirm-add to add a new endpoint'
|
|
||||||
)
|
|
||||||
elif len(clients) == 1:
|
|
||||||
scan_torrents(qb_client, debug=args.debug)
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Multiple clients ({len(clients)}) using "{args.endpoint}"')
|
|
||||||
elif args.directory:
|
|
||||||
print("[INFO]: --directory is not implemented")
|
|
||||||
else:
|
|
||||||
raise ValueError("Must specify directory OR client endpoint")
|
|
||||||
|
|
||||||
|
|
||||||
def handle_client_add(args, engine):
|
|
||||||
"""
|
|
||||||
Handle the client addition command to add a new client to the database.
|
|
||||||
"""
|
|
||||||
if args.name and args.endpoint:
|
|
||||||
now = datetime.now(timezone.utc)
|
|
||||||
add_client(engine, args.name, args.endpoint, now)
|
|
||||||
print(f"[INFO]: Added client {args.name} ({args.endpoint})")
|
|
||||||
else:
|
|
||||||
raise ValueError("Must specify --name and --endpoint to add a client")
|
|
||||||
|
|
||||||
|
|
||||||
def handle_client_list(args, engine):
|
|
||||||
"""
|
|
||||||
Handle the client listing command to display all stored clients.
|
|
||||||
"""
|
|
||||||
clients = list_clients(engine)
|
|
||||||
for client in clients:
|
|
||||||
print(f"{client.name} ({client.endpoint}) - Last seen: {client.last_seen}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
Parses command-line arguments and executes the corresponding command.
|
Entrypoint of the program.
|
||||||
"""
|
"""
|
||||||
parser = argparse.ArgumentParser(description="Manage BT archives", prog="tarc")
|
|
||||||
subparsers = parser.add_subparsers(dest="command", required=True, help="Available commands")
|
|
||||||
|
|
||||||
# scan command
|
parser = argparse.ArgumentParser(description="Manage BT archives", prog="tarc")
|
||||||
scan_parser = subparsers.add_parser("scan", help="Scan torrents")
|
subparsers = parser.add_subparsers(
|
||||||
|
dest="command", required=True, help="Available commands"
|
||||||
|
)
|
||||||
|
|
||||||
|
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("--debug", action="store_true", help="Enable debug mode")
|
||||||
scan_parser.add_argument(
|
scan_parser.add_argument(
|
||||||
"--confirm-add", action="store_true", help="Confirm adding a new client"
|
"--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("-n", "--name", help="Name of client")
|
||||||
scan_parser.add_argument("-d", "--directory", help="Directory to scan")
|
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("-e", "--endpoint", help="Endpoint URL")
|
||||||
scan_parser.add_argument("-u", "--username", help="Username")
|
scan_parser.add_argument("-u", "--username", help="Username")
|
||||||
scan_parser.add_argument("-p", "--password", help="Password")
|
scan_parser.add_argument("-p", "--password", help="Password")
|
||||||
scan_parser.add_argument("-s", "--storage", help="Path of sqlite3 database")
|
scan_parser.add_argument("-s", "--storage", help="Path of sqlite3 database")
|
||||||
|
|
||||||
# client add command
|
|
||||||
client_add_parser = subparsers.add_parser("client-add", help="Add a new client")
|
|
||||||
client_add_parser.add_argument("-n", "--name", required=True, help="Name of client")
|
|
||||||
client_add_parser.add_argument("-e", "--endpoint", required=True, help="Endpoint URL")
|
|
||||||
client_add_parser.add_argument("-s", "--storage", help="Path of sqlite3 database")
|
|
||||||
|
|
||||||
# client list command
|
|
||||||
client_list_parser = subparsers.add_parser("client-list", help="List all clients")
|
|
||||||
client_list_parser.add_argument("-s", "--storage", help="Path of sqlite3 database")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
storage_path = args.storage or os.path.expanduser("~/.tarc.db")
|
if args.command == "scan":
|
||||||
engine = create_engine(f"sqlite:///{storage_path}")
|
if args.storage is None:
|
||||||
|
STORAGE = os.path.expanduser("~/.tarch.db")
|
||||||
|
else:
|
||||||
|
STORAGE = args.storage
|
||||||
|
try:
|
||||||
|
engine = create_engine(f"sqlite:///{STORAGE}")
|
||||||
|
tables = list_tables(engine)
|
||||||
|
Session = sessionmaker(bind=engine)
|
||||||
|
session = Session()
|
||||||
|
except Exception 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(engine)
|
||||||
|
|
||||||
if not list_tables(engine):
|
# Check schema version using SQLAlchemy primitives
|
||||||
print(f"[INFO]: Initializing database at {storage_path}")
|
schema_found = None
|
||||||
init_db(engine)
|
|
||||||
|
|
||||||
schema_found = get_schema_version(engine)
|
@event.listens_for(engine, 'connect')
|
||||||
if schema_found != SCHEMA:
|
def get_sqlite_pragma(dbapi_connection, connection_record):
|
||||||
raise ValueError(f"SCHEMA {schema_found}, expected {SCHEMA}")
|
nonlocal schema_found
|
||||||
|
cursor = dbapi_connection.cursor()
|
||||||
|
cursor.execute("PRAGMA user_version")
|
||||||
|
schema_found = cursor.fetchone()[0]
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
try:
|
# Force a connection to trigger the event
|
||||||
if args.command == "scan":
|
with engine.connect() as conn:
|
||||||
handle_scan(args, engine)
|
pass
|
||||||
elif args.command == "client-add":
|
|
||||||
handle_client_add(args, engine)
|
if not SCHEMA == schema_found:
|
||||||
elif args.command == "client-list":
|
print(f"[ERROR]: SCHEMA {schema_found}, expected {SCHEMA}")
|
||||||
handle_client_list(args, engine)
|
sys.exit(1)
|
||||||
except ValueError as e:
|
if not args.directory is None:
|
||||||
print(f"[ERROR]: {e}")
|
print("[INFO]: --directory is not implemented")
|
||||||
sys.exit(1)
|
sys.exit(0)
|
||||||
|
elif not args.endpoint is None:
|
||||||
|
qb = qbittorrent.Client(args.endpoint)
|
||||||
|
if args.username and args.password:
|
||||||
|
qb.login(args.username, args.password)
|
||||||
|
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'[INFO]: Found qbittorrent {qb.qbittorrent_version} at "{args.endpoint}"'
|
||||||
|
)
|
||||||
|
clients = find_client(session, args.endpoint)
|
||||||
|
if args.confirm_add:
|
||||||
|
if len(clients) == 0:
|
||||||
|
if not args.name is None:
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
add_client(session, 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)
|
||||||
|
else:
|
||||||
|
print("[ERROR]: Must specify directory OR client endpoint")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
127
tarc/models.py
127
tarc/models.py
@@ -1,106 +1,109 @@
|
|||||||
"""SQLAlchemy models for the tarc database."""
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Database models for Torrent Archiver
|
||||||
|
"""
|
||||||
|
|
||||||
from sqlalchemy import (
|
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, UniqueConstraint
|
||||||
Column,
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
Integer,
|
from sqlalchemy.orm import relationship
|
||||||
String,
|
|
||||||
DateTime,
|
|
||||||
Boolean,
|
|
||||||
ForeignKey,
|
|
||||||
UniqueConstraint,
|
|
||||||
)
|
|
||||||
from sqlalchemy.orm import declarative_base
|
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
class Client(Base):
|
||||||
|
__tablename__ = 'clients'
|
||||||
|
|
||||||
class SchemaVersion(Base): # pylint: disable=too-few-public-methods
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
"""Database schema version tracking."""
|
|
||||||
|
|
||||||
__tablename__ = "schema_version"
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
version = Column(Integer, nullable=False)
|
|
||||||
applied_at = Column(DateTime, nullable=False)
|
|
||||||
|
|
||||||
|
|
||||||
class Client(Base): # pylint: disable=too-few-public-methods
|
|
||||||
"""BitTorrent client instance."""
|
|
||||||
|
|
||||||
__tablename__ = "clients"
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
name = Column(String, nullable=False, unique=True)
|
name = Column(String, nullable=False, unique=True)
|
||||||
uuid = Column(String, nullable=False, unique=True)
|
uuid = Column(String, nullable=False, unique=True)
|
||||||
endpoint = Column(String, nullable=False)
|
endpoint = Column(String, nullable=False)
|
||||||
last_seen = Column(DateTime, nullable=False)
|
last_seen = Column(DateTime, nullable=False)
|
||||||
|
|
||||||
|
torrent_clients = relationship("TorrentClient", back_populates="client")
|
||||||
|
torrent_trackers = relationship("TorrentTracker", back_populates="client")
|
||||||
|
torrent_files = relationship("TorrentFile", back_populates="client")
|
||||||
|
|
||||||
class Torrent(Base): # pylint: disable=too-few-public-methods
|
|
||||||
"""BitTorrent metadata."""
|
|
||||||
|
|
||||||
__tablename__ = "torrents"
|
class Torrent(Base):
|
||||||
id = Column(Integer, primary_key=True)
|
__tablename__ = 'torrents'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
info_hash_v1 = Column(String, nullable=False, unique=True)
|
info_hash_v1 = Column(String, nullable=False, unique=True)
|
||||||
info_hash_v2 = Column(String, unique=True)
|
info_hash_v2 = Column(String, unique=True)
|
||||||
file_count = Column(Integer, nullable=False)
|
file_count = Column(Integer, nullable=False)
|
||||||
completed_on = Column(DateTime, nullable=False)
|
completed_on = Column(DateTime, nullable=False)
|
||||||
|
|
||||||
|
torrent_clients = relationship("TorrentClient", back_populates="torrent")
|
||||||
|
torrent_trackers = relationship("TorrentTracker", back_populates="torrent")
|
||||||
|
torrent_files = relationship("TorrentFile", back_populates="torrent")
|
||||||
|
|
||||||
class TorrentClient(Base): # pylint: disable=too-few-public-methods
|
|
||||||
"""Association between torrents and clients."""
|
|
||||||
|
|
||||||
__tablename__ = "torrent_clients"
|
class TorrentClient(Base):
|
||||||
id = Column(Integer, primary_key=True)
|
__tablename__ = 'torrent_clients'
|
||||||
torrent_id = Column(Integer, ForeignKey("torrents.id"), nullable=False)
|
|
||||||
client_id = Column(Integer, ForeignKey("clients.id"), nullable=False)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
torrent_id = Column(Integer, ForeignKey('torrents.id'), nullable=False)
|
||||||
|
client_id = Column(Integer, ForeignKey('clients.id'), nullable=False)
|
||||||
name = Column(String, nullable=False)
|
name = Column(String, nullable=False)
|
||||||
content_path = Column(String, nullable=False)
|
content_path = Column(String, nullable=False)
|
||||||
last_seen = Column(DateTime, nullable=False)
|
last_seen = Column(DateTime, nullable=False)
|
||||||
__table_args__ = (UniqueConstraint("torrent_id", "client_id"),)
|
|
||||||
|
torrent = relationship("Torrent", back_populates="torrent_clients")
|
||||||
|
client = relationship("Client", back_populates="torrent_clients")
|
||||||
|
|
||||||
|
__table_args__ = (UniqueConstraint('torrent_id', 'client_id'),)
|
||||||
|
|
||||||
|
|
||||||
class Tracker(Base): # pylint: disable=too-few-public-methods
|
class Tracker(Base):
|
||||||
"""BitTorrent tracker information."""
|
__tablename__ = 'trackers'
|
||||||
|
|
||||||
__tablename__ = "trackers"
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
url = Column(String, nullable=False, unique=True)
|
url = Column(String, nullable=False, unique=True)
|
||||||
last_seen = Column(DateTime, nullable=False)
|
last_seen = Column(DateTime, nullable=False)
|
||||||
|
|
||||||
|
torrent_trackers = relationship("TorrentTracker", back_populates="tracker")
|
||||||
|
|
||||||
class TorrentTracker(Base): # pylint: disable=too-few-public-methods
|
|
||||||
"""Association between torrents and trackers."""
|
|
||||||
|
|
||||||
__tablename__ = "torrent_trackers"
|
class TorrentTracker(Base):
|
||||||
id = Column(Integer, primary_key=True)
|
__tablename__ = 'torrent_trackers'
|
||||||
client_id = Column(Integer, ForeignKey("clients.id"), nullable=False)
|
|
||||||
torrent_id = Column(Integer, ForeignKey("torrents.id"), nullable=False)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
tracker_id = Column(Integer, ForeignKey("trackers.id"), nullable=False)
|
client_id = Column(Integer, ForeignKey('clients.id'), nullable=False)
|
||||||
|
torrent_id = Column(Integer, ForeignKey('torrents.id'), nullable=False)
|
||||||
|
tracker_id = Column(Integer, ForeignKey('trackers.id'), nullable=False)
|
||||||
last_seen = Column(DateTime, nullable=False)
|
last_seen = Column(DateTime, nullable=False)
|
||||||
__table_args__ = (UniqueConstraint("client_id", "torrent_id", "tracker_id"),)
|
|
||||||
|
client = relationship("Client", back_populates="torrent_trackers")
|
||||||
|
torrent = relationship("Torrent", back_populates="torrent_trackers")
|
||||||
|
tracker = relationship("Tracker", back_populates="torrent_trackers")
|
||||||
|
|
||||||
|
__table_args__ = (UniqueConstraint('client_id', 'torrent_id', 'tracker_id'),)
|
||||||
|
|
||||||
|
|
||||||
class File(Base): # pylint: disable=too-few-public-methods
|
class File(Base):
|
||||||
"""File metadata and hashes."""
|
__tablename__ = 'files'
|
||||||
|
|
||||||
__tablename__ = "files"
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
size = Column(Integer, nullable=False)
|
size = Column(Integer, nullable=False)
|
||||||
oshash = Column(String, nullable=False, unique=True)
|
oshash = Column(String, nullable=False, unique=True)
|
||||||
hash = Column(String, unique=True)
|
hash = Column(String, unique=True)
|
||||||
|
|
||||||
|
torrent_files = relationship("TorrentFile", back_populates="file")
|
||||||
|
|
||||||
class TorrentFile(Base): # pylint: disable=too-few-public-methods
|
|
||||||
"""Association between torrents and files."""
|
|
||||||
|
|
||||||
__tablename__ = "torrent_files"
|
class TorrentFile(Base):
|
||||||
id = Column(Integer, primary_key=True)
|
__tablename__ = 'torrent_files'
|
||||||
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
|
|
||||||
torrent_id = Column(Integer, ForeignKey("torrents.id"), nullable=False)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
client_id = Column(Integer, ForeignKey("clients.id"), nullable=False)
|
file_id = Column(Integer, ForeignKey('files.id'), nullable=False)
|
||||||
|
torrent_id = Column(Integer, ForeignKey('torrents.id'), nullable=False)
|
||||||
|
client_id = Column(Integer, ForeignKey('clients.id'), nullable=False)
|
||||||
file_index = Column(Integer, nullable=False)
|
file_index = Column(Integer, nullable=False)
|
||||||
file_path = Column(String, nullable=False)
|
file_path = Column(String, nullable=False)
|
||||||
is_downloaded = Column(Boolean, nullable=False)
|
is_downloaded = Column(Boolean, nullable=False)
|
||||||
last_checked = Column(DateTime, nullable=False)
|
last_checked = Column(DateTime, nullable=False)
|
||||||
__table_args__ = (
|
|
||||||
UniqueConstraint("file_id", "torrent_id", "client_id", "file_index"),
|
file = relationship("File", back_populates="torrent_files")
|
||||||
)
|
torrent = relationship("Torrent", back_populates="torrent_files")
|
||||||
|
client = relationship("Client", back_populates="torrent_files")
|
||||||
|
|
||||||
|
__table_args__ = (UniqueConstraint('file_id', 'torrent_id', 'client_id', 'file_index'),)
|
Reference in New Issue
Block a user