Skip to content

Commit

Permalink
Added support for downloading torrents from GC (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
Casvt committed Nov 23, 2023
1 parent f9c629a commit b429887
Show file tree
Hide file tree
Showing 22 changed files with 2,632 additions and 724 deletions.
113 changes: 98 additions & 15 deletions backend/custom_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ def __init__(self) -> None:
return

class RootFolderInUse(Exception):
"""A root folder with the given id is requested to be deleted but is used by a volume
"""A root folder with the given id is requested to be deleted
but is used by a volume
"""
api_response = {'error': 'RootFolderInUse', 'result': {}, 'code': 400}

Expand All @@ -40,7 +41,8 @@ def __init__(self) -> None:
return

class VolumeNotMatched(Exception):
"""The volume with the given id was found in the database but the comicvine id returned nothing
"""The volume with the given id was found in the database
but the comicvine id returned nothing
"""
api_response = {'error': 'VolumeNotMatched', 'result': {}, 'code': 400}

Expand Down Expand Up @@ -77,7 +79,11 @@ def __init__(self, volume_id: int):

@property
def api_response(self):
return {'error': 'VoolumeDownloadedFor', 'result': {'volume_id': self.volume_id}, 'code': 400}
return {
'error': 'VolumeDownloadedFor',
'result': {'volume_id': self.volume_id},
'code': 400
}

class IssueNotFound(Exception):
"""The issue with the given id was not found
Expand Down Expand Up @@ -144,7 +150,14 @@ def __init__(self, reason_id: int, reason_text: str):

@property
def api_response(self):
return {'error': 'LinkBroken', 'result': {'reason_text': self.reason_text, 'reason_id': self.reason_id}, 'code': 400}
return {
'error': 'LinkBroken',
'result': {
'reason_text': self.reason_text,
'reason_id': self.reason_id
},
'code': 400
}

class InvalidSettingKey(Exception):
"""The setting key is unknown
Expand All @@ -157,7 +170,11 @@ def __init__(self, key: str=''):

@property
def api_response(self):
return {'error': 'InvalidSettingKey', 'result': {'key': self.key}, 'code': 400}
return {
'error': 'InvalidSettingKey',
'result': {'key': self.key},
'code': 400
}

class InvalidSettingValue(Exception):
"""The setting value is invalid
Expand All @@ -171,7 +188,11 @@ def __init__(self, key: str='', value: str=''):

@property
def api_response(self):
return {'error': 'InvalidSettingValue', 'result': {'key': self.key, 'value': self.value}, 'code': 400}
return {
'error': 'InvalidSettingValue',
'result': {'key': self.key, 'value': self.value},
'code': 400
}

class InvalidSettingModification(Exception):
"""The setting is not allowed to be changed this way
Expand All @@ -180,12 +201,18 @@ def __init__(self, key: str='', instead: str=''):
self.key = key
self.instead = instead
super().__init__(key)
logging.warning(f'This setting is not allowed to be changed this way: {key}. Instead: {instead}')
logging.warning(
f'This setting is not allowed to be changed this way: {key}.' +
f' Instead: {instead}')
return

@property
def api_response(self):
return {'error': 'InvalidSettingModification', 'result': {'key': self.key, 'instead': self.instead}, 'code': 400}
return {
'error': 'InvalidSettingModification',
'result': {'key': self.key, 'instead': self.instead},
'code': 400
}

class KeyNotFound(Exception):
"""A key that is required to be given in the api request was not found
Expand All @@ -194,7 +221,10 @@ def __init__(self, key: str=''):
self.key = key
super().__init__(self.key)
if key != 'password':
logging.warning(f'This key was not found in the API request, eventhough it\'s required: {key}')
logging.warning(
"This key was not found in the API request,"
+ f" eventhough it's required: {key}"
)
return

@property
Expand All @@ -209,12 +239,19 @@ def __init__(self, key: str='', value: str=''):
self.value = value
super().__init__(self.key)
if value not in ('undefined', 'null'):
logging.warning(f'This key in the API request has an invalid value: {key} = {value}')
logging.warning(
'This key in the API request has an invalid value: ' +
f'{key} = {value}'
)
return

@property
def api_response(self):
return {'error': 'InvalidKeyValue', 'result': {'key': self.key, 'value': self.value}, 'code': 400}
return {
'error': 'InvalidKeyValue',
'result': {'key': self.key, 'value': self.value},
'code': 400
}

class CredentialNotFound(Exception):
"""The credential with the given id was not found
Expand All @@ -235,7 +272,11 @@ def __init__(self, string: str) -> None:

@property
def api_response(self):
return {'error': 'CredentialSourceNotFound', 'result': {'string': self.string}, 'code': 404}
return {
'error': 'CredentialSourceNotFound',
'result': {'string': self.string},
'code': 404
}

class CredentialAlreadyAdded(Exception):
"""A credential for the given source is already added
Expand All @@ -259,10 +300,52 @@ class DownloadLimitReached(Exception):
"""
def __init__(self, string: str) -> None:
self.string = string
logging.warning(f'Credential source {string} has reached it\'s download limit')
logging.warning(f"Credential source {string} has reached it's download limit")
return

@property
def api_response(self):
return {'error': 'DownloadLimitReached', 'result': {'string': self.string}, 'code': 509}

return {
'error': 'DownloadLimitReached',
'result': {'string': self.string},
'code': 509
}

class TorrentClientNotFound(Exception):
"""The torrent client with the given ID was not found
"""
api_response = {'error': 'TorrentClientNotFound', 'result': {}, 'code': 404}

def __init__(self) -> None:
logging.warning('Torrent client with given id not found')
return

class TorrentClientDownloading(Exception):
"""The torrent client is desired to be deleted
but there is a torrent downloading with it
"""
def __init__(self, torrent_client_id: int):
self.torrent_client_id = torrent_client_id
super().__init__(self.torrent_client_id)
logging.warning(
f'Deleting torrent client failed because there is '
+ f'a torrent downloading with it: {self.torrent_client_id}'
)
return

@property
def api_response(self):
return {
'error': 'TorrentClientDownloading',
'result': {'torrent_client_id': self.torrent_client_id},
'code': 400
}

class TorrentClientNotWorking(Exception):
"""The torrent client is not working
"""
api_response = {'error': 'TorrentClientNotWorking', 'result': {}, 'code': 400}

def __init__(self) -> None:
logging.warning('Torrent client is not working')
return
82 changes: 71 additions & 11 deletions backend/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from backend.logging import set_log_level

__DATABASE_FILEPATH__ = 'db', 'Kapowarr.db'
__DATABASE_VERSION__ = 11
__DATABASE_VERSION__ = 12

class Singleton(type):
_instances = {}
Expand Down Expand Up @@ -93,7 +93,8 @@ def __repr__(self) -> str:
return f'<{self.__class__.__name__}; {current_thread().name}; {id(self)}>'

def set_db_location(db_file_location: str) -> None:
"""Setup database location. Create folder for database and set location for db.DBConnection
"""Setup database location. Create folder for database
and set location for db.DBConnection
Args:
db_file_location (str): The absolute path to the database file
Expand All @@ -112,8 +113,12 @@ def get_db(output_type='tuple', temp: bool=False):
"""Get a database cursor instance or create a new one if needed
Args:
output_type ('tuple'|'dict', optional): The type of output the cursor should have. Defaults to 'tuple'.
temp (bool, optional): Decides if a new manually handled cursor is returned instead of the cached one. Defaults to False.
output_type ('tuple'|'dict', optional): The type of output of the cursor.
Defaults to 'tuple'.
temp (bool, optional): Decides if a new manually handled cursor is returned
instead of the cached one.
Defaults to False.
Returns:
Cursor: Database cursor instance with desired output type set
Expand All @@ -135,7 +140,7 @@ def get_db(output_type='tuple', temp: bool=False):
return cursor

def close_db(e: str=None):
"""Close database cursor, commit database and close database (setup after each request)
"""Close database cursor, commit database and close database.
Args:
e (str, optional): Error. Defaults to None.
Expand Down Expand Up @@ -311,7 +316,10 @@ def migrate_db(current_db_version: int) -> None:
str(v[0])
for v in cursor.execute("SELECT comicvine_id FROM volumes;")
]
updates = ((r['date_last_updated'], r['comicvine_id']) for r in ComicVine().fetch_volumes(volume_ids))
updates = (
(r['date_last_updated'], r['comicvine_id'])
for r in ComicVine().fetch_volumes(volume_ids)
)
cursor.executemany(
"UPDATE volumes SET last_cv_update = ? WHERE comicvine_id = ?;",
updates
Expand Down Expand Up @@ -352,7 +360,10 @@ def migrate_db(current_db_version: int) -> None:
GROUP BY v.id;
""").fetchall()

updates = ((determine_special_version(v[1], v[2], v[3], v[4]) ,v[0]) for v in volumes)
updates = (
(determine_special_version(v[1], v[2], v[3], v[4]) ,v[0])
for v in volumes
)

cursor.executemany(
"UPDATE volumes SET special_version = ? WHERE id = ?;",
Expand Down Expand Up @@ -441,7 +452,10 @@ def migrate_db(current_db_version: int) -> None:
(volume[0],)
)
]
updates.append((determine_special_version(volume[1], volume[2], issue_titles), volume[0]))
updates.append(
(determine_special_version(volume[1], volume[2], issue_titles),
volume[0])
)

cursor.executemany(
"UPDATE volumes SET special_version = ? WHERE id = ?;",
Expand All @@ -451,6 +465,30 @@ def migrate_db(current_db_version: int) -> None:
current_db_version = 11
update_db_version(current_db_version)

if current_db_version == 11:
# V11 -> V12
cursor.executescript("""
DROP TABLE download_queue;
CREATE TABLE download_queue(
id INTEGER PRIMARY KEY,
client_type VARCHAR(255) NOT NULL,
torrent_client_id INTEGER,
link TEXT NOT NULL,
filename_body TEXT NOT NULL,
source VARCHAR(25) NOT NULL,
volume_id INTEGER NOT NULL,
issue_id INTEGER,
page_link TEXT,
FOREIGN KEY (torrent_client_id) REFERENCES torrent_clients(id),
FOREIGN KEY (volume_id) REFERENCES volumes(id),
FOREIGN KEY (issue_id) REFERENCES issues(id)
);
""")

return

def setup_db() -> None:
Expand Down Expand Up @@ -523,12 +561,29 @@ def setup_db() -> None:
issue_id
)
);
CREATE TABLE IF NOT EXISTS torrent_clients(
id INTEGER PRIMARY KEY,
type VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL,
base_url TEXT NOT NULL,
username VARCHAR(255),
password VARCHAR(255),
api_token VARCHAR(255)
);
CREATE TABLE IF NOT EXISTS download_queue(
id INTEGER PRIMARY KEY,
client_type VARCHAR(255) NOT NULL,
torrent_client_id INTEGER,
link TEXT NOT NULL,
filename_body TEXT NOT NULL,
source VARCHAR(25) NOT NULL,
volume_id INTEGER NOT NULL,
issue_id INTEGER,
page_link TEXT,
FOREIGN KEY (torrent_client_id) REFERENCES torrent_clients(id),
FOREIGN KEY (volume_id) REFERENCES volumes(id),
FOREIGN KEY (issue_id) REFERENCES issues(id)
);
Expand Down Expand Up @@ -599,7 +654,9 @@ def setup_db() -> None:
).fetchone()[0])

if current_db_version < __DATABASE_VERSION__:
logging.debug(f'Database migration: {current_db_version} -> {__DATABASE_VERSION__}')
logging.debug(
f'Database migration: {current_db_version} -> {__DATABASE_VERSION__}'
)
migrate_db(current_db_version)
# Redundant but just to be sure, in case
# the version isn't updated in the last migration of the function
Expand Down Expand Up @@ -648,7 +705,10 @@ def setup_db() -> None:
)

# Add service preferences
order = [(names[0], place + 1) for place, names in enumerate(supported_source_strings)]
order = [
(names[0], place + 1)
for place, names in enumerate(supported_source_strings)
]
logging.debug(f'Inserting service preferences: {order}')
cursor.executemany(
"""
Expand Down
Loading

0 comments on commit b429887

Please sign in to comment.