From 5c2d93a9e91547bf12be0b75e99c666d6077de9b Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 9 Nov 2019 23:27:04 -0500 Subject: [PATCH] Moved main program files --- __init__.py | 4 -- config.py | 12 ---- data/shelf.json | 1 - lib/__init__.py | 0 lib/api_hooks.py | 30 --------- lib/display.py | 103 ----------------------------- lib/library.py | 151 ------------------------------------------- lib/pyShelf.py | 149 ------------------------------------------ lib/storage.py | 73 --------------------- main.py | 21 ------ static/css/main.css | 69 -------------------- static/img/py.png | Bin 22149 -> 0 bytes static/img/shelf.png | Bin 1124 -> 0 bytes static/index.html | 32 --------- 14 files changed, 645 deletions(-) delete mode 100755 __init__.py delete mode 100755 config.py delete mode 100644 data/shelf.json delete mode 100644 lib/__init__.py delete mode 100644 lib/api_hooks.py delete mode 100644 lib/display.py delete mode 100755 lib/library.py delete mode 100755 lib/pyShelf.py delete mode 100644 lib/storage.py delete mode 100755 main.py delete mode 100644 static/css/main.css delete mode 100755 static/img/py.png delete mode 100755 static/img/shelf.png delete mode 100644 static/index.html diff --git a/__init__.py b/__init__.py deleted file mode 100755 index 9a3cbc8..0000000 --- a/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import os -import sys - -sys.path.insert(0, os.path.abspath('.')) diff --git a/config.py b/config.py deleted file mode 100755 index f5c146f..0000000 --- a/config.py +++ /dev/null @@ -1,12 +0,0 @@ -class Config: - """Main System Configuration""" - def __init__(self): - self.book_path = "books/" - self.TITLE = "pyShelf E-Book Server" - self.book_shelf = "data/shelf.json" - self.catalogue_db = "data/catalogue.db" - self.file_array = [ - self.book_shelf, - self.catalogue_db, - ] - self.auto_scan = True diff --git a/data/shelf.json b/data/shelf.json deleted file mode 100644 index 5efa207..0000000 --- a/data/shelf.json +++ /dev/null @@ -1 +0,0 @@ -{"/home/raelon/Projects/pyShelf/books/Python Tricks by Dan Bader.epub": {"files": ["content.opf", "media/cover-6x9.png", "cover.xhtml"], "path": "/home/raelon/Projects/pyShelf/books/Python Tricks by Dan Bader.epub"}, "/home/raelon/Projects/pyShelf/books/Automate the Boring Stuff with Python - Practical Programming for Total Beginners - 1st Edition (2015) (Pdf, Epub & Mobi) Gooner/Automate the Boring Stuff with Python (2015).epub": {"files": ["OEBPS/content.opf", "OEBPS/Images/cover00710.jpeg", "OEBPS/Text/cover_page.xhtml"], "path": "/home/raelon/Projects/pyShelf/books/Automate the Boring Stuff with Python - Practical Programming for Total Beginners - 1st Edition (2015) (Pdf, Epub & Mobi) Gooner/Automate the Boring Stuff with Python (2015).epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Dune Chronicles (Dune 7)/Dune Chronicles 1 - Hunters of Dune.epub": {"files": ["OPS/xhtml/cover.html", "OPS/9780765312921.opf"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Dune Chronicles (Dune 7)/Dune Chronicles 1 - Hunters of Dune.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Dune Chronicles (Dune 7)/Dune Chronicles 2 - The Sandworms of Dune.epub": {"files": ["OPS/package.opf", "OPS/xhtml/cover.html"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Dune Chronicles (Dune 7)/Dune Chronicles 2 - The Sandworms of Dune.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Great Schools of Dune/Great Schools of Dune 1 - Sisterhood of Dune.epub": {"files": ["OEBPS/content.opf", "OEBPS/Text/cover.xml"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Great Schools of Dune/Great Schools of Dune 1 - Sisterhood of Dune.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Great Schools of Dune/Great Schools of Dune 2 - Mentats of Dune.epub": {"files": ["OEBPS/content.opf", "OEBPS/Images/cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Great Schools of Dune/Great Schools of Dune 2 - Mentats of Dune.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Great Schools of Dune/Great Schools of Dune 2.5 - Red Plague.epub": {"files": ["OEBPS/content.opf", "OEBPS/Text/cover.xhtml"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Great Schools of Dune/Great Schools of Dune 2.5 - Red Plague.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Great Schools of Dune/Great Schools of Dune 3 - Navigators of Dune.epub": {"files": ["OEBPS/content.opf", "OEBPS/Text/cover.xhtml"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Great Schools of Dune/Great Schools of Dune 3 - Navigators of Dune.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Heroes of Dune/Heroes of Dune 1 - Paul of Dune.epub": {"files": ["OPS/package.opf", "OPS/xhtml/cover.xml"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Heroes of Dune/Heroes of Dune 1 - Paul of Dune.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Heroes of Dune/Heroes of Dune 2 - The Winds of Dune.epub": {"files": ["OPS/package.opf", "OPS/xhtml/cover.xml"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Heroes of Dune/Heroes of Dune 2 - The Winds of Dune.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/1 - Dune - Frank Herbert (1965).epub": {"files": ["OEBPS/package.opf", "OEBPS/cover.xml"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/1 - Dune - Frank Herbert (1965).epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/2 - Dune Messiah - Frank Herbert (1969).epub": {"files": ["OEBPS/9780575104426_oeb_opf_r1.opf", "OEBPS/9780575104426_oeb_cover_r1.html"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/2 - Dune Messiah - Frank Herbert (1969).epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/3 - Children of Dune - Frank Herbert (1976).epub": {"files": ["OEBPS/9780575104402_oeb_cover_r1.html", "OEBPS/9780575104402_oeb_opf_r1.opf"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/3 - Children of Dune - Frank Herbert (1976).epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/4 - God Emperor Of Dune - Frank Herbert (1981).epub": {"files": ["OEBPS/9780575104440_oeb_opf_r1.opf", "OEBPS/9780575104440_oeb_cover_r1.html"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/4 - God Emperor Of Dune - Frank Herbert (1981).epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/5 - Heretics of Dune - Frank Herbert (1984).epub": {"files": ["OEBPS/9780575104457_oeb_cover_r1.html", "OEBPS/9780575104457_oeb_opf_r1.opf"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/5 - Heretics of Dune - Frank Herbert (1984).epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/6 - Chapter House Dune - Frank Herbert (1985).epub": {"files": ["OEBPS/9780575104396_oeb_opf_r1.opf", "OEBPS/9780575104396_oeb_cover_r1.html"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Original Dune series/6 - Chapter House Dune - Frank Herbert (1985).epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Prelude to Dune/Prelude to Dune 1 - House Atreides.epub": {"files": ["OEBPS/content.opf"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Prelude to Dune/Prelude to Dune 1 - House Atreides.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Prelude to Dune/Prelude to Dune 2 - House Harkonnen.epub": {"files": ["Herb_9780553897838_epub_opf_r1.opf"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Prelude to Dune/Prelude to Dune 2 - House Harkonnen.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Prelude to Dune/Prelude to Dune 3 - House Corrino.epub": {"files": ["OPS/content.opf"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Prelude to Dune/Prelude to Dune 3 - House Corrino.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/CliffsNotes on Herbert's Dune & Other Works - L. David Allen.epub": {"files": ["OPS/images/cover.jpg", "OPS/content.opf"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/CliffsNotes on Herbert's Dune & Other Works - L. David Allen.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/Dreamer of Dune- The Biography of Frank Herbert by Brian Herbert.epub": {"files": ["OEBPS/content.opf", "OEBPS/Images/cover.jpg"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/Dreamer of Dune- The Biography of Frank Herbert by Brian Herbert.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/Eye (Short stories) - Frank Herbert.epub": {"files": ["OEBPS/content.opf", "OEBPS/Text/cover.xhtml"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/Eye (Short stories) - Frank Herbert.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/Tales of Dune (Short stories) - Brian Herbert and Kevin J. Anderson.epub": {"files": ["OEBPS/content.opf"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/Tales of Dune (Short stories) - Brian Herbert and Kevin J. Anderson.epub"}, "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/The Road to Dune (Companion book) - Frank Herbert et al.epub": {"files": ["OEBPS/e9781429924917_cover.jpg", "OEBPS/e9781429924917_content.opf"], "path": "/home/raelon/Projects/pyShelf/books/All Dune books + short stories + extras ePUB/Short story collections and extras/The Road to Dune (Companion book) - Frank Herbert et al.epub"}} \ No newline at end of file diff --git a/lib/__init__.py b/lib/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/api_hooks.py b/lib/api_hooks.py deleted file mode 100644 index 239f88a..0000000 --- a/lib/api_hooks.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/python -import sys - -import requests - -# sys.path.insert(1, 'lib/') - - -class DuckDuckGo: - """duckduckgo related searching""" - def __init__(self): - self.url = "https://api.duckduckgo.com/?q=" - - def image_result(self, query): - """ - 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'] - except ValueError: - 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 diff --git a/lib/display.py b/lib/display.py deleted file mode 100644 index 9e89641..0000000 --- a/lib/display.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/python -import cgi -import sys - -from config import Config - -sys.path.insert(0, '../') - - -class Frontend(): - """Dynamic frontend display functions""" - - def __init__(self, dimensions=[0, 0]): - """ - :param dimensions: array containing screen size [x, y] - """ - self.dimensions = dimensions - self.TITLE = Config().TITLE - - def html_Headers(self): - """ - HTML headers - :returns _head: HTML render of page headers - """ - _head = """ - - - - - - - - %s - - """ % self.TITLE - return _head - - def app_Headers(self): - """ - App specific headers - :returns _head: HTML render of application specific headers - """ - _head = """ - -
-
-

pyShelf

-

class=\"app_subhdr shadow\">Open Source E-book Server

-
- """ - return _head - - def app_body(self, nav, shelf): - """ - Main interface body, and navigation - :param nav: nav[] system navigation list - :param shelf: shelf[0{path:"",title:"",cover:"",author:""}] - :returns _body: HTML render of page body - """ - _body = """ -
-
- %s -
-
-
- %s -
-
-
- """ %(nav, shelf) - return _body - - def app_footer(self): - """ - Main interface footer; Closes HTML - :returns _footer: HTML render of page footer - """ - _footer = """ -
-
- -
-
-
- - - """ - return _footer - - def compile(self, nav, shelf): - """ - Compiles user interface - :returns _ui: Compiled HTML for page layout - """ - _head = self.html_Headers() + self.app_Headers() - _body = self.app_body(nav, shelf) - _foot = self.app_footer() - try: - _ui = _head + _body + _foot - return _ui - except Exception as e: - return e diff --git a/lib/library.py b/lib/library.py deleted file mode 100755 index dde8525..0000000 --- a/lib/library.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/python -import json -import os -import re -import zipfile - -from bs4 import BeautifulSoup -from PIL import Image - -from config import Config -from lib.api_hooks import DuckDuckGo -from lib.storage import Storage - -config = Config() - - -class Catalogue: - """Decodes and stores book information""" - """Step One: filter_books""" - - def __init__(self): - 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') - - def scan_folder(self, folder=config.book_path): - for f in os.listdir(folder): - _path = os.path.abspath(folder+'/'+f) - #_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) - - def scan_book(self, book): - """REMOVE ME?""" - _epub = zipfile.ZipFile(book) - with _epub as _epub_open: - try: _epub_open.open('content.opf'); return True - except Exception as e: print(e); return False - - def filter_books(self): - """ - Scan book folder recursively for epub files - filter_books(0) -> Catalogue.books - filter_books(1) -> self.books[] - :param ret: 0 -> create class property -> dump json - :param ret: 1 -> create & return class property - """ - self.scan_folder() - regx = re.compile(r"\.epub") - self.books = list(filter(regx.search, filter(None, self.file_list))) - _book_list_expanded = {} - with open(config.book_shelf, 'w') as f: - for book in self.books: - _book_list_expanded[book] = self.process_book(book) - json.dump(_book_list_expanded, f) - return _book_list_expanded - - def process_book(self, book): - """Return dictionary of epub file contents""" - f_name = 'content.opf' - book = zipfile.ZipFile(book, 'r') - details = {} - with book as book_zip: - details['files'] = [] - details['path'] = book.filename - expanded = book_zip.infolist() - 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) - return details - - def extract_metadata(self, book): - """ - Return extracted metadata and cover picture - book['path'] == Full path to ebook file - book['files'] == list of files from self.process_book(book) - """ - 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 == None: - title = book['path'].split('/')[-1].rsplit('.', 1)[0] - else: title = title.contents[0] - author = soup.find("dc:creator") - if author != 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']] - return book_details - - def extract_content(self, book_zip, book): - 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] - ) - 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 - - def compare_shelf_current(self): - db = Storage() - stored = db.book_paths_list() - closed = db.close() - try: self.books - except Exception: 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]) - a, b, = set(on_disk), set(in_storage) - c = set.difference(a, b) - return c - - def import_books(self, list=None): - book_list = self.compare_shelf_current() - db = Storage() - for book in book_list: - book = self.process_book(book) - extracted = self.extract_metadata(book) - db.insert_book(extracted) - inserted = db.commit() - if inserted is not True: - print(inserted) - if input('Continue ? y/n') == 'y': - pass - db.close() diff --git a/lib/pyShelf.py b/lib/pyShelf.py deleted file mode 100755 index 25f42d6..0000000 --- a/lib/pyShelf.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/python -import mimetypes -import os -import zipfile -from http.server import BaseHTTPRequestHandler, HTTPServer - -from config import Config -from lib.library import Catalogue -from lib.storage import Storage - -config = Config() -Storage = Storage() - - -class InitFiles: - """First run file creation operations""" - def __init__(self, file_array): - print("Begining creation of file structure") - for _pointer in file_array: - if not os.path.isfile(_pointer): - self.CreateFile(_pointer) - print("Concluded file creation") - - def CreateFile(self, _pointer): - """Create the file""" - if not os.path.isdir(os.path.split(_pointer)[0]): - os.mkdir(os.path.split(_pointer)[0]) - f = open(_pointer, "w+") - f.close() - - -class RequestHandler(BaseHTTPRequestHandler): - """Request Handler""" - def do_GET(self): - # TODO determine how to include stylesheets - self.send_response(200) - if self.path == '/': - self.path = '../static/index.html' - mimetype = 'text/html' - serve_file = open(self.path[1:]).read() - self.send_header('Content-type', mimetype) - self.end_headers() - self.wfile.write(bytes(serve_file, 'utf-8')) - elif self.path.split('.', 1)[1] == 'css': - self.path = '../static' + self.path - mimetype = 'text/css' - serve_file = open(self.path[1:]).read() - self.send_header('Content-type', mimetype) - self.end_headers() - self.wfile.write(bytes(serve_file, 'utf-8')) - elif self.path.endswith('.png'): - self.path = '../static' + self.path - mimetype = 'image/png' - serve_file = open(self.path[1:], 'rb') - # Important to rb read binary for images - self.send_header('Content-type', mimetype) - self.end_headers() - self.wfile.write(serve_file.read()) - else: - self.send_response(404) - serve_file = "File Not Found" - mimetype = 'text/html' - self.send_header('Content-type', mimetype) - self.end_headers() - try: serve_file.close() - except Exception: pass - - -class BookDisplay: - """All functions related to displaying book information in the HTML UI""" - - def __init__(self): - """ - Initialize class variables - :return: None - """ - self.books_per_page = None - self.current_page = 0 - self.thumbnail_size = [200, 300] - self.thumbnail_scale = 1 - self.total_pages = None - - def nextPage(self): - """ - Goto next book page - :return: new current_page - """ - self.current_page += 1 - return self.current_page - - def previousPage(self): - """ - Goto previous book page - :return: new current_page - """ - self.current_page -= 1 - return self.current_page - - def booksPerPage(self, screen_size): - """ - Set books per page - - :param screen_size: Array containing x,y pixel sizes - :return: self.books_per_page - """ - x = (self.thumbnail_size[0] * self.thumbnail_scale) + 10 - y = (self.thumbnail_size[1] * self.thumbnail_scale) + 10 - self.books_per_page = int(screen_size[0]//x) * int(screen_size[1]//y) - return self.books_per_page - - -class BookServer: - """ - HTTP server functions required to display e-books - """ - - def __init__(self): - self.server_address = ('', 8000) - self.handler = RequestHandler - self.httpd = HTTPServer(self.server_address, self.handler) - - def close_prompt(self): - """Prompt to close server""" - close = input("Close Server? y/n") - if close == 'y': - self.close() - return True - else: - self.close_prompt() - - def run(self): - """Start HTTP Server""" - try: - print("Server running @ http://127.0.0.1:8000") - self.httpd.serve_forever() - self.httpd.handle_request() - except KeyboardInterrupt: - print("Interrupt received, Closing Server") - self.close() - print("Server shutdown, Goodbye!") - return True - - def close(self): - """Stop HTTP Server""" - try: - self.httpd.server_close() - return True - except Exception: - return False diff --git a/lib/storage.py b/lib/storage.py deleted file mode 100644 index 196cc95..0000000 --- a/lib/storage.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/python -import sqlite3 -import sys - -# sys.path.insert(1, '../') -from config import Config - -db_pointer = Config().catalogue_db - - -class Storage: - """Contains all methods for system storage""" - - def __init__(self): - self.db_file = db_pointer - self.database() - self.create_tables() - - def database(self): - """Create database cursor""" - try: - self.db = sqlite3.connect(self.db_file) - self.cursor = self.db.cursor() - return True - except Exception as e: - return False - - def create_tables(self): - """Create table structure""" - q_check = "SELECT * FROM books" - q_create = '''CREATE TABLE books(title text, author text, - categories text, cover blob, pages int, progress int, - file_name text)''' - try: - self.cursor.execute(q_check) - except sqlite3.OperationalError as e: - self.cursor.execute(q_create) - - def insert_book(self, book): - """ - 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, file_name) values (?, ?, ?, ?)''' - try: - try: cover_image = book[2].data - except: cover_image = book[2] - x = self.cursor.execute(q_x, (book[0],)) - try: len(x.fetchone()) > 0 - except Exception: - 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 - except Exception as e: - print(e) - return False - - def book_paths_list(self): - q = '''SELECT file_name FROM books''' - x = self.cursor.execute(q) - try: x = x.fetchall() - except Exception: x = [] - return x - - def commit(self): - try: self.db.commit(); return True - except Exception as e: return e - - def close(self): - self.db.close() - return True diff --git a/main.py b/main.py deleted file mode 100755 index 7ee3fd1..0000000 --- a/main.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/python -import sys - -from config import Config -from lib.display import Frontend -from lib.library import Catalogue -from lib.pyShelf import BookDisplay, BookServer, InitFiles - -# sys.path.insert(1, 'lib/') - -config = Config() # Get configuration settings -InitFiles(config.file_array) # Initialize file system -Catalogue = Catalogue() # Open the Catalogue -UI = Frontend() -Server = BookServer() -# new_books = Catalogue.new_files() -Catalogue.import_books() # Filter Your books -Server.run() -# TODO Figure out a system to get books page count -# TODO Update Documentation -# TODO Requirements.txt diff --git a/static/css/main.css b/static/css/main.css deleted file mode 100644 index e3371a8..0000000 --- a/static/css/main.css +++ /dev/null @@ -1,69 +0,0 @@ -body{ - margin: 0px 10px 0px 10px; - padding: 0px; - background-color: #DCDCDD; - color: #46494C -} -#app{ - display: grid; - grid-template-areas: "app_header" - "app_body" - "app_footer"; - grid-template-rows: 5vh 90vh 5vh; - max-height: 100% -} -.app_header{ - grid-area: app_header; - display: grid; - grid-template-areas: "title slogan"; - align-items: center; -} -.app_hdr{ - grid-area: title; - margin: 0px; - font-family: 'Audiowide', cursive; - font-size: 25px; - text-align: start; -} -.shadow{ - text-shadow: #4c5c68 -5px 3px 5px; -} -.app_subhdr{ - grid-area: slogan; - margin: 0px; - font-family: 'Audiowide', cursive; - font-size: 18px; - text-shadow: #4c5c68 -5px 3px 5px; - text-align: end; -} -.app_body{ - display: grid; - grid-area: app_body; - grid-template-columns: 20% 80%; - grid-template-areas: "left_col shelf"; -} -.app_footer{ - grid-area: app_footer; -} -.left_col{ - grid-area: left_col -} -.shelf{ - grid-area: shelf; - margin: 0px auto 0px auto; -} -.shelf_contents{ - display: flex; - flex-direction: column; - flex: 1; -} -.right_col{ - grid-area: right_col -} -.python_logo{ - -} -#python_logo{ - height: 50px; - width: 91px; -} diff --git a/static/img/py.png b/static/img/py.png deleted file mode 100755 index 828eac7e3cade47c844747dd64a1811a2d324f26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22149 zcmXtf18`(r*LBcwCZ1qo+qP{_Y}>YN+t$Q(GO=x26Z`My{pzo-?&`W#_ndRj##wvq zbt4qy#DBp4f&~Eq`5`GGq67j0+7J9+3k?bUm9uSG0A2{O5EfRD6c#41ceFFLur>h! zp-Ava;FTB@z!}z4ZZ4!nKu2~6SID6WkL0lY8L2?H2wTIIuKh;HVj-cVjRUa~7=$0C z3WZ^&gR2c8cndx>yd*g%D)phKWR85%@4&yk<+a%j9&DLlp|*jUf7X%_Jv!uUDhl6K zh>d$H3@yym7_A{Gm4Eco5f)eHrkK0*wwXsa zl7||X=`6S}UQ3PF>oDC(b30j|AvKL07BTFNN-aAHTPnsQiKEDx^Dm@nl>H!eiX4zHvt22>7im@fx1zfyfT2O%SNCK8f5vE1F%M zjqR;|GmG!rLnqDUyqAvWO*cg#G*Kkv zA_Wy&&u{1vOja^7-?x<^%ndUUg$5D9zU>l>GwB5TW>R%(Q>AQDLc2^dO%gz)Y_2wZY%s#q4gUFqlhv$CQhGJ$YqSicA zCY79ReUX2np;vVqpI|6?TeFCYi>5sO!bX*ixMNIKYKB#nt`=gn-Fanb@)iLOSdRf( z0o#Z%6MrGgBzTf4NbSvXk5ZDyB~RY?=WOxSdb}%MI3vyR?BmG0BUwve&EqwBFnYQ| z&~_{i&;GOIW4iORR_dE0u$cz#E^IZOLMl9}#1f_Pp5Nh1731b}lx(|{q50UDj)ER;tq))Rd*pK$r*M;qz>oiP1GPid<={E)O!2wG>a5%yVUD=ZFf z>&t~+>-^so;RFDpKp3G)t(Gea8~#Me&N$<_J!T9If8~tqq^j4+O)v+Q5vMYQa;rl9noVEK0}6%478~uTP!~J714!3+ct)ht2|iMwwZer6tTPA})Aj1M@k0E)^t4|i2a%`;grQ(PvB}TH)}y$ll!0XBmkR8(A;Eg z@8;-J+HE;%S6m2>$J*$X1RAPLYtzoS9wDMEeuKW=$$U1y=86v96tPoLNdM<4pb`Ea zfPqk$Tm$k{^&YvRqm4KZ37_MB=(*Hb7q8g4;U^FiT`{2cxX&w zBy_upJa)Wdt?x%-+AUX>ny&Q)On-X|MHQv}^lo?Tt;ZSqQzWl8sYGar|9leE2q2UT z)AMcbeLshw&5R=*I0w(H^go&y55hC33kj zKx!4s`eM1J$#RKmy}=0TCmNbU4wnm+hnnMyy%2$M#_oQN`01c%BB&+q$Uvdt=nlVb zHOa{6yg!2M;MwGOvj_;7*VCn}DX5=TQ|4KCV8I zxH*9MV`4HS+2Nly1THyev`8Nwz;r58)M~lffdPC7k8X$0tHEDDf~h|P&UJQ8 z+5m(TGh#851A0$iYW`~S9eR)FAeu|R_RX)DxO{xN(GlDguDlDYeeX|_X2 zXy)V_&I$O|AY%v|JAUDVk|d?IT^3ZV+1xvS>D(csPqcmFj`aV6ALvep>AR6Rqo1=FzQJZt|29 zFG*JX5?V-APWhkV0!05?6!kL>J8kolM5Ow57E@yFk#<%t{49hOnjd4Oy0oX=xJx|J z;PP&+oZ9^$NN1?O zMkqD$C8ysfUo%?r{|{ga;FRgR4U-e4PqywWrHeHtI?6UWF`lip@`D{LjSn&utdQUU z;$#wF-ulnC2>${UvG$2DT3H3h+rDaKSnh2dmA{u?M(tA|ajD&K{=s0p#{XYx5C;fJ zq3}4L6FvD;3!kE*NVwS;$ig~5t`axn4U2@K(TLe6zx!IfNlIUt82#T9OhX4RZt!vU zLVFqf{#9YB1STr}N;Vk)x?Gux)Z*p)<%nmZqr_}4@qd1f948>bC}a!ISQ*Z5j`=G?jFp3S9A}C_(W);2w68vCN6)>X30TIU2I!>(;=^h_E1)B+q?A}e`uhZ>wBv_NQ5TU@qP{~! z(DF5}q%b#Y3r_+oPS{^o7+QV|Da6CWaRwN_CpiATAJ}ZO1;SycLlLOXZx0q!bW3=r zxHt&wisc4_p^F8=P#tgQRp4qrK%?ES)|>x3qM#6P^4FWKprT>+!TeznHG|VEtJK!z zEZr+=wA<$|?lw2Ir}*yw{aZX-_gpL#g|l2NPvGCMe$&uq@n^<_*PTq~=5uE}5RHyf zxIdn%P%e}EIj!ACV2{pbvtB%nY&LkBJu)<;uuv+y^ooGT^V=jgMf7Akr)aCwZJ|F1 zYH0_XF6;HRGQRL#OBg)+-Sui+@%!tuLN<#nQyQ9*OyYP!kIR&HqvmA*U`D@T0|G`*W>!dZKYK=y$_vHy(3nFHbl2 zd_|mt4hJKY0fvFA;`;E_cY%CfPt{g<9rVX-Z5);xEwo$i2QiD)8v4QweZhV&dr+DU zh9Tv!VcXMO_2g$~XD`K$_-8rXZlzha9Tfv~KTl@z)ali**=>sl!w`jwn6k;c`i2~6 zS`p)29W?giz)BG|fqt;RBx}wn2uEQp$1l56Ctt?ct(EC}ezEj!Ew3KzfnTIU6_`sS$oV49m+_thD#l?xOVHcreK(Au3V8wU^klUu;fblVNZTexc-1Frve9CrpD%T@-F-IAeN!9^c+a`*6F8pEu@XCHUhe^)wYT2x!b;|?ToaFhb(PK=8-|h41FK{3*EVOQWN}tUYL2dawZynhqYLSJ5zs=07 zP)el6;1q~8S#KbF9!D)!SX9D`07c$O&JkCe*gSVob71PQ(QeSbcyk{Kol zVozhGvFY{(PV5RJ@)obRUKKK%O_$4fO{CHWeAHC(xDV3t4V&!LnSj;b#8co)LodTr}_q#&NAq=z-T6WS84h)*#`RnkbaqQ6`fBxXC zEz1MaH7e-MS3j*yX%N(^2PV?!7qIQR3f8A(CY5=u#>U3ZGeUx+ z9y98jIs58$eP2zE0-*2+$|XMuqo1ePcgoY?QWFza+sefJ?9J*T+Lvpz)IVcXYcy4x z2M77!vv_>XUZygBcLuSNCFyxxHDmdG-RG%BFjQv{Ann0LA1st~Cey2375Um=D`bhW z0&S;TBi{D@S3|S&1aO3r2{x1WnK}~+0(Q${loDZy$oRrY*=)H~SgX^Ca_L!7QISbS z#IOVmtl5v(`xKo{=c_4%rv5nZ)7b(-|5RS(;QPO)b47C+oSmfBoZb;gxH66VbFOa>XUw_Y|0Ey(9m4g0J)c+A zp^!-Dv{R*<_4FF)%o9DRCsytj5b*;B#YyDh`9Xw7eb&o zPzxtc)Xh2Q8p%1B8nzRPE(jYci_=S3J*YoZdjB=)e$)MY3Jmifeq^VBset&1lInlj z9apzMai%u!A6-{>PJB3d|7G|S_~Y~L*u${v)Zio4&fKO4nU$?+G3Z9RL*mya%jJ3h zix^7F^g{1c!G3~;UgQPfD3m7G#6NKhP z_P`6Q=!=LX#Z|qkxL3-ZPOHVr^ZRO-Keu;3+21c^hupYLzNqbt1v-}K?FSlN_zWc` zF`s?l>seXetzuIX;iNMTFd$m~jq-mf`i#&7X!K9kd%rsJE|mrDtURvh)#)-!Az^Yo zf#GZC^Sxg(ckg6$;MY41h+#h^=3{}FU`O(ibEJQWpj^4yL`pBGDHF85H{a@2}_H%CJHNVwEc!V7gUN zO;-DB(z<-=1uk9IO>jkEJ53W%5RJ>NmeRBBeJ=~K-BxJkp!Vuk#PfdHSbCt>uU+u9 zI1o4mJ8sjuRcd%bZD4on_4(ZUT~lAFF0bNH|6H}7^>KA^dFX@^Kp@(8c1-sm2Fz7> zy!VsLzKjAZe+XGOec!KMs*bVI@G8U{>;++s+T^4SObcbQITCc;_aInZ<>Ej)f6qe|I9Er&H4O)Vdtp*ULd% z60UA(?N5Oi*cIPyj6bo=a%)BkIgTTQA3E+6W?9?8TDI-OYP+^Quci#BQ`v*Jp`s`O z`otUz&DIrp45Cc^`WvjAdEtWkyL|z_t4t*kS0Qt@E0KdF8E6+Vmddt24DeZi@IMs( zbO6MQaUZN1+JHM;t2t?R7UE1PE`a+g$rZs$TF=u%n9<)ju%(ZNI|2H)cpsK&K?X*r5zZt;vNVe0qxFbLLJQCL`F6MddAW-U>;tX?@5e$6?bej)m`NzJUJ%q0xXf)Owd>y3xEXt|pK zGh)jUPbfz34V9sP*&_bd30d?jmv9&``e56yO1PM%GCOajS5q{TT2YY-Bo*M11>g(u z2-%5jzbGJxDIbsp_SB0boyd4vyYhkFxR-IJiTEprd;;8)2PBM+mn>Y0tX8P|E7oM? zUJOIR0<{UwVl7Uj4+21G@1hnr+XNTbZf0!!($OX5 zA;;7&6|gmsuhd`(T0LuZE(2SQ9q{AXNfHILe0U4?1r`*B1=V`BzJO}%Jbuu++Nl1r z+&HlR52_;`a)dvOqyjw!7ZGFBqG%B4uT^;Qd?gVA7y~@9STeC_uK4@=61y>gk6?{b zUP7Tk$$;Jcu9%n?d>IC+>AkbDn_#s6LV#uMG#bVLH_NOsOQ_mooWRV)$`|-PW|G4j zij54vuvc7OQBg+P+2kL+ zBZZV;|HjiA+a(j`G%CL`yJR5*EE}N+YoovoM6)?J3#=y79Sg3E6mYqde$mNLGL8)h zaMXwWph8j)HdsZ;n&NnBq|OxFyAT+3d-N!5mYj>8DpT9M_@DdD|l4^V9O+K zR?7eJG{O8tRweloUv~qWlrPVMZ)qNXRlx&8pqeD&dO;LT zK~nxsTPl@IVb3DKUZZ&LQ3P>`rxlkY7l2Mr_~+>46qD%u&dW{gLQdyWAd-q}C=?pO7an+IP| zpKYL7@(^O`1F?}9a?bJ_5cDej!z{OJSKA*=IbVpSC?pi9i2CXSAOJZYVEuehT9^%G z$v3waLda~;N$&a4W{JJ@X%n(OwQd`2?5B>zP#J?^E{O#5=Mg%ZYca9=f~jBm#Obs` zepw2V!Y?F1q)$+M+n5bW6STz@)R2sx;vXxJ^#K7j;}w!+0L!u7I-Q54WU%fMMt-<$ zZMSfKa_mV4Il7^*hepdLOVMfG~YvC0{^>NSI1;ZI|?rkfaMrD8#uq!&Kj*mBItEIlGDs*u4n z`DH(fo@Ivm>3js+uF0f&{J*)i>mMm}#+T|eRI!qhjl__^%B|8%>d30iS>&=g1f5Q% zn~#R0v{+~V@M$Lsoix@^2T z-INOkL1umqjI;v>@Q=op%Gm~@##JY}MM{#<{oyE^{%D)ahxcOdo!8Kc~GxW%jm5##jeI@97UWiJ{ z%AiQn>KDM3tY~9_T>nxXYw+NpL;)mZar23{hW+k<%jqm(UfTOX9Dm1j)M*zLujfNP zC^+QHrXK^(?-76j11V~l8JzchZ0K(sgN%AT%2!y}X zpy?N|m`!IFKpU95aQBM#W5kNb%eBTz zW`Igur|XsDa5z>?e_91xUi<5Dmc@FLMLcBp2q5*Q_qzvd14Cu>YeZ?C?M&*_e19k+ zIU@t+M-UP!Ui z4J^i3sGIf#sEBH-psg$u;WpOat|AWZik3l{+u_GK&}7ND&IUm!#WJ%Y3t(;M!UKb0 zOJXLK+#JojWLJkCrtFICUvC!{tMvxHSKyfIxNe(*G*^^jG8j8SoCkv{Y@vyI1-DJv^; z8i&nwf8~dOq$;!qAq(Z4uUoB_Ck!BBu$%cv*Em)2eFHJSavO;&JrL5wiN;g0EXuXV z+}r%F;!=&cuZQ813Wo*P3!9v0qO)sU;(-wk9$$;$6JR`p&S zM|}lg!H5qLD{ITd#l;tBTQ`M>#iAK5`L%x$Sg?{XmtS!52;G1k3M^ybSIJTUiHG9% zo8G*KE;r#H3rtzNxd$EFmIW-I<)GER9Ja+)z ze}9P=%N2>cqFHm~Y2;)KDn$DyCg>nffAz}e@jYBN&ImF6aq6kP{;3V0KDOw+7lL8w z;4Y?91hGW$EvCcNWm2{vx|dF6S9sgekAm`o;F>fu3lVW~&-8DnRs)H9lkHr(k3F z_3?Upsp4$yB{rc#a7T#y%Q}M2t6@&q*BeoiPocO(lENhLVxfeWLE$fya_>@=S^=RB zK;~9SALaV3{Yj})Kj-4}a%6k-Zhur76d%Zx)9djCW$eMbYjQ2+XSSd*05}SFNg?Wb zBExF}of#YtB~)44Os9S=kp=S<+EW<(n$Cl;g(kf91X~U_{#*a)e^=lLn%=AS2-Y2s z%$=TZ<=Ym#8qFI|a|TNi*WFuoQX`PHQ)J<4 zm+@+`33^mDWB1yq<19-CkqjG>6)Z{o*+!c2Ly1N_zQr++adbOVsq=g4b zm&TpbshgFQn_KHgb(j=_5}t1`MKqjQ@HiC*tSNL#A$2L75+S!Rjj<`Tyiy9`K}j59 z;6u&i!kGji&xhI@9j9-D0rnGBUO3y z4vfs#J!%RUa4Fc)I?vQ*gzZ=<%(aL``zGLIWhcJZ!mXfQfL5RG4revUWuw5KgjXHu z?Ad7~XT%^&Oaq>`2`Z8PROmS4(Orh6$gUvCJGu)YmTLFmh|cx*eHyG`CiY6({kz#| z*{Ym?he|FH&9*?EUj<9qGzQp3G^?Y%u~!9b+sO#`HH&WvhVTz6bphA+-gJ z@!1^xExeU3XR4LBdg*m|SNOvZA&h|Xt zDwxwCjhXlSOinQ+1>(N5!=~d_25qp)AW!f^;N1jN-HUkTrUog-%wQ>#x*Xim{|;@0 z%XO3kY7SR7L_`H|kXUmZpEkNE|8R37`*UK+Q4$5_EJ~5WbDxmDCqk!-5}YrF$f@KF z{C_QgcB73+Cz6o=4*$3?`%}rq8u4NjT`f7+vvj?5K*_@NJukQ~w2k^-xyQ*2=1f)A z-GT~rLOx>*T5>!!=%H-RJd8lNN@HZ|zsLs+&_{>|Q1i4w5HRJ0pSZr9?z}?04O9H6 zhvwo>4+uB{iRdW5Y?1XSB#DqNf%o>1koN2Ekigwb*QoL`e*V`y<-7Xj!5^9#0QCiL zmY$0KR=rsoVo3xgDHsU?d%?7XBs;^KN-t4HUq*_YpWb+hO9kWr{6B9xwrkFwTjBH3 zrs0Wq{j)V+G!UZIog6Dyp5qC|070HvZI%JcAIYh-!Y8#q7YQcDAkYHR;h1=c@WY~_OB+-xNcpBqXCTx09^^m_{7;C_Tuw6bWL z@CrIV<*~+^4oZ9|(-A2D3{7UTO5e*61~?HYl|rCGI@0Y02tHckOvE9Q* z3cZqt7lCvD4uNmfWI8U40^HfLe}AGBg(z%Po{2^64#=ifs-B1jYpvhz{hgu(8sh`n zT}A!J7mQYRL82yqh-Y)HxX%V#u`TUI`6S6DOf#sw?|A=hUIW#fv*OZjNC^kI`w6-M z^I*v<5T%JxQr1MJ1fIh)v6}?rMkDFcr2|=?H8h3}t4{W!toP+ouPFUNv69MP)R-l0o%BHvC{9l1ve2PH$^zURrVSP+Y^zMosbNW1^bz9 zS}qti(520tu!>&(3U?BTJbvT*%$-3>#;`%)^#ch=`_Qocu8qdcKYQ7%nQv6(^)waI zvs7UoJCPGQ7j*a5U7wt^WA*#Zs#-ikt-k-0#Q!wqFW?4LM17SNb0-#;+Z@=)YjQe~ zQ`vC*S^cx+r1AiWHk2hoMO7Z;#_m3Vy3aUam z^M$xE0BYY^2?>G}9TUY9FWJg!3Jfp6=1BfX47U0{)!{4(=nhgikZj-R5Kd6aTCPa_ z8ZYi&jFtxSihL(|U2TCBUQo>OgOfZe;!!ML4=Q=eD>pEKGt}Si@BnaD+lG!L{XQWRKO?+*(MZtmRsk!IR{?phGSUWE6J6K}lJyGQ3{biEEe2L!If*qbPM%%f*f?&o z3_S^4@t83+f0FPHH((c0oTrdJn($EJdb6E*Dua1++%qLO7;p~k#Kqw8W}YuE{gp#= zl=urH&IQKUTp5CnYOU6)`BwG(<0rEKM^v_V8m=h%fetz{eIqnSAFL?U_SzXRJW7Sb z5axir;FP8&mNZ!!>luY^Pax^QPc8i$WWc35*>b6};%vtA`C4|X!=)%aJ>HEt+&?c` zyYMH#&{93BjMuq;oH=E=#nbTT(c^n9N|iu@`Ri#E&1b`uJX4?42Jt(TsR3z^p^EKP zCM&V`NF2UA5TqBnTr5-G#=@(EIe{;XzG*|)TdmaANO@2ME%x|$Rb0cbOr>2=2@81a zMae$ctP}R!MRwsrU>MMYP87w=jtvP@S7gauj;qvOCV}%S9UXN-&BezCVi_e+A?u0SMxd&=g8aN{ewM5dtK{NK0LEyH~VdVA+W(A*Io0*IPd# zhwwrG?qr)vy{~W@FFg2K?J^AB{>jt{K#_t7v`Di}`8FVkQ@WSwEE7TdQ7|6z3XMmA z(>>cfU~`*6lt33!KkRIJ+yt17!2-%hSgVu_Q+6L;Ds^}RPz~TBolNZv=x0kn6e|UT z_9-CDQ6zD}7aK0sfRd$f6#&}}ugtgI@pE!cmb~Q%Cc{8C`7{(U9LaiCG~1ZSa6*2hjE9!lj$cpBOhw8{ShKpBZ8n#39YgRn2G@NvM-SlL!bgmVuG2# zgd`a{R9-cXU12O{)2XJhl>dSx5NK5j#k5l5XsX;`BUHs({imG?1PR#RH>}eb5=pV- zpGenVA!vkKmX~}8Mc@?jsRiHzxCzVj(LqC$AhT?!H~vpCrUMW;S6G~<9R<9%R*2hU z{6Mc9KUbiq;t24oja$0X34P|1vEmyABlqQ&J=C?b30^`pVl~d?Q8`KggJ{0Ut~5$bTZly4@>a1LO0xx~b^(e(2f3q$A%IPx z=7NL`xM+WoJ~cpORM!D;Ts{WfT!w?UCTK^CSv6~8v;JUzl!d7CFS2w~k`to;wj30! zkfYyReiUetpagG?`c4%AVLZd3NWM4w>Obk zkeLYJlJ;xsfs@T>S29pkf;<)7-K&TvrDi0gg~;vA7RI1InQxcotXwCdWX!cBQ#Uzb z61?7k1!?lP>pmN0rX;7&bG(P8AmocZFIFiUaj}XY=(XsCxu`?IkcRnaNTM@6K|#SG zCmVWQx*D5=Q|Wbe5A)HP9xLO8lArx5DtpB-RR$%G3hk!On9*N&W36zi8cA`*`&{Sg zALQ71kIPn@aV6Y%O-w?eXtxaP7~Rz%qN2K!%Z+i3Tp%mY%Bpm$;$m@-z4pwTedC3_uZ1hcEv3;yTO+>3VWd+JAi1l-;#SXI8xK>B+mJAAC>61 z(%h+v014ap=E_M-Pl!F;h_L;&KKQR=ZnRG8X>R^Ur^R!BQnVfV(IG~;$43>pTEVX| zNfmC=h>lhZsg)n6LQ~j_|9+}T&)BTG#2?pf31Yk9t7Pa&TG-0WacjQLB)QG!*H(H= zuo%#%7GISfWj)RI43?dWEJK*`giLa*YL7&hi7Bb3+&x&YM2jI_O1W4r!gxS6pRD9b(t8am82pQ z;-Ro^Bfe`3es;!pWquz+G>-C{Ux#RQ{^5^&ivEuIc6;At8((a|i$yu{CE@xsumE@a zhoVyBOV7nW&Bt=03A&S}6-us_%Yf-a|GNm!cEIOhQ@B69)`!ve3x~A1PV%Y@$|ZNU z8gA`jjK0bOs}~7@;ox8=YH1>0sa4Fy^Rjj2-$Y@QY+cB_ZI50?pgbi9nj#!~2GKIsK2u;IzGmy43kf+IPwYCNAFoLDFx2qLS)r_x)ExzZ@1bT?1T(92h-nZt&6uqw-hlNj{W@GT~=PAcW!@bO; z?Lqje`6?eL&&#KTMMhNxq|JxF`=MnsI)yXoSo%BiCORyvWwkX~6!`ql{$~kMzg=GT zo~g~3XRJ;yhpm~7tixj?uUV_zv@*vpLPz@P1~gut%lH@Es9O{a6cbW*a>mi#-6TsQ zh@ljMxF}i9pX*BRx;#5(YV1Z+$D_@z2?uG0;~DfmZ1CScp{Om4-ZDU>gVmNTFc$yh zNeL3PsL5JBD$O$WDSO}hJYO6=eEy>adh9KM(a`2Pxnnk;TSD&F%*pt9&HGvV7`p?q z)pupH!0_`Vx1PfKbdag8l5SF{2l)KdIxf~YuMywZEtTCkNpDDTb@=e8*{b+Hbd`<*;JX{vUOSE{yvRwA5EDe-Y>rxgYQ~MEb{5u zgSx2#a2gHI>jpV;-YDrvT)5@y!-+Z2qYB9)3mPlFj}ly{*af4yDW<7Q-VrAO;YQ<6 z+s8r8IPEXKekVD)8wFvqaHxadDfh?40S_k{Ru-?*sGz37UjyWMh@Jq$#GjV$T=>;|2Ur!>G`3j??t*`|3!1lgVCnb`~JveqCVo_8FFH5ymGNt zz$rN8m3t+39gO{6Y4dIlT~$zxVlQp9lp78U8)?ocy}R;GAy~IB9fnP*{%C2&(S2Yx zz2Gx5Q+$wdBAQ;|wOz0Fr_z$!YmX^smlq1v{w5tO#b_NvI=Qi0)ctsyG1SPC)qfro zqakCbw$G#w_FlO&SY>^NW8|O4OqYFxTuyg*eUPJ# z2KVmE2DctHF!#^p6urH7*_IFZeUH;rYg|NA!#FrBRtw6y;5)AJrH^?9Ou!|rFo(&T zdN2yGMDI11f3tEc1Lk^p8u+_=-PxzKo3tohsS2j}=z;J86EtKW8@1Utgrdh{78I<) z=)Bi#A@i!zW9EKTrPA;$S!&SJEuU`^>3l`*PIHjXRpI4uR2A0tDnwMb;sRHC-j%ZM zU(J_8i<<^1Ltog}onA+F*2$xA7*#FyM#%{Lxs(rAOEKA_6t2Oo;i}K+;ehCcD;58r znixHmTqW9Kp`(PfZ|NHz7mVQ%^Inu)_t`wXO8%PdV@{XRLYGc7z4YC48>mwyxm+^* z--#{Cz}|XbM!)Sv;m`{eOS!a_&fmSm``sw~Kv2&spVpSCQ$O3DNCMdoZhZlx+d0Il z%}nR;$a`6^a3X2{xI>Y@^R#Z7H;aq%gnM0iu}KT%NIC zuaBk!eyZFB+%PtRc=wD3W=U*m&5ez_v?)srZfN^bQ@Vfp+J zuIat}_Nb57<;R~(B&+P-Z_{a&MPa)T1<~g_8DDqhzRS6KYIbG(pF7%hCl|}rr8Mpr zFpU$GR?h?PJ9Ay47px`gmZ}FSM+

s)fF8!%`mB%X*U^xy!Y%kNEvMl-+z0dp}^f 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 VnxXzb@{j-k002ovPDHLkV1jq5*%<%; diff --git a/static/img/shelf.png b/static/img/shelf.png deleted file mode 100755 index fdffe48b9fde009747f7cdf79b6dd1a9c852a5b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1124 zcmV-q1e^PbP)EX>4Tx04R}tkv&MmKpe$iQ$?v22Rn#3WT;LSL`4J_twIqhgj%6h2a`*`ph-iL z;^HW{799LotU9+0Yt2!bCVF0M|BE>hzEl0u6Z503ls?%w0>9U#=pOtU)108O{e zR3a{Bva4d(D|!$_7$MBa%rfRADGA^4b&mjF?_xa5|JTxwGo6|zju4B5Hdfl06-|wJk~perI^_!) zk5$fFoV9Y5HSft^7|!V{%Uq{9gaj6`1PLM(R8c}1He$5uq*zGNe%!}D==vpcDdZ}F zkz)ZBXpmh$_#gc4*2+(edr9Fq(D~vxAHzV`F3_ks&iAq7G){ovGjOH1{FOQ|{YiSQ zrA3c`zHQ**x}_<5z~v4w@MOrQ>`FnJLOu_?pV2pEf&N<{wC45J+{ftykfyGZH^9Lm zFjAoGb)R>4xA*q%nPz`KRH<^ClFOG~00006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru;|l`}9V2aUxp@Ep0%1u+K~z}7wOB!R+%O2`OL%6o z&NZ^=WqPvSsJ%okQ~xeHZX_82a#Anmjcp|0!v|#i`2LN62t}ShB7(pFf#%uqJi0Il z9UJrG_0IWnecbANZ{r!KH{L?}J~Ruo7NniEwkF4WOL>_ZJgU%5OBxawNYIdP!_N#t zjpr&3LL)>RjDm>3P}YIO-W(cEbs_r=PNl&_QkfT**07P+hgSLe3u#A2pH-?Sjq|?9 zFdu8?H{z;WU5|+5Jr*PH#<%SneYvSD6{3s?*0$+b#yrVP49u!a-$FPGbZbJk>RH4X zs}e_-KJL{m>9}YRJKvFP93pz#zLxe3Rr)!nFOO{+B8EjjIRGpKSplCN;Qv8wmKIvJ zGOrm?VVtm#ypEBUa=jx=eMW!~`%c^;q|Nw235K1^eT#mL!mJgv&Y;bq zy#){u>-h{1v{omHB8#ThFpm(QNzEgNnu(Eyas>XLXX;GvnvcO+E3!S&oRLljE&_x> zLbss~`4r8Oj7yz)V4281_l8_&nH6TQdCRKfffa!4`!0AYA$dM}L}8{s zZ}Bh~5R#(rzkV*vzUe}g?)F@wz2Q6(QVf#(v?nGki}0Tv3};?5n?m@|r2cGhrZkgG z>rSSKt>?dP?@5CO>yaUv-&`FJR5vf-kjdG&b}k%U6;2L;>veIQZ^Hh^UWvL&jL$k; zMGV0aoP9G>FCneE6S0ie8e}Rv;vs+aLxBNmm^LJzxk|ga`Yj0IFo+8VNq - - - - - - -pyShelf E-Book Server - - - -

-
-

pyShelf

-

Open Source E-book Server

-
-
-
- left_col -
-
-
Shelf
-
-
- -
- -