diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47976ea..fbdc1ed 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,3 +22,10 @@ repos: hooks: - id: isort additional_dependencies: ["toml"] + + # Python code formatting + - repo: https://github.com/psf/black + rev: stable + hooks: + - id: black + language_version: python3.7 diff --git a/__init__.py b/__init__.py index c173435..9171045 100755 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ import sys -sys.path.insert(1, 'app/') -sys.path.insert(2, 'frontend/') +sys.path.insert(1, "app/") +sys.path.insert(2, "frontend/") diff --git a/app/lib/api_hooks.py b/app/lib/api_hooks.py index 239f88a..54fc80f 100755 --- a/app/lib/api_hooks.py +++ b/app/lib/api_hooks.py @@ -8,6 +8,7 @@ import requests class DuckDuckGo: """duckduckgo related searching""" + def __init__(self): self.url = "https://api.duckduckgo.com/?q=" @@ -16,15 +17,19 @@ class DuckDuckGo: Returns json containing url to image :param _key: &t=h_&iar=images&iax=images&ia=images&format=json&pretty=1 """ - _key = '&t=h_&iar=images&iax=images&ia=images&format=json&pretty=1' - try: query = query.string - except AttributeError: query = query - search_result = requests.get(self.url+query+_key) - try: image_result = search_result.json()['Image'] + _key = "&t=h_&iar=images&iax=images&ia=images&format=json&pretty=1" + try: + query = query.string + except AttributeError: + query = query + search_result = requests.get(self.url + query + _key) + try: + image_result = search_result.json()["Image"] except ValueError: - image_result = '' - if search_result.status_code == 200 and image_result != '': - image = requests.get(search_result.json()['Image'], stream=True) + image_result = "" + if search_result.status_code == 200 and image_result != "": + image = requests.get(search_result.json()["Image"], stream=True) image.raw.decode_content = True return image.raw - else: return False + else: + return False diff --git a/app/lib/config.py b/app/lib/config.py index 288b27d..a84037d 100755 --- a/app/lib/config.py +++ b/app/lib/config.py @@ -7,25 +7,26 @@ class Config: """ Main System Configuration """ + _fp = "config.json" print(os.path) - def __init__(self, root=os.path.abspath('../')): + def __init__(self, root=os.path.abspath("../")): _data = self.open_file(root) - self.book_path = _data['BOOKPATH'] - self.TITLE = _data['TITLE'] - self.VERSION = _data['VERSION'] + self.book_path = _data["BOOKPATH"] + self.TITLE = _data["TITLE"] + self.VERSION = _data["VERSION"] self.TITLE = self.TITLE + " ver " + self.VERSION - self.book_shelf = _data['BOOKSHELF'] + self.book_shelf = _data["BOOKSHELF"] # self.catalogue_db = "data/catalogue.db" - self.catalogue_db = root+'/'+_data['DATABASE'] + self.catalogue_db = root + "/" + _data["DATABASE"] self.file_array = [ self.book_shelf, self.catalogue_db, - ] + ] self.auto_scan = True def open_file(self, root): - with open(root+'/'+self._fp, "r") as read_file: + with open(root + "/" + self._fp, "r") as read_file: data = json.load(read_file) return data diff --git a/app/lib/library.py b/app/lib/library.py index 5df7ea5..d31b1bc 100755 --- a/app/lib/library.py +++ b/app/lib/library.py @@ -16,13 +16,14 @@ from .storage import Storage class Catalogue: """Decodes and stores book information""" + """Step One: filter_books""" def __init__(self, root): self.file_list = [] - self.opf_regx = re.compile(r'\.opf') - self.cover_regx = re.compile(r'\.jpg|\.jpeg|\.png|\.bmp|\.gif') - self.html_regx = re.compile(r'\.html') + self.opf_regx = re.compile(r"\.opf") + self.cover_regx = re.compile(r"\.jpg|\.jpeg|\.png|\.bmp|\.gif") + self.html_regx = re.compile(r"\.html") self.root_dir = root _config = Config(root) self.book_folder = _config.book_path @@ -33,12 +34,13 @@ class Catalogue: def scan_folder(self, _path=None): if _path is not None: folder = _path - elif os.path.isdir(self.root_dir+'/'+self.book_folder): - folder = self.root_dir+'/'+self.book_folder - else: folder = self.book_folder + elif os.path.isdir(self.root_dir + "/" + self.book_folder): + folder = self.root_dir + "/" + self.book_folder + else: + folder = self.book_folder for f in os.listdir(folder): - _path = os.path.abspath(folder+'/'+f) - _is_dir = os.path.isdir(_path.strip()+'/') + _path = os.path.abspath(folder + "/" + f) + _is_dir = os.path.isdir(_path.strip() + "/") if _is_dir: self.file_list.append(self.scan_folder(_path)) self.file_list.append(_path) @@ -58,7 +60,7 @@ class Catalogue: except TypeError as e: print(e) self._book_list_expanded = {} - with open(self.book_shelf, 'w') as f: + with open(self.book_shelf, "w") as f: for book in self.books: self._book_list_expanded[book] = self.process_book(book) json.dump(self._book_list_expanded, f) @@ -67,18 +69,18 @@ class Catalogue: @staticmethod def process_book(book): """Return dictionary of epub file contents""" - book = zipfile.ZipFile(book, 'r') + book = zipfile.ZipFile(book, "r") details = {} with book as book_zip: - details['files'] = [] - details['path'] = book.filename + details["files"] = [] + details["path"] = book.filename expanded = book_zip.infolist() - regx = re.compile(r'\.opf|cover') + regx = re.compile(r"\.opf|cover") for i in expanded: match = re.search(regx, i.filename) if match: # Returns zip file location of requested files - details['files'].append(match.string) + details["files"].append(match.string) return details def extract_metadata(self, book): @@ -87,47 +89,41 @@ class Catalogue: book['path'] == Full path to ebook file book['files'] == list of files from self.process_book(book) """ - book_zip = zipfile.ZipFile(book['path'], 'r') + book_zip = zipfile.ZipFile(book["path"], "r") with book_zip as f: content = self.extract_content(book_zip, book) soup = BeautifulSoup(content, "lxml") title = soup.find("dc:title") if title is None: - title = book['path'].split('/')[-1].rsplit('.', 1)[0] - else: title = title.contents[0] + title = book["path"].split("/")[-1].rsplit(".", 1)[0] + else: + title = title.contents[0] author = soup.find("dc:creator") - if author is not None: author = author.contents[0] - try: cover = self.extract_cover_image(book_zip, book) + if author is not None: + author = author.contents[0] + try: + cover = self.extract_cover_image(book_zip, book) except IndexError: # cover = self.extract_cover_html(book_zip, book) cover = DuckDuckGo().image_result(title) - book_details = [title, author, cover, book['path']] + book_details = [title, author, cover, book["path"]] return book_details def extract_content(self, book_zip, book): - content = book_zip.open( - list( - filter(self.opf_regx.search, book['files']) - )[0] - ) + content = book_zip.open(list(filter(self.opf_regx.search, book["files"]))[0]) return content def extract_cover_html(self, book_zip, book): - cover = book_zip.open( - list( - filter(self.html_regx.search, book['files']) - )[0] - ) + cover = book_zip.open(list(filter(self.html_regx.search, book["files"]))[0]) return cover def extract_cover_image(self, book_zip, book): - cover = book_zip.open( - list( - filter(self.cover_regx.search, book['files']) - )[0] - ) - try: cover = book_zip.read(cover.name); return cover - except KeyError: return False + cover = book_zip.open(list(filter(self.cover_regx.search, book["files"]))[0]) + try: + cover = book_zip.read(cover.name) + return cover + except KeyError: + return False def compare_shelf_current(self): db = Storage() @@ -136,8 +132,10 @@ class Catalogue: if self.books is None: self.filter_books() on_disk, in_storage = [], [] - for _x in self.books: on_disk.append(_x) - for _y in stored: in_storage.append(_y[0]) + for _x in self.books: + on_disk.append(_x) + for _y in stored: + in_storage.append(_y[0]) a, b, = set(on_disk), set(in_storage) c = set.difference(a, b) return c @@ -152,6 +150,6 @@ class Catalogue: inserted = db.commit() if inserted is not True: print(inserted) - if input('Continue ? y/n') == 'y': + if input("Continue ? y/n") == "y": pass db.close() diff --git a/app/lib/pyShelf.py b/app/lib/pyShelf.py index 1984d4d..2ee313d 100755 --- a/app/lib/pyShelf.py +++ b/app/lib/pyShelf.py @@ -10,6 +10,7 @@ from .storage import Storage class InitFiles: """First run file creation operations""" + def __init__(self, file_array): print("Begining creation of file structure") for _pointer in file_array: @@ -38,8 +39,10 @@ class BookDisplay: self.thumbnail_size = [200, 300] self.thumbnail_scale = 1 self.total_pages = None - try: self.screen_size = kwargs['screen_size'] - except Exception: self.screen_size = [900, 600] + try: + self.screen_size = kwargs["screen_size"] + except Exception: + self.screen_size = [900, 600] def nextPage(self): """ @@ -66,4 +69,6 @@ class BookDisplay: """ x = (self.thumbnail_size[0] * self.thumbnail_scale) + 10 y = (self.thumbnail_size[1] * self.thumbnail_scale) + 10 - self.books_per_page = int(self.screen_size[0]//x) * int(self.screen_size[1]//y) + self.books_per_page = int(self.screen_size[0] // x) * int( + self.screen_size[1] // y + ) diff --git a/app/lib/storage.py b/app/lib/storage.py index 49a87ab..c9c8cc0 100755 --- a/app/lib/storage.py +++ b/app/lib/storage.py @@ -8,12 +8,13 @@ from .config import Config # db_pointer = Config().catalogue_db -class Storage(): +class Storage: """Contains all methods for system storage""" def __init__(self, db_pointer=None): # Optionaly pass db_file to specify another db or for testing - if db_pointer is None: db_pointer = Config().catalogue_db + if db_pointer is None: + db_pointer = Config().catalogue_db self.db_file = db_pointer self.database() # self.create_tables() @@ -32,9 +33,9 @@ class Storage(): def create_tables(self): """Create table structure""" q_check = "SELECT * FROM books" - q_create = '''CREATE TABLE books(title text, author text, + q_create = """CREATE TABLE books(title text, author text, categories text null, cover blob null, pages int null, progress int null, - file_name text)''' + file_name text)""" try: self.cursor.execute(q_check) except sqlite3.OperationalError as e: @@ -45,15 +46,18 @@ class Storage(): Insert book in database :returns: True if succeeds False if not """ - q_x = '''SELECT title FROM books WHERE EXISTS(SELECT * from books WHERE `title` = ?)''' - q = '''INSERT INTO books (title, author, cover, progress, file_name, pages) values (?, ?, ?, 0, ?, 0)''' + q_x = """SELECT title FROM books WHERE EXISTS(SELECT * from books WHERE `title` = ?)""" + q = """INSERT INTO books (title, author, cover, progress, file_name, pages) values (?, ?, ?, 0, ?, 0)""" try: - try: cover_image = book[2].data - except: cover_image = book[2] + try: + cover_image = book[2].data + except: + cover_image = book[2] x = self.cursor.execute(q_x, (book[0],)) - try: len(x.fetchone()) > 0 + try: + len(x.fetchone()) > 0 except Exception: - if not book[2]: # If cover image is missing unset entry + if not book[2]: # If cover image is missing unset entry cover_image = None self.cursor.execute(q, (book[0], book[1], cover_image, book[3])) return True @@ -62,15 +66,20 @@ class Storage(): return False def book_paths_list(self): - q = '''SELECT file_name FROM books''' + q = """SELECT file_name FROM books""" x = self.cursor.execute(q) - try: x = x.fetchall() - except Exception: x = [] + try: + x = x.fetchall() + except Exception: + x = [] return x def commit(self): - try: self.db.commit(); return True - except Exception as e: return e + try: + self.db.commit() + return True + except Exception as e: + return e def close(self): self.db.close() diff --git a/app/main.py b/app/main.py index d29c71d..84e648b 100755 --- a/app/main.py +++ b/app/main.py @@ -6,7 +6,7 @@ from lib.config import Config from lib.library import Catalogue from lib.pyShelf import InitFiles -ROOT_DIR = os.path.abspath('../') +ROOT_DIR = os.path.abspath("../") sys.path.append(ROOT_DIR) config = Config(ROOT_DIR) # Get configuration settings InitFiles(config.file_array) # Initialize file system diff --git a/app/tests/config_test.py b/app/tests/config_test.py index 8b1bcb2..4d67b2f 100755 --- a/app/tests/config_test.py +++ b/app/tests/config_test.py @@ -10,7 +10,7 @@ class TestConfig: assert os.path.isdir(self.config.book_path) def test_title(self): - assert 'pyShelf' in self.config.TITLE + assert "pyShelf" in self.config.TITLE def test_version(self): assert self.config.VERSION is not None diff --git a/app/tests/library_test.py b/app/tests/library_test.py index 9950f27..e2d98a8 100755 --- a/app/tests/library_test.py +++ b/app/tests/library_test.py @@ -7,22 +7,21 @@ from ..lib.library import Catalogue class Test_Config(Config): def __init__(self): - Config.__init__(self, 'config.json') + Config.__init__(self, "config.json") _data = self.open_file() - def open_file(self, root='config.json'): - with open('config.json') as read_file: + def open_file(self, root="config.json"): + with open("config.json") as read_file: data = json.load(read_file) return data class Test_Catalogue(Catalogue): - def __init__(self): - Catalogue.__init__(self, root=os.path.abspath('.')) + Catalogue.__init__(self, root=os.path.abspath(".")) def filter_books(self): - self.book_shelf = 'app/'+self.book_shelf + self.book_shelf = "app/" + self.book_shelf return super().filter_books() diff --git a/frontend/frontend/settings.py b/frontend/frontend/settings.py index aa9534e..7bf286e 100755 --- a/frontend/frontend/settings.py +++ b/frontend/frontend/settings.py @@ -20,7 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '@(9b9jslgg41u1u=mr)-2*-n2x0vef0zsy39*z@sz18&tvow18' +SECRET_KEY = "@(9b9jslgg41u1u=mr)-2*-n2x0vef0zsy39*z@sz18&tvow18" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -31,54 +31,54 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'interface', - 'interface.templatetags' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "interface", + "interface.templatetags", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'frontend.urls' +ROOT_URLCONF = "frontend.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'frontend.wsgi.application' +WSGI_APPLICATION = "frontend.wsgi.application" # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } @@ -88,26 +88,20 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, ] # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -119,4 +113,4 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = "/static/" diff --git a/frontend/frontend/urls.py b/frontend/frontend/urls.py index 3de8e64..b5ae4bd 100755 --- a/frontend/frontend/urls.py +++ b/frontend/frontend/urls.py @@ -18,6 +18,6 @@ from django.urls import path from interface import views urlpatterns = [ - path('admin/', admin.site.urls), - path('', views.index, name='index'), + path("admin/", admin.site.urls), + path("", views.index, name="index"), ] diff --git a/frontend/frontend/wsgi.py b/frontend/frontend/wsgi.py index ad6c6b9..2ab6822 100755 --- a/frontend/frontend/wsgi.py +++ b/frontend/frontend/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'frontend.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "frontend.settings") application = get_wsgi_application() diff --git a/frontend/interface/apps.py b/frontend/interface/apps.py index 7810300..0c7d2c4 100755 --- a/frontend/interface/apps.py +++ b/frontend/interface/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class InterfaceConfig(AppConfig): - name = 'interface' + name = "interface" diff --git a/frontend/interface/migrations/0001_initial.py b/frontend/interface/migrations/0001_initial.py index ac80ebb..8877dc8 100755 --- a/frontend/interface/migrations/0001_initial.py +++ b/frontend/interface/migrations/0001_initial.py @@ -7,21 +7,28 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='books', + name="books", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=255)), - ('author', models.CharField(blank=True, max_length=255)), - ('categories', models.CharField(blank=True, max_length=255)), - ('cover', models.BinaryField(blank=True, editable=True)), - ('pages', models.IntegerField(blank=True)), - ('progress', models.IntegerField(blank=True)), - ('file_name', models.CharField(max_length=255)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=255)), + ("author", models.CharField(blank=True, max_length=255)), + ("categories", models.CharField(blank=True, max_length=255)), + ("cover", models.BinaryField(blank=True, editable=True)), + ("pages", models.IntegerField(blank=True)), + ("progress", models.IntegerField(blank=True)), + ("file_name", models.CharField(max_length=255)), ], ), ] diff --git a/frontend/interface/models.py b/frontend/interface/models.py index 9e0a949..b2f5951 100755 --- a/frontend/interface/models.py +++ b/frontend/interface/models.py @@ -14,8 +14,9 @@ class Books(models.Model): :param progress: Reader percentage <-- Not implented :param file_name: Path to book """ + class Meta: - db_table = 'books' + db_table = "books" def __str__(self): return self.title @@ -30,4 +31,4 @@ class Books(models.Model): def get_absolute_url(self): """Returns the url to access a particular instance of MyModelName.""" - return reverse('model-detail-view', args=[str(self.id)]) + return reverse("model-detail-view", args=[str(self.id)]) diff --git a/frontend/interface/templatetags/filters.py b/frontend/interface/templatetags/filters.py index 1074a41..11482a8 100644 --- a/frontend/interface/templatetags/filters.py +++ b/frontend/interface/templatetags/filters.py @@ -7,4 +7,5 @@ register = template.Library() @register.filter def bin_2_img(_bin): - if _bin is not None: return b64encode(_bin).decode('utf-8') + if _bin is not None: + return b64encode(_bin).decode("utf-8") diff --git a/frontend/interface/views.py b/frontend/interface/views.py index 4642a13..d657069 100755 --- a/frontend/interface/views.py +++ b/frontend/interface/views.py @@ -4,12 +4,12 @@ from .models import Books def index(request): - return render(request, "index.html", {'Books': Books.objects.all()}) + return render(request, "index.html", {"Books": Books.objects.all()}) def book_set(_set): r = 20 - x = _set*r + x = _set * r y = x + r books = Books.objects.all()[x:y] return books diff --git a/frontend/manage.py b/frontend/manage.py index 4e2c213..a682b48 100755 --- a/frontend/manage.py +++ b/frontend/manage.py @@ -5,7 +5,7 @@ import sys def main(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'frontend.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "frontend.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -17,5 +17,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main()