diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
index d88c825..52ee07a
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,16 @@
-app/books/*
-*.json
+books/*
+*.epub
+*.idea
*.pyc
app/content.opf
.vscode
-app/data/catalogue.db
+app/data/*
+fontend/db.sqlite3
GPATH
GRTAGS
GTAGS
.#*
-
+frontend/interface/migrations/*
# Standard Python gitignore below
# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
old mode 100644
new mode 100755
index 47976ea..8c0e19a
--- 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.8
diff --git a/README.md b/README.md
index 951f42b..7d83c85 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,8 @@
# pyShelf 0.1.0
-A simple terminal based ebook server
+
+
A simple terminal based ebook server
+
+
Frustrated with Calibre being my only option for hosting my eBook collection, I have decided to spin up my own.
@@ -17,23 +20,38 @@ pyShelf uses [`pre-commit`](https://pre-commit.com/) to automate some tasks.
Before developing, run `pre-commit install`.
See the [documentation](https://pre-commit.com/) for more information.
-pyShelf uses ['Doxygen'](https://http://www.doxygen.nl/) for source code documentation.
+pyShelf uses ['Doxygen'](http://www.doxygen.nl/) for source code documentation.
Any changes to source should be documented and have run doxygen doxygen.conf prior to commiting.
pyShelf follows ['sem-ver'](https://semver.org) standards. Before advancing version numbers be sure to set PROJECT_NUMBER in doxygen.conf accordingly.
## Configuration
-All configuration is done in config.py.
-The only currently required configuration is to set book_path to the location of your books.
+All pyShelf configuration is done in config.py.
+
+### Nginx configuration
+I have included a default nginx config file pyshelf_nginx.conf. This file should be sufficient to get you up and running. You are required to change the location alias's to reflect your pyshelf install folder leaving everything after /frontend intact.
+
+Further resources for nginx setup may be found @ [This nginx, django, & uwsgi, guide](https://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html)
+
+### uwsgi configuration
+Inside uwsgi.ini you should make changes to reflect your install directory, and the port you wish uwsgi to listen on. Alternativly you can make the requisite changes to listen on a socket instead. This change would also require a change to the pyshelf_nginx.conf file as well.
+
+### pyShelf configuration
+User configuration is contained within config.json in the project root. The only currently required configuration is to set book_path to the location of your books.
## Current Features
-Currently pyShelf will recursively scan your collection, extract and store some metadata in the sqlite database.
+Currently pyShelf will recursively scan your collection, extract and store some metadata in the sqlite database. It will also provide you with a web based frontend to view and download your books. Note that this is a very early alpha and lacking the ability to sort and search your collection. This feature is coming however.
-Django is being implemented to power the frontend experience, and web based database maintenance. The first steps of which are included in this commit. Also the book database has been switched over to reflect this.
+Django has been implemented to power the frontend experience, and web based database maintenance. The first steps of which are included in this commit. Also the book database has been switched over to reflect this. A properly configured web server is required for hosting the frontend, configuration of which is outside of the scope of this readme. Running via the Django test server might be possible, albeit not recomended.
+
+## In Progress
+
+* UI/UX tweaks, including making the book display responsive. and not so ugly.
+* Searching, & further organizational tools.
+* Improved cover image storage, and acquisition.
## Future Goals
-* HTML Frontend for file transfers
-* HTML Backend for catalogue maintenance
+* Support for other book formats (Currently on supporting EPUBS)
* Terminal Backend for catalogue maintenance
* Calculate page count from total characters
* (Thanks to @Fireblend for the idea) https://github.com/th3r00t/pyShelf/issues/3
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..9171045
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,4 @@
+import sys
+
+sys.path.insert(1, "app/")
+sys.path.insert(2, "frontend/")
diff --git a/app/__init__.py b/app/__init__.py
deleted file mode 100755
index 9a3cbc8..0000000
--- a/app/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-import os
-import sys
-
-sys.path.insert(0, os.path.abspath('.'))
diff --git a/app/config.py b/app/config.py
deleted file mode 100755
index b4200e7..0000000
--- a/app/config.py
+++ /dev/null
@@ -1,13 +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.catalogue_db = "../frontend/db.sqlite3"
- self.file_array = [
- self.book_shelf,
- self.catalogue_db,
- ]
- self.auto_scan = True
diff --git a/app/lib/display.py b/app/lib/display.py
deleted file mode 100644
index 9e89641..0000000
--- a/app/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/app/lib/library.py b/app/lib/library.py
deleted file mode 100755
index dde8525..0000000
--- a/app/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/app/lib/pyShelf.py b/app/lib/pyShelf.py
deleted file mode 100755
index 25f42d6..0000000
--- a/app/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/app/main.py b/app/main.py
deleted file mode 100755
index 8011fb7..0000000
--- a/app/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/config.json b/config.json
new file mode 100644
index 0000000..af20934
--- /dev/null
+++ b/config.json
@@ -0,0 +1,7 @@
+{
+ "TITLE": "pyShelf E-Book Server",
+ "VERSION": "0.1.0",
+ "BOOKPATH": "books/",
+ "DATABASE": "src/db.sqlite3",
+ "BOOKSHELF": "data/shelf.json"
+}
diff --git a/data/shelf.json b/data/shelf.json
new file mode 100644
index 0000000..197b2ea
--- /dev/null
+++ b/data/shelf.json
@@ -0,0 +1 @@
+{"/home/raelon/Projects/pyShelf/books/1789133807-[it-eb.com]/1789133807-[it-eb.com].epub": {"files": ["book.opf", "cover.xhtml"], "path": "/home/raelon/Projects/pyShelf/books/1789133807-[it-eb.com]/1789133807-[it-eb.com].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"}, "/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/Clancy/Clancy, Tom/Against All Enemies/Against All Enemies - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Against All Enemies/Against All Enemies - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Archimedes Effect, The/Archimedes Effect, The - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Archimedes Effect, The/Archimedes Effect, The - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Cardinal of the Kremlin, The/Cardinal of the Kremlin, The - Tom Clancy.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Cardinal of the Kremlin, The/Cardinal of the Kremlin, The - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Dead or Alive/Dead or Alive - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Dead or Alive/Dead or Alive - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Debt of Honor/Debt of Honor - Tom Clancy.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Debt of Honor/Debt of Honor - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Rainbow Six/Rainbow Six - Tom Clancy.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Rainbow Six/Rainbow Six - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Red Storm Rising/Red Storm Rising - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Red Storm Rising/Red Storm Rising - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Springboard/Springboard - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/Springboard/Springboard - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/State of War/State of War - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Clancy/Clancy, Tom/State of War/State of War - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Python Tricks by Dan Bader/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/Python Tricks by Dan Bader.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/1 - Before the Republic/002 - Dawn of the Jedi_Into the Void.epub": {"files": ["Lebb_9780345541949_epub_opf_r1.opf"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/1 - Before the Republic/002 - Dawn of the Jedi_Into the Void.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/003 - Lost Tribe of the Sith_The Collected Stories.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/003 - Lost Tribe of the Sith_The Collected Stories.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/004 - The Old Republic_Revan.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/004 - The Old Republic_Revan.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/005 - The Old Republic_Deceived.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/005 - The Old Republic_Deceived.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/006 - Red Harvest.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/006 - Red Harvest.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/007 - The Old Republic_Fatal Alliance.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/007 - The Old Republic_Fatal Alliance.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/008 - The Old Republic_Annihilation.epub": {"files": ["Karp_9780345535672_epub_opf_r1.opf"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/008 - The Old Republic_Annihilation.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/009 - Knight Errant.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/009 - Knight Errant.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/010 - Darth Bane_Path of Destruction.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/010 - Darth Bane_Path of Destruction.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/011 - Darth Bane_Rule of Two.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/011 - Darth Bane_Rule of Two.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/012 - Darth Bane_Dynasty of Evil.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/2 - Old Galactic Republic Era/012 - Darth Bane_Dynasty of Evil.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/013 - Legacy of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/013 - Legacy of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/014 - Darth Plagueis.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/014 - Darth Plagueis.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/015 - Jedi Apprentice_The Rising Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/015 - Jedi Apprentice_The Rising Force.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/016 - Jedi Apprentice_The Dark Rival.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/016 - Jedi Apprentice_The Dark Rival.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/017 - Jedi Apprentice_The Hidden Past.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/017 - Jedi Apprentice_The Hidden Past.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/018 - Jedi Apprentice_The Mark of the Crown.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/018 - Jedi Apprentice_The Mark of the Crown.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/019 - Jedi Apprentice_The Defenders of the Dead.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/019 - Jedi Apprentice_The Defenders of the Dead.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/020 - Jedi Apprentice_The Uncertain Path.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/020 - Jedi Apprentice_The Uncertain Path.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/021 - Jedi Apprentice_The Captive Temple.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/021 - Jedi Apprentice_The Captive Temple.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/022 - Jedi Apprentice_The Day of Reckoning.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/022 - Jedi Apprentice_The Day of Reckoning.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/023 - Jedi Apprentice_The Fight for Truth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/023 - Jedi Apprentice_The Fight for Truth.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/024 - Jedi Apprentice_The Shattered Peace.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/024 - Jedi Apprentice_The Shattered Peace.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/025 - Jedi Apprentice_Deceptions.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/025 - Jedi Apprentice_Deceptions.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/026 - The Life and Legend of Obi-Wan Kenobi.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/026 - The Life and Legend of Obi-Wan Kenobi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/027 - Jedi Apprentice_The Deadly Hunter.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/027 - Jedi Apprentice_The Deadly Hunter.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/039 - Cloak of Deception.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/039 - Cloak of Deception.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/040 - Maul_Lockdown.epub": {"files": ["Schr_9780345535665_epub_opf_r1.opf"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/040 - Maul_Lockdown.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/041 - Darth Maul_Shadow Hunter.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/041 - Darth Maul_Shadow Hunter.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/042 - Episode I_The Phantom Menace.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/042 - Episode I_The Phantom Menace.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/043 - Journal_Anakin Skywalker.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/043 - Journal_Anakin Skywalker.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/044 - Journal_Darth Maul.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/044 - Journal_Darth Maul.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/045 - Journal_Queen Amidala.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/045 - Journal_Queen Amidala.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/046 - Rogue Planet.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/046 - Rogue Planet.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/047 - Jedi Quest_Path to Truth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/047 - Jedi Quest_Path to Truth.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/048 - Jedi Quest_The Way of the Apprentice.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/048 - Jedi Quest_The Way of the Apprentice.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/049 - Outbound Flight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/049 - Outbound Flight.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/050 - Jedi Quest_The Trail of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/050 - Jedi Quest_The Trail of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/051 - Jedi Quest_Dangerous Games.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/051 - Jedi Quest_Dangerous Games.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/052 - Jedi Quest_The Master of Disguise.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/052 - Jedi Quest_The Master of Disguise.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/071 - Clone Wars Secret Missions_Guardians of the Chiss Key.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/071 - Clone Wars Secret Missions_Guardians of the Chiss Key.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/072 - The Clone Wars_Wild Space.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/072 - The Clone Wars_Wild Space.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/073 - Boba Fett_Hunted.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/073 - Boba Fett_Hunted.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/074 - Republic Commando_Hard Contact.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/074 - Republic Commando_Hard Contact.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/075 - Shatterpoint.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/075 - Shatterpoint.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/076 - The Clone Wars_No Prisoners.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/076 - The Clone Wars_No Prisoners.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/077 - Republic Commando_Triple Zero.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/077 - Republic Commando_Triple Zero.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/078 - Clone Wars Gambit_Stealth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/078 - Clone Wars Gambit_Stealth.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/079 - Clone Wars Gambit_Siege.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/079 - Clone Wars Gambit_Siege.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/080 - Republic Commando_True Colors.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/080 - Republic Commando_True Colors.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/081 - The Wrath of Darth Maul.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/081 - The Wrath of Darth Maul.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/082 - Medstar I_Battle Surgeons.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/082 - Medstar I_Battle Surgeons.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/083 - Medstar II_Jedi Healer.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/083 - Medstar II_Jedi Healer.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/084 - Yoda Dark Rendezvous.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/084 - Yoda Dark Rendezvous.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/085 - Boba Fett_A New Threat.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/085 - Boba Fett_A New Threat.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/086 - Boba Fett_Pursuit.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/086 - Boba Fett_Pursuit.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/087 - Episode III_Labyrinth of Evil.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/087 - Episode III_Labyrinth of Evil.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/088 - Episode III_Revenge of the Sith.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/088 - Episode III_Revenge of the Sith.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/089 - Republic Commando_Order 066.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/089 - Republic Commando_Order 066.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/090 - Kenobi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/090 - Kenobi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/091 - Episode III_Dark Lord The Rise of Darth Vader.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/091 - Episode III_Dark Lord The Rise of Darth Vader.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/092 - Republic Commando_501st.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/092 - Republic Commando_501st.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/028 - Jedi Apprentice_The Evil Experiment.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/028 - Jedi Apprentice_The Evil Experiment.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/038 - Darth Maul_Saboteur.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/038 - Darth Maul_Saboteur.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/053 - Jedi Quest_The School of Fear.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/053 - Jedi Quest_The School of Fear.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/070 - Clone Wars Secret Missions_Duel at Shattered Rock.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/070 - Clone Wars Secret Missions_Duel at Shattered Rock.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/093 - Coruscant Nights_Jedi Twilight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/093 - Coruscant Nights_Jedi Twilight.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/107 - A New Hope_The Life of Luke Skywalker.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/107 - A New Hope_The Life of Luke Skywalker.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/094 - The Last of the Jedi_The Desperate Mission.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/094 - The Last of the Jedi_The Desperate Mission.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/095 - The Last of the Jedi_Dark Warning.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/095 - The Last of the Jedi_Dark Warning.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/096 - The Last of the Jedi_Underworld.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/096 - The Last of the Jedi_Underworld.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/097 - The Last of the Jedi_Death on Naboo.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/097 - The Last of the Jedi_Death on Naboo.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/098 - Coruscant Nights_Street of Shadows.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/098 - Coruscant Nights_Street of Shadows.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/099 - The Last of the Jedi_A Tangled Web.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/099 - The Last of the Jedi_A Tangled Web.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/100 - The Last of the Jedi_Return of the Dark Side.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/100 - The Last of the Jedi_Return of the Dark Side.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/101 - The Last of the Jedi_Secret Weapon.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/101 - The Last of the Jedi_Secret Weapon.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/102 - The Last of the Jedi_Against the Empire.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/102 - The Last of the Jedi_Against the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/103 - The Last of the Jedi_Master of Deception.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/103 - The Last of the Jedi_Master of Deception.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/104 - The Last of the Jedi_Reckoning.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/104 - The Last of the Jedi_Reckoning.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/105 - Coruscant Nights_Patterns of Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/105 - Coruscant Nights_Patterns of Force.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/106 - The Last Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/106 - The Last Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/054 - Jedi Quest_The Shadow Trap.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/054 - Jedi Quest_The Shadow Trap.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/055 - Jedi Quest_The Moment of Truth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/055 - Jedi Quest_The Moment of Truth.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/056 - Jedi Quest_The Changing of the Guard.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/056 - Jedi Quest_The Changing of the Guard.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/057 - Jedi Quest_The False Peace.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/057 - Jedi Quest_The False Peace.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/058 - Jedi Quest_The Final Showdown.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/058 - Jedi Quest_The Final Showdown.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/059 - The Approaching Storm.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/059 - The Approaching Storm.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/060 - Episode II_Attack of the Clones.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/060 - Episode II_Attack of the Clones.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/061 - Boba Fett_The Fight to Survive.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/061 - Boba Fett_The Fight to Survive.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/062 - Boba Fett_Crossfire.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/062 - Boba Fett_Crossfire.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/063 - Boba Fett_Maze of Deception.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/063 - Boba Fett_Maze of Deception.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/064 - The Cestus Deception.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/064 - The Cestus Deception.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/065 - The Hive.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/065 - The Hive.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/066 - Jedi Trial.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/066 - Jedi Trial.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/067 - The Clone Wars.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/067 - The Clone Wars.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/068 - Clone Wars Secret Missions_Breakout Squad.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/068 - Clone Wars Secret Missions_Breakout Squad.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/069 - Clone Wars Secret Missions_Curse of the Black Hole Pirates.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/069 - Clone Wars Secret Missions_Curse of the Black Hole Pirates.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/108 - Han Solo_The Paradise Snare.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/108 - Han Solo_The Paradise Snare.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/109 - Han Solo_The Hutt Gambit.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/109 - Han Solo_The Hutt Gambit.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/110 - The Force Unleashed.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/110 - The Force Unleashed.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/111 - The Adventures of Lando Calrissian.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/111 - The Adventures of Lando Calrissian.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/112 - Death Star.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/112 - Death Star.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/113 - The Han Solo Adventures.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/113 - The Han Solo Adventures.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/114 - Han Solo_Rebel Dawn.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/114 - Han Solo_Rebel Dawn.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/115 - The Force Unleashed II.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/115 - The Force Unleashed II.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/116 - Dark Forces_Soldier for the Empire.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/116 - Dark Forces_Soldier for the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/117 - Dark Forces_Rebel Agent.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/117 - Dark Forces_Rebel Agent.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/118 - Dark Forces_Jedi Knight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/118 - Dark Forces_Jedi Knight.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/119 - Death Troopers.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/119 - Death Troopers.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/029 - Jedi Apprentice_The Dangerous Rescue.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/029 - Jedi Apprentice_The Dangerous Rescue.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/030 - Jedi Apprentice_The Ties That Bind.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/030 - Jedi Apprentice_The Ties That Bind.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/031 - Jedi Apprentice_The Death of Hope.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/031 - Jedi Apprentice_The Death of Hope.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/032 - Jedi Apprentice_The Call to Vengeance.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/032 - Jedi Apprentice_The Call to Vengeance.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/033 - Jedi Apprentice_The Only Witness.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/033 - Jedi Apprentice_The Only Witness.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/034 - Jedi Apprentice_The Threat Within.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/034 - Jedi Apprentice_The Threat Within.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/035 - Jedi Apprentice_The Followers.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/035 - Jedi Apprentice_The Followers.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/036 - Secrets of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/036 - Secrets of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/037 - The Rise and Fall of Darth Vader.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/3 - Rise of the Empire Era/037 - The Rise and Fall of Darth Vader.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/139 - The Swarm.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/139 - The Swarm.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/120 - Shadow Games.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/120 - Shadow Games.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/121 - Episode IV_A New Hope.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/121 - Episode IV_A New Hope.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/122 - Tales From Mos Eisley Cantina.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/122 - Tales From Mos Eisley Cantina.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/123 - Scoundrels.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/123 - Scoundrels.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/124 - Rebel Force_Target.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/124 - Rebel Force_Target.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/125 - Rebel Force_Hostage.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/125 - Rebel Force_Hostage.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/126 - Rebel Force_Renegade.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/126 - Rebel Force_Renegade.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/127 - Rebel Force_Firefight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/127 - Rebel Force_Firefight.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/128 - Rebel Force_Trapped.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/128 - Rebel Force_Trapped.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/129 - Allegiance.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/129 - Allegiance.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/130 - Rebel Force_Uprising.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/130 - Rebel Force_Uprising.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/131 - Eaten Alive.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/131 - Eaten Alive.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/132 - City of the Dead.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/132 - City of the Dead.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/133 - Planet Plague.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/133 - Planet Plague.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/134 - The Nightmare Machine.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/134 - The Nightmare Machine.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/135 - Ghost of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/135 - Ghost of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/136 - Army of Terror.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/136 - Army of Terror.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/137 - Choices of One.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/137 - Choices of One.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/138 - The Brain Spiders.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/138 - The Brain Spiders.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/140 - Spore.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/140 - Spore.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/141 - The Doomsday Ship.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/141 - The Doomsday Ship.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/142 - Clones.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/142 - Clones.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/143 - The Hunger.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/143 - The Hunger.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/144 - Honor Among Thieves.epub": {"files": ["OEBPS/content.opf", "OEBPS/Text/cover.xhtml", "OEBPS/Images/cover.jpg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/144 - Honor Among Thieves.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/145 - Galaxies_The Ruins of Dantooine.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/145 - Galaxies_The Ruins of Dantooine.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/146 - Splinter of the Mind's Eye.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/146 - Splinter of the Mind's Eye.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/147 - Empire and Rebellion_Razor's Edge.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/147 - Empire and Rebellion_Razor's Edge.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/148 - Episode V_The Empire Strikes Back.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/148 - Episode V_The Empire Strikes Back.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/149 - Tales of the Bounty Hunters.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/149 - Tales of the Bounty Hunters.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/150 - Shadows of the Empire.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/150 - Shadows of the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/151 - Tales From the Empire.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/151 - Tales From the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/152 - Tales From Jabba's Palace.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/152 - Tales From Jabba's Palace.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/153 - Episode VI_Return of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/153 - Episode VI_Return of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/154 - The Bounty Hunter Wars_The Mandalorian Armor.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/154 - The Bounty Hunter Wars_The Mandalorian Armor.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/155 - The Bounty Hunter Wars_Slave Ship.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/155 - The Bounty Hunter Wars_Slave Ship.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/156 - The Truce at Bakura.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/156 - The Truce at Bakura.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/157 - The Bounty Hunter Wars_Hard Merchandise.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/4 - Rebellion Era/157 - The Bounty Hunter Wars_Hard Merchandise.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/158 - Tales From the New Republic.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/158 - Tales From the New Republic.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/159 - Luke Skywalker and the Shadows of Mindor.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/159 - Luke Skywalker and the Shadows of Mindor.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/160 - Jedi Prince_The Glove of Darth Vader.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/160 - Jedi Prince_The Glove of Darth Vader.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/161 - Jedi Prince_The Lost City of the Jedi.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/161 - Jedi Prince_The Lost City of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/162 - Jedi Prince_Zorba the Hutt's Revenge.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/162 - Jedi Prince_Zorba the Hutt's Revenge.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/163 - Jedi Prince_Mission From Mount Yoda.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/163 - Jedi Prince_Mission From Mount Yoda.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/164 - Jedi Prince_Queen of the Empire.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/164 - Jedi Prince_Queen of the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/165 - Jedi Prince_Prophets of the Dark Side.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/165 - Jedi Prince_Prophets of the Dark Side.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/166 - Dark Forces_Rebel Agent.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/166 - Dark Forces_Rebel Agent.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/167 - Dark Forces_ Jedi Knight.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/167 - Dark Forces_ Jedi Knight.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/168 - X-Wing_Rogue Squadron.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/168 - X-Wing_Rogue Squadron.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/169 - X-Wing_Wedge's Gamble.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/169 - X-Wing_Wedge's Gamble.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/170 - X-Wing_The Krytos Trap.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/170 - X-Wing_The Krytos Trap.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/171 - X-Wing_The Bacta War.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/171 - X-Wing_The Bacta War.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/172 - X-Wing_Wraith Squadron.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/172 - X-Wing_Wraith Squadron.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/173 - X-Wing_Iron Fist.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/173 - X-Wing_Iron Fist.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/174 - X-Wing_Solo Command.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/174 - X-Wing_Solo Command.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/175 - The Courtship of Princess Leia.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/175 - The Courtship of Princess Leia.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/177 - Tatooine Ghost.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/177 - Tatooine Ghost.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/178 - Thrawn_Heir to the Empire.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/178 - Thrawn_Heir to the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/179 - Thrawn_Dark Force Rising.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/179 - Thrawn_Dark Force Rising.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/180 - Thrawn_The Last Command.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/180 - Thrawn_The Last Command.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/181 - X-Wing_Isard's Revenge.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/181 - X-Wing_Isard's Revenge.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/182 - The Jedi Academy_Jedi Search.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/182 - The Jedi Academy_Jedi Search.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/183 - The Jedi Academy_Dark Apprentice.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/183 - The Jedi Academy_Dark Apprentice.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/184 - The Jedi Academy_Champions of the Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/184 - The Jedi Academy_Champions of the Force.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/185 - I, Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/185 - I, Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/186 - Children of the Jedi.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/186 - Children of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/187 - Darksaber.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/187 - Darksaber.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/188 - X-Wing_Starfighters of Adumar.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/188 - X-Wing_Starfighters of Adumar.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/189 - Planet of Twilight.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/189 - Planet of Twilight.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/191 - The Black Fleet Crisis_Before the Storm.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/191 - The Black Fleet Crisis_Before the Storm.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/192 - The Black Fleet Crisis_Shield of Lies.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/192 - The Black Fleet Crisis_Shield of Lies.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/193 - The Black Fleet Crisis_Tyrant's Test.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/193 - The Black Fleet Crisis_Tyrant's Test.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/194 - The New Rebellion.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/194 - The New Rebellion.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/195 - The Corellian_Ambush at Corellia.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/195 - The Corellian_Ambush at Corellia.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/196 - The Corellian_Assault at Selonia.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/196 - The Corellian_Assault at Selonia.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/197 - The Corellian_Showdown at Centerpoint.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/197 - The Corellian_Showdown at Centerpoint.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/198 - The Hand of Thrawn_Specter of the Past.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/198 - The Hand of Thrawn_Specter of the Past.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/199 - The Hand of Thrawn_Vision of the Future.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/199 - The Hand of Thrawn_Vision of the Future.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/200 - Scourge.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/200 - Scourge.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/201 - Junior Jedi Knights_The Golden Globe.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/201 - Junior Jedi Knights_The Golden Globe.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/202 - Junior Jedi Knights_Lyric's World.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/202 - Junior Jedi Knights_Lyric's World.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/203 - Junior Jedi Knights_Promises.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/203 - Junior Jedi Knights_Promises.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/176 - A Forest Apart.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/176 - A Forest Apart.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/190 - The Crystal Star.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/190 - The Crystal Star.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/204 - Junior Jedi Knights_Anakin's Quest.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/204 - Junior Jedi Knights_Anakin's Quest.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/205 - Junior Jedi Knights_Vader's Fortress.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/205 - Junior Jedi Knights_Vader's Fortress.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/206 - Junior Jedi Knights_Kenobi's Blade.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/206 - Junior Jedi Knights_Kenobi's Blade.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/207 - Fool's Bargain.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/207 - Fool's Bargain.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/208 - Survivor's Quest.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/208 - Survivor's Quest.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/209 - Young Jedi Knights_Heirs of the Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/209 - Young Jedi Knights_Heirs of the Force.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/210 - Young Jedi Knights_Shadow Academy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/210 - Young Jedi Knights_Shadow Academy.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/211 - Young Jedi Knights_The Lost Ones.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/211 - Young Jedi Knights_The Lost Ones.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/212 - Young Jedi Knights_Lightsabers.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/212 - Young Jedi Knights_Lightsabers.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/213 - Young Jedi Knights_Darkest Knight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/213 - Young Jedi Knights_Darkest Knight.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/214 - Young Jedi Knights_Jedi Under Siege.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/214 - Young Jedi Knights_Jedi Under Siege.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/215 - Young Jedi Knights_Shards of Alderaann.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/215 - Young Jedi Knights_Shards of Alderaann.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/216 - Young Jedi Knights_Diversity Alliance.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/216 - Young Jedi Knights_Diversity Alliance.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/217 - Young Jedi Knights_Delusions of Grandeur.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/217 - Young Jedi Knights_Delusions of Grandeur.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/218 - Young Jedi Knights_Jedi Bounty.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/218 - Young Jedi Knights_Jedi Bounty.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/219 - Young Jedi Knights_The Emperor's Plague.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/219 - Young Jedi Knights_The Emperor's Plague.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/220 - Young Jedi Knights_Return to Ord Mantell.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/220 - Young Jedi Knights_Return to Ord Mantell.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/221 - Young Jedi Knights_Trouble on Cloud City.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/221 - Young Jedi Knights_Trouble on Cloud City.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/222 - Young Jedi Knights_Crisis at Crystal Reef.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/5 - New Republic Era/222 - Young Jedi Knights_Crisis at Crystal Reef.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/237 - The New Jedi Order_Traitor.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/237 - The New Jedi Order_Traitor.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/223 - Boba Fett_A Practical Man.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/223 - Boba Fett_A Practical Man.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/224 - The New Jedi Order_Vector Prime.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/224 - The New Jedi Order_Vector Prime.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/225 - The New Jedi Order_Dark Tide I - Onslaught.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/225 - The New Jedi Order_Dark Tide I - Onslaught.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/226 - The New Jedi Order_Dark Tide II - Ruin.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/226 - The New Jedi Order_Dark Tide II - Ruin.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/227 - The New Jedi Order_Agents of Chaos I - Hero's Trial.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/227 - The New Jedi Order_Agents of Chaos I - Hero's Trial.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/228 - The New Jedi Order_Agents of Chaos II - Jedi Eclipse.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/228 - The New Jedi Order_Agents of Chaos II - Jedi Eclipse.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/229 - The New Jedi Order_Balance Point.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/229 - The New Jedi Order_Balance Point.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/230 - The New Jedi Order_Recovery.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/230 - The New Jedi Order_Recovery.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/231 - The New Jedi Order_Edge of Victory I - Conquest.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/231 - The New Jedi Order_Edge of Victory I - Conquest.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/232 - The New Jedi Order_Edge of Victory II - Rebirth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/232 - The New Jedi Order_Edge of Victory II - Rebirth.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/233 - The New Jedi Order_Star by Star.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/233 - The New Jedi Order_Star by Star.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/234 - The New Jedi Order_Dark Journey.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/234 - The New Jedi Order_Dark Journey.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/235 - The New Jedi Order_Enemy Lines I - Rebel Dream.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/235 - The New Jedi Order_Enemy Lines I - Rebel Dream.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/236 - The New Jedi Order_Enemy Lines II - Rebel Stand.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/236 - The New Jedi Order_Enemy Lines II - Rebel Stand.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/238 - The New Jedi Order_Destiny's Way.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/238 - The New Jedi Order_Destiny's Way.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/239 - The New Jedi Order_Ylesia.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/239 - The New Jedi Order_Ylesia.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/240 - The New Jedi Order_Force Heretic I - Remnant.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/240 - The New Jedi Order_Force Heretic I - Remnant.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/241 - The New Jedi Order_Force Heretic II - Refugee.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/241 - The New Jedi Order_Force Heretic II - Refugee.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/242 - The New Jedi Order_Force Heretic III - Reunion.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/242 - The New Jedi Order_Force Heretic III - Reunion.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/243 - The New Jedi Order_The Final Prophecy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/243 - The New Jedi Order_The Final Prophecy.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/244 - The New Jedi Order_The Unifying Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/244 - The New Jedi Order_The Unifying Force.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/245 - Dark Nest_The Joiner King.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/245 - Dark Nest_The Joiner King.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/246 - Dark Nest_The Unseen Queen.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/246 - Dark Nest_The Unseen Queen.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/247 - Dark Nest_The Swarm War.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/6 - New Jedi Order Era/247 - Dark Nest_The Swarm War.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/248 - Legacy of the Force_Betrayal.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/248 - Legacy of the Force_Betrayal.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/249- Legacy of the Force_Bloodlines.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/249- Legacy of the Force_Bloodlines.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/250 - Legacy of the Force_Tempest.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/250 - Legacy of the Force_Tempest.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/251 - Legacy of the Force_Exile.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/251 - Legacy of the Force_Exile.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/252 - Legacy of the Force_Sacrifice.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/252 - Legacy of the Force_Sacrifice.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/253 - Legacy of the Force_Inferno.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/253 - Legacy of the Force_Inferno.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/254 - Legacy of the Force_Fury.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/254 - Legacy of the Force_Fury.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/255 - Legacy of the Force_Revelation.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/255 - Legacy of the Force_Revelation.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/256 - Legacy of the Force_Invincible.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/256 - Legacy of the Force_Invincible.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/257 - Crosscurrent.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/257 - Crosscurrent.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/258 - Riptide.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/258 - Riptide.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/259 - Millennium Falcon.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/259 - Millennium Falcon.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/260 - Fate of the Jedi_Outcast.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/260 - Fate of the Jedi_Outcast.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/261 - Fate of the Jedi_Omen.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/261 - Fate of the Jedi_Omen.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/262 - Fate of the Jedi_Abyss.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/262 - Fate of the Jedi_Abyss.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/263 - Fate of the Jedi_Backlash.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/263 - Fate of the Jedi_Backlash.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/264 - Fate of the Jedi_Allies.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/264 - Fate of the Jedi_Allies.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/265 - Fate of the Jedi_Vortex.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/265 - Fate of the Jedi_Vortex.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/266 - Fate of the Jedi_Conviction.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/266 - Fate of the Jedi_Conviction.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/267 - Fate of the Jedi_Ascension.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/267 - Fate of the Jedi_Ascension.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/268 - Fate of the Jedi_Apocalypse.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/268 - Fate of the Jedi_Apocalypse.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/269 - X-Wing_Mercy Kill.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/269 - X-Wing_Mercy Kill.epub"}, "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/270 - Crucible.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Star Wars/7 - Legacy Era/270 - Crucible.epub"}, "/home/raelon/Projects/pyShelf/books/Ubuntu Server Essentials - 6685 [ECLiPSE]/Ubuntu Server Essentials.epub": {"files": ["OEBPS/cover/cover.jpg", "OEBPS/cover.html", "OEBPS/content.opf"], "path": "/home/raelon/Projects/pyShelf/books/Ubuntu Server Essentials - 6685 [ECLiPSE]/Ubuntu Server Essentials.epub"}}
diff --git a/docs/html/HTML/D/index.html b/docs/html/HTML/D/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/FILEMAP b/docs/html/HTML/FILEMAP
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/GTAGSROOT b/docs/html/HTML/GTAGSROOT
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/I/index.html b/docs/html/HTML/I/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/J/index.html b/docs/html/HTML/J/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/R/index.html b/docs/html/HTML/R/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/S/index.html b/docs/html/HTML/S/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/Y/index.html b/docs/html/HTML/Y/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/defines.html b/docs/html/HTML/defines.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/defines/index.html b/docs/html/HTML/defines/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/files.html b/docs/html/HTML/files.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/files/index.html b/docs/html/HTML/files/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/help.html b/docs/html/HTML/help.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/index.html b/docs/html/HTML/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/mains.html b/docs/html/HTML/mains.html
old mode 100644
new mode 100755
diff --git a/docs/html/HTML/rebuild.sh b/docs/html/HTML/rebuild.sh
old mode 100644
new mode 100755
diff --git a/docs/html/annotated.html b/docs/html/annotated.html
old mode 100644
new mode 100755
diff --git a/docs/html/bc_s.png b/docs/html/bc_s.png
old mode 100644
new mode 100755
diff --git a/docs/html/bdwn.png b/docs/html/bdwn.png
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1config_1_1Config-members.html b/docs/html/classapp_1_1config_1_1Config-members.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1config_1_1Config.html b/docs/html/classapp_1_1config_1_1Config.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1api__hooks_1_1DuckDuckGo-members.html b/docs/html/classapp_1_1lib_1_1api__hooks_1_1DuckDuckGo-members.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1api__hooks_1_1DuckDuckGo.html b/docs/html/classapp_1_1lib_1_1api__hooks_1_1DuckDuckGo.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1display_1_1Frontend-members.html b/docs/html/classapp_1_1lib_1_1display_1_1Frontend-members.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1display_1_1Frontend.html b/docs/html/classapp_1_1lib_1_1display_1_1Frontend.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1library_1_1Catalogue-members.html b/docs/html/classapp_1_1lib_1_1library_1_1Catalogue-members.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1library_1_1Catalogue.html b/docs/html/classapp_1_1lib_1_1library_1_1Catalogue.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1pyShelf_1_1BookDisplay-members.html b/docs/html/classapp_1_1lib_1_1pyShelf_1_1BookDisplay-members.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1pyShelf_1_1BookDisplay.html b/docs/html/classapp_1_1lib_1_1pyShelf_1_1BookDisplay.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1pyShelf_1_1BookServer-members.html b/docs/html/classapp_1_1lib_1_1pyShelf_1_1BookServer-members.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1pyShelf_1_1BookServer.html b/docs/html/classapp_1_1lib_1_1pyShelf_1_1BookServer.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1pyShelf_1_1InitFiles-members.html b/docs/html/classapp_1_1lib_1_1pyShelf_1_1InitFiles-members.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1pyShelf_1_1InitFiles.html b/docs/html/classapp_1_1lib_1_1pyShelf_1_1InitFiles.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1pyShelf_1_1RequestHandler-members.html b/docs/html/classapp_1_1lib_1_1pyShelf_1_1RequestHandler-members.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1pyShelf_1_1RequestHandler.html b/docs/html/classapp_1_1lib_1_1pyShelf_1_1RequestHandler.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1pyShelf_1_1RequestHandler.png b/docs/html/classapp_1_1lib_1_1pyShelf_1_1RequestHandler.png
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1storage_1_1Storage-members.html b/docs/html/classapp_1_1lib_1_1storage_1_1Storage-members.html
old mode 100644
new mode 100755
diff --git a/docs/html/classapp_1_1lib_1_1storage_1_1Storage.html b/docs/html/classapp_1_1lib_1_1storage_1_1Storage.html
old mode 100644
new mode 100755
diff --git a/docs/html/classes.html b/docs/html/classes.html
old mode 100644
new mode 100755
diff --git a/docs/html/closed.png b/docs/html/closed.png
old mode 100644
new mode 100755
diff --git a/docs/html/dir_9dc6c7acf21934bbaaf79b41db58c4e7.html b/docs/html/dir_9dc6c7acf21934bbaaf79b41db58c4e7.html
old mode 100644
new mode 100755
diff --git a/docs/html/dir_d422163b96683743ed3963d4aac17747.html b/docs/html/dir_d422163b96683743ed3963d4aac17747.html
old mode 100644
new mode 100755
diff --git a/docs/html/doc.png b/docs/html/doc.png
old mode 100644
new mode 100755
diff --git a/docs/html/doxygen.css b/docs/html/doxygen.css
old mode 100644
new mode 100755
diff --git a/docs/html/doxygen.png b/docs/html/doxygen.png
old mode 100644
new mode 100755
diff --git a/docs/html/dynsections.js b/docs/html/dynsections.js
old mode 100644
new mode 100755
diff --git a/docs/html/files.html b/docs/html/files.html
old mode 100644
new mode 100755
diff --git a/docs/html/folderclosed.png b/docs/html/folderclosed.png
old mode 100644
new mode 100755
diff --git a/docs/html/folderopen.png b/docs/html/folderopen.png
old mode 100644
new mode 100755
diff --git a/docs/html/functions.html b/docs/html/functions.html
old mode 100644
new mode 100755
diff --git a/docs/html/functions_func.html b/docs/html/functions_func.html
old mode 100644
new mode 100755
diff --git a/docs/html/hierarchy.html b/docs/html/hierarchy.html
old mode 100644
new mode 100755
diff --git a/docs/html/index.hhc b/docs/html/index.hhc
old mode 100644
new mode 100755
diff --git a/docs/html/index.hhk b/docs/html/index.hhk
old mode 100644
new mode 100755
diff --git a/docs/html/index.hhp b/docs/html/index.hhp
old mode 100644
new mode 100755
diff --git a/docs/html/index.html b/docs/html/index.html
old mode 100644
new mode 100755
diff --git a/docs/html/jquery.js b/docs/html/jquery.js
old mode 100644
new mode 100755
diff --git a/docs/html/menu.js b/docs/html/menu.js
old mode 100644
new mode 100755
diff --git a/docs/html/menudata.js b/docs/html/menudata.js
old mode 100644
new mode 100755
diff --git a/docs/html/nav_f.png b/docs/html/nav_f.png
old mode 100644
new mode 100755
diff --git a/docs/html/nav_g.png b/docs/html/nav_g.png
old mode 100644
new mode 100755
diff --git a/docs/html/nav_h.png b/docs/html/nav_h.png
old mode 100644
new mode 100755
diff --git a/docs/html/open.png b/docs/html/open.png
old mode 100644
new mode 100755
diff --git a/docs/html/splitbar.png b/docs/html/splitbar.png
old mode 100644
new mode 100755
diff --git a/docs/html/sync_off.png b/docs/html/sync_off.png
old mode 100644
new mode 100755
diff --git a/docs/html/sync_on.png b/docs/html/sync_on.png
old mode 100644
new mode 100755
diff --git a/docs/html/tab_a.png b/docs/html/tab_a.png
old mode 100644
new mode 100755
diff --git a/docs/html/tab_b.png b/docs/html/tab_b.png
old mode 100644
new mode 100755
diff --git a/docs/html/tab_h.png b/docs/html/tab_h.png
old mode 100644
new mode 100755
diff --git a/docs/html/tab_s.png b/docs/html/tab_s.png
old mode 100644
new mode 100755
diff --git a/docs/html/tabs.css b/docs/html/tabs.css
old mode 100644
new mode 100755
diff --git a/docs/man/man3/app_config_Config.3 b/docs/man/man3/app_config_Config.3
old mode 100644
new mode 100755
diff --git a/docs/man/man3/app_lib_api_hooks_DuckDuckGo.3 b/docs/man/man3/app_lib_api_hooks_DuckDuckGo.3
old mode 100644
new mode 100755
diff --git a/docs/man/man3/app_lib_display_Frontend.3 b/docs/man/man3/app_lib_display_Frontend.3
old mode 100644
new mode 100755
diff --git a/docs/man/man3/app_lib_library_Catalogue.3 b/docs/man/man3/app_lib_library_Catalogue.3
old mode 100644
new mode 100755
diff --git a/docs/man/man3/app_lib_pyShelf_BookDisplay.3 b/docs/man/man3/app_lib_pyShelf_BookDisplay.3
old mode 100644
new mode 100755
diff --git a/docs/man/man3/app_lib_pyShelf_BookServer.3 b/docs/man/man3/app_lib_pyShelf_BookServer.3
old mode 100644
new mode 100755
diff --git a/docs/man/man3/app_lib_pyShelf_InitFiles.3 b/docs/man/man3/app_lib_pyShelf_InitFiles.3
old mode 100644
new mode 100755
diff --git a/docs/man/man3/app_lib_pyShelf_RequestHandler.3 b/docs/man/man3/app_lib_pyShelf_RequestHandler.3
old mode 100644
new mode 100755
diff --git a/docs/man/man3/app_lib_storage_Storage.3 b/docs/man/man3/app_lib_storage_Storage.3
old mode 100644
new mode 100755
diff --git a/docs/warn.log b/docs/warn.log
old mode 100644
new mode 100755
diff --git a/frontend/db.sqlite3 b/frontend/db.sqlite3
deleted file mode 100644
index c1041f9..0000000
Binary files a/frontend/db.sqlite3 and /dev/null differ
diff --git a/frontend/frontend/settings.py b/frontend/frontend/settings.py
deleted file mode 100644
index aff4e67..0000000
--- a/frontend/frontend/settings.py
+++ /dev/null
@@ -1,121 +0,0 @@
-"""
-Django settings for frontend project.
-
-Generated by 'django-admin startproject' using Django 2.2.7.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/2.2/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/2.2/ref/settings/
-"""
-
-import os
-
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-
-# Quick-start development settings - unsuitable for production
-# 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'
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
-
-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',
-]
-
-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',
-]
-
-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',
- ],
- },
- },
-]
-
-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'),
- }
-}
-
-
-# Password validation
-# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
-
-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',
- },
-]
-
-
-# Internationalization
-# https://docs.djangoproject.com/en/2.2/topics/i18n/
-
-LANGUAGE_CODE = 'en-us'
-
-TIME_ZONE = 'UTC'
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = True
-
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/2.2/howto/static-files/
-
-STATIC_URL = '/static/'
diff --git a/frontend/interface/migrations/0001_initial.py b/frontend/interface/migrations/0001_initial.py
deleted file mode 100644
index ac80ebb..0000000
--- a/frontend/interface/migrations/0001_initial.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Generated by Django 2.2.7 on 2019-11-10 03:56
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ]
-
- operations = [
- migrations.CreateModel(
- 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)),
- ],
- ),
- ]
diff --git a/frontend/interface/models.py b/frontend/interface/models.py
deleted file mode 100644
index 28d17c8..0000000
--- a/frontend/interface/models.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from django.db import models
-
-# Create your models here.
-
-
-class books(models.Model):
- """
- pyShelfs Book Database class
- :param title: Book title
- :param author: Author
- :param categories: Categories <-- Not implemented
- :param cover: Cover image BinaryField
- :param pages: # of pages <-- Not implemented
- :param progress: Reader percentage <-- Not implented
- :param file_name: Path to book
- """
- title = models.CharField(max_length=255)
- author = models.CharField(max_length=255, blank=True)
- categories = models.CharField(max_length=255, blank=True)
- cover = models.BinaryField(blank=True, editable=True)
- pages = models.IntegerField(blank=True)
- progress = models.IntegerField(blank=True)
- file_name = models.CharField(max_length=255, blank=False)
diff --git a/frontend/interface/views.py b/frontend/interface/views.py
deleted file mode 100644
index ec88b94..0000000
--- a/frontend/interface/views.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.shortcuts import render
-from django.http import HttpResponse
-# Create your views here.
-
-def index(request):
- return HttpResponse("HTTP Request")
diff --git a/importBooks b/importBooks
new file mode 100755
index 0000000..326e905
--- /dev/null
+++ b/importBooks
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+import pathlib
+import sys
+
+from src.backend.pyShelf_ScanLibrary import execute_scan
+
+PRG_PATH = pathlib.Path.cwd()
+LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib")
+sys.path.insert(0, PRG_PATH)
+print("\n")
+execute_scan(PRG_PATH)
diff --git a/pyproject.toml b/pyproject.toml
index 08bc4fb..ffa6471 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,4 +7,4 @@ use_parentheses = true
# NOTE: the known_third_party setting is managed by
# seed-isort-config and should not be modified directly.
# Any changes made to this setting will be overwritten.
-known_third_party = ["PIL", "bs4", "config", "django", "interface", "lib", "requests"]
+known_third_party = ["bs4", "django", "interface", "requests"]
diff --git a/pyshelf_nginx.conf b/pyshelf_nginx.conf
new file mode 100644
index 0000000..9820f27
--- /dev/null
+++ b/pyshelf_nginx.conf
@@ -0,0 +1,40 @@
+# mysite_nginx.conf
+
+# the upstream component nginx needs to connect to
+upstream django {
+ # server unix:///path/to/your/mysite/mysite.sock; # for a file socket
+ server 127.0.0.1:8001; # for a web port socket (we'll use this first)
+}
+
+# configuration of the server
+server {
+ # the port your site will be served on
+ listen 8000;
+ # the domain name it will serve for
+ server_name 127.0.0.1; # substitute your machine's IP address or FQDN
+ charset utf-8;
+
+ # max upload size
+ client_max_body_size 75M; # adjust to taste
+
+ # Django media
+ location /media {
+ alias /home/raelon/Projects/pyShelf/frontend/interface/media; # your Django project's media files - amend as required
+ }
+
+ location /static {
+ alias /home/raelon/Projects/pyShelf/frontend/interface/static; # your Django project's static files - amend as required
+ }
+
+ location /books {
+ internal;
+ alias /home/raelon/Projects/pyShelf/books;
+ # Absolute location of your ebook files
+ }
+
+ # Finally, send all non-media requests to the Django server.
+ location / {
+ uwsgi_pass django;
+ include /home/raelon/Projects/pyShelf/uwsgi_params; # the uwsgi_params file you installed
+ }
+}
diff --git a/requirements.txt b/requirements.txt
index 770a533..761f48e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,3 +8,9 @@ urllib3
urwid
w3lib
websockets
+pre-commit
+isort
+django
+toml
+uwsgi
+django-debug-toolbar
diff --git a/app/lib/__init__.py b/src/__init__.py
similarity index 100%
rename from app/lib/__init__.py
rename to src/__init__.py
diff --git a/frontend/frontend/__init__.py b/src/backend/__init__.py
old mode 100644
new mode 100755
similarity index 100%
rename from frontend/frontend/__init__.py
rename to src/backend/__init__.py
diff --git a/src/backend/data/shelf.json b/src/backend/data/shelf.json
new file mode 100755
index 0000000..e3723ec
--- /dev/null
+++ b/src/backend/data/shelf.json
@@ -0,0 +1 @@
+{"/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Against All Enemies/Against All Enemies - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Against All Enemies/Against All Enemies - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Archimedes Effect, The/Archimedes Effect, The - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Archimedes Effect, The/Archimedes Effect, The - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Cardinal of the Kremlin, The/Cardinal of the Kremlin, The - Tom Clancy.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Cardinal of the Kremlin, The/Cardinal of the Kremlin, The - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Dead or Alive/Dead or Alive - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Dead or Alive/Dead or Alive - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Debt of Honor/Debt of Honor - Tom Clancy.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Debt of Honor/Debt of Honor - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Rainbow Six/Rainbow Six - Tom Clancy.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Rainbow Six/Rainbow Six - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Red Storm Rising/Red Storm Rising - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Red Storm Rising/Red Storm Rising - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Springboard/Springboard - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/Springboard/Springboard - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/State of War/State of War - Tom Clancy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Clancy/Clancy, Tom/State of War/State of War - Tom Clancy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/1789133807-[it-eb.com]/1789133807-[it-eb.com].epub": {"files": ["book.opf", "cover.xhtml"], "path": "/home/raelon/Projects/pyShelf/books/Books/1789133807-[it-eb.com]/1789133807-[it-eb.com].epub"}, "/home/raelon/Projects/pyShelf/books/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/Books/All Dune books + short stories + extras ePUB/Dune Chronicles (Dune 7)/Dune Chronicles 1 - Hunters of Dune.epub"}, "/home/raelon/Projects/pyShelf/books/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/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/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/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/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/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/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/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/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/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/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/Books/All Dune books + short stories + extras ePUB/Heroes of Dune/Heroes of Dune 1 - Paul of Dune.epub"}, "/home/raelon/Projects/pyShelf/books/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/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/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/Books/All Dune books + short stories + extras ePUB/Original Dune series/1 - Dune - Frank Herbert (1965).epub"}, "/home/raelon/Projects/pyShelf/books/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/Books/All Dune books + short stories + extras ePUB/Original Dune series/2 - Dune Messiah - Frank Herbert (1969).epub"}, "/home/raelon/Projects/pyShelf/books/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/Books/All Dune books + short stories + extras ePUB/Original Dune series/3 - Children of Dune - Frank Herbert (1976).epub"}, "/home/raelon/Projects/pyShelf/books/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/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/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/Books/All Dune books + short stories + extras ePUB/Original Dune series/5 - Heretics of Dune - Frank Herbert (1984).epub"}, "/home/raelon/Projects/pyShelf/books/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/Books/All Dune books + short stories + extras ePUB/Original Dune series/6 - Chapter House Dune - Frank Herbert (1985).epub"}, "/home/raelon/Projects/pyShelf/books/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/Books/All Dune books + short stories + extras ePUB/Prelude to Dune/Prelude to Dune 1 - House Atreides.epub"}, "/home/raelon/Projects/pyShelf/books/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/Books/All Dune books + short stories + extras ePUB/Prelude to Dune/Prelude to Dune 2 - House Harkonnen.epub"}, "/home/raelon/Projects/pyShelf/books/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/Books/All Dune books + short stories + extras ePUB/Prelude to Dune/Prelude to Dune 3 - House Corrino.epub"}, "/home/raelon/Projects/pyShelf/books/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/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/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/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/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/Books/All Dune books + short stories + extras ePUB/Short story collections and extras/Eye (Short stories) - Frank Herbert.epub"}, "/home/raelon/Projects/pyShelf/books/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/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/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/Books/All Dune books + short stories + extras ePUB/Short story collections and extras/The Road to Dune (Companion book) - Frank Herbert et al.epub"}, "/home/raelon/Projects/pyShelf/books/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/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/Books/Python Tricks by Dan Bader/Python Tricks by Dan Bader.epub": {"files": ["content.opf", "media/cover-6x9.png", "cover.xhtml"], "path": "/home/raelon/Projects/pyShelf/books/Books/Python Tricks by Dan Bader/Python Tricks by Dan Bader.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/1 - Before the Republic/002 - Dawn of the Jedi_Into the Void.epub": {"files": ["Lebb_9780345541949_epub_opf_r1.opf"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/1 - Before the Republic/002 - Dawn of the Jedi_Into the Void.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/003 - Lost Tribe of the Sith_The Collected Stories.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/003 - Lost Tribe of the Sith_The Collected Stories.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/004 - The Old Republic_Revan.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/004 - The Old Republic_Revan.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/005 - The Old Republic_Deceived.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/005 - The Old Republic_Deceived.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/006 - Red Harvest.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/006 - Red Harvest.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/007 - The Old Republic_Fatal Alliance.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/007 - The Old Republic_Fatal Alliance.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/008 - The Old Republic_Annihilation.epub": {"files": ["Karp_9780345535672_epub_opf_r1.opf"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/008 - The Old Republic_Annihilation.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/009 - Knight Errant.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/009 - Knight Errant.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/010 - Darth Bane_Path of Destruction.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/010 - Darth Bane_Path of Destruction.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/011 - Darth Bane_Rule of Two.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/011 - Darth Bane_Rule of Two.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/012 - Darth Bane_Dynasty of Evil.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/2 - Old Galactic Republic Era/012 - Darth Bane_Dynasty of Evil.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/013 - Legacy of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/013 - Legacy of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/014 - Darth Plagueis.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/014 - Darth Plagueis.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/015 - Jedi Apprentice_The Rising Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/015 - Jedi Apprentice_The Rising Force.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/016 - Jedi Apprentice_The Dark Rival.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/016 - Jedi Apprentice_The Dark Rival.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/017 - Jedi Apprentice_The Hidden Past.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/017 - Jedi Apprentice_The Hidden Past.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/018 - Jedi Apprentice_The Mark of the Crown.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/018 - Jedi Apprentice_The Mark of the Crown.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/019 - Jedi Apprentice_The Defenders of the Dead.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/019 - Jedi Apprentice_The Defenders of the Dead.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/020 - Jedi Apprentice_The Uncertain Path.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/020 - Jedi Apprentice_The Uncertain Path.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/021 - Jedi Apprentice_The Captive Temple.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/021 - Jedi Apprentice_The Captive Temple.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/022 - Jedi Apprentice_The Day of Reckoning.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/022 - Jedi Apprentice_The Day of Reckoning.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/023 - Jedi Apprentice_The Fight for Truth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/023 - Jedi Apprentice_The Fight for Truth.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/024 - Jedi Apprentice_The Shattered Peace.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/024 - Jedi Apprentice_The Shattered Peace.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/025 - Jedi Apprentice_Deceptions.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/025 - Jedi Apprentice_Deceptions.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/026 - The Life and Legend of Obi-Wan Kenobi.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/026 - The Life and Legend of Obi-Wan Kenobi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/027 - Jedi Apprentice_The Deadly Hunter.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/027 - Jedi Apprentice_The Deadly Hunter.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/039 - Cloak of Deception.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/039 - Cloak of Deception.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/040 - Maul_Lockdown.epub": {"files": ["Schr_9780345535665_epub_opf_r1.opf"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/040 - Maul_Lockdown.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/041 - Darth Maul_Shadow Hunter.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/041 - Darth Maul_Shadow Hunter.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/042 - Episode I_The Phantom Menace.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/042 - Episode I_The Phantom Menace.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/043 - Journal_Anakin Skywalker.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/043 - Journal_Anakin Skywalker.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/044 - Journal_Darth Maul.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/044 - Journal_Darth Maul.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/045 - Journal_Queen Amidala.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/045 - Journal_Queen Amidala.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/046 - Rogue Planet.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/046 - Rogue Planet.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/047 - Jedi Quest_Path to Truth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/047 - Jedi Quest_Path to Truth.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/048 - Jedi Quest_The Way of the Apprentice.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/048 - Jedi Quest_The Way of the Apprentice.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/049 - Outbound Flight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/049 - Outbound Flight.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/050 - Jedi Quest_The Trail of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/050 - Jedi Quest_The Trail of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/051 - Jedi Quest_Dangerous Games.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/051 - Jedi Quest_Dangerous Games.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/052 - Jedi Quest_The Master of Disguise.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/052 - Jedi Quest_The Master of Disguise.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/071 - Clone Wars Secret Missions_Guardians of the Chiss Key.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/071 - Clone Wars Secret Missions_Guardians of the Chiss Key.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/072 - The Clone Wars_Wild Space.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/072 - The Clone Wars_Wild Space.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/073 - Boba Fett_Hunted.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/073 - Boba Fett_Hunted.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/074 - Republic Commando_Hard Contact.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/074 - Republic Commando_Hard Contact.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/075 - Shatterpoint.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/075 - Shatterpoint.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/076 - The Clone Wars_No Prisoners.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/076 - The Clone Wars_No Prisoners.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/077 - Republic Commando_Triple Zero.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/077 - Republic Commando_Triple Zero.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/078 - Clone Wars Gambit_Stealth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/078 - Clone Wars Gambit_Stealth.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/079 - Clone Wars Gambit_Siege.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/079 - Clone Wars Gambit_Siege.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/080 - Republic Commando_True Colors.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/080 - Republic Commando_True Colors.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/081 - The Wrath of Darth Maul.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/081 - The Wrath of Darth Maul.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/082 - Medstar I_Battle Surgeons.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/082 - Medstar I_Battle Surgeons.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/083 - Medstar II_Jedi Healer.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/083 - Medstar II_Jedi Healer.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/084 - Yoda Dark Rendezvous.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/084 - Yoda Dark Rendezvous.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/085 - Boba Fett_A New Threat.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/085 - Boba Fett_A New Threat.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/086 - Boba Fett_Pursuit.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/086 - Boba Fett_Pursuit.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/087 - Episode III_Labyrinth of Evil.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/087 - Episode III_Labyrinth of Evil.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/088 - Episode III_Revenge of the Sith.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/088 - Episode III_Revenge of the Sith.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/089 - Republic Commando_Order 066.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/089 - Republic Commando_Order 066.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/090 - Kenobi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/090 - Kenobi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/091 - Episode III_Dark Lord The Rise of Darth Vader.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/091 - Episode III_Dark Lord The Rise of Darth Vader.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/092 - Republic Commando_501st.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/092 - Republic Commando_501st.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/028 - Jedi Apprentice_The Evil Experiment.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/028 - Jedi Apprentice_The Evil Experiment.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/038 - Darth Maul_Saboteur.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/038 - Darth Maul_Saboteur.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/053 - Jedi Quest_The School of Fear.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/053 - Jedi Quest_The School of Fear.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/070 - Clone Wars Secret Missions_Duel at Shattered Rock.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/070 - Clone Wars Secret Missions_Duel at Shattered Rock.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/093 - Coruscant Nights_Jedi Twilight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/093 - Coruscant Nights_Jedi Twilight.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/107 - A New Hope_The Life of Luke Skywalker.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/107 - A New Hope_The Life of Luke Skywalker.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/094 - The Last of the Jedi_The Desperate Mission.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/094 - The Last of the Jedi_The Desperate Mission.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/095 - The Last of the Jedi_Dark Warning.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/095 - The Last of the Jedi_Dark Warning.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/096 - The Last of the Jedi_Underworld.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/096 - The Last of the Jedi_Underworld.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/097 - The Last of the Jedi_Death on Naboo.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/097 - The Last of the Jedi_Death on Naboo.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/098 - Coruscant Nights_Street of Shadows.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/098 - Coruscant Nights_Street of Shadows.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/099 - The Last of the Jedi_A Tangled Web.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/099 - The Last of the Jedi_A Tangled Web.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/100 - The Last of the Jedi_Return of the Dark Side.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/100 - The Last of the Jedi_Return of the Dark Side.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/101 - The Last of the Jedi_Secret Weapon.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/101 - The Last of the Jedi_Secret Weapon.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/102 - The Last of the Jedi_Against the Empire.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/102 - The Last of the Jedi_Against the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/103 - The Last of the Jedi_Master of Deception.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/103 - The Last of the Jedi_Master of Deception.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/104 - The Last of the Jedi_Reckoning.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/104 - The Last of the Jedi_Reckoning.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/105 - Coruscant Nights_Patterns of Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/105 - Coruscant Nights_Patterns of Force.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/106 - The Last Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/106 - The Last Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/054 - Jedi Quest_The Shadow Trap.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/054 - Jedi Quest_The Shadow Trap.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/055 - Jedi Quest_The Moment of Truth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/055 - Jedi Quest_The Moment of Truth.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/056 - Jedi Quest_The Changing of the Guard.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/056 - Jedi Quest_The Changing of the Guard.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/057 - Jedi Quest_The False Peace.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/057 - Jedi Quest_The False Peace.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/058 - Jedi Quest_The Final Showdown.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/058 - Jedi Quest_The Final Showdown.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/059 - The Approaching Storm.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/059 - The Approaching Storm.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/060 - Episode II_Attack of the Clones.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/060 - Episode II_Attack of the Clones.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/061 - Boba Fett_The Fight to Survive.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/061 - Boba Fett_The Fight to Survive.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/062 - Boba Fett_Crossfire.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/062 - Boba Fett_Crossfire.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/063 - Boba Fett_Maze of Deception.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/063 - Boba Fett_Maze of Deception.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/064 - The Cestus Deception.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/064 - The Cestus Deception.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/065 - The Hive.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/065 - The Hive.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/066 - Jedi Trial.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/066 - Jedi Trial.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/067 - The Clone Wars.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/067 - The Clone Wars.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/068 - Clone Wars Secret Missions_Breakout Squad.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/068 - Clone Wars Secret Missions_Breakout Squad.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/069 - Clone Wars Secret Missions_Curse of the Black Hole Pirates.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/069 - Clone Wars Secret Missions_Curse of the Black Hole Pirates.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/108 - Han Solo_The Paradise Snare.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/108 - Han Solo_The Paradise Snare.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/109 - Han Solo_The Hutt Gambit.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/109 - Han Solo_The Hutt Gambit.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/110 - The Force Unleashed.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/110 - The Force Unleashed.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/111 - The Adventures of Lando Calrissian.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/111 - The Adventures of Lando Calrissian.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/112 - Death Star.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/112 - Death Star.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/113 - The Han Solo Adventures.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/113 - The Han Solo Adventures.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/114 - Han Solo_Rebel Dawn.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/114 - Han Solo_Rebel Dawn.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/115 - The Force Unleashed II.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/115 - The Force Unleashed II.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/116 - Dark Forces_Soldier for the Empire.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/116 - Dark Forces_Soldier for the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/117 - Dark Forces_Rebel Agent.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/117 - Dark Forces_Rebel Agent.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/118 - Dark Forces_Jedi Knight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/118 - Dark Forces_Jedi Knight.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/119 - Death Troopers.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/119 - Death Troopers.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/029 - Jedi Apprentice_The Dangerous Rescue.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/029 - Jedi Apprentice_The Dangerous Rescue.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/030 - Jedi Apprentice_The Ties That Bind.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/030 - Jedi Apprentice_The Ties That Bind.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/031 - Jedi Apprentice_The Death of Hope.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/031 - Jedi Apprentice_The Death of Hope.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/032 - Jedi Apprentice_The Call to Vengeance.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/032 - Jedi Apprentice_The Call to Vengeance.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/033 - Jedi Apprentice_The Only Witness.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/033 - Jedi Apprentice_The Only Witness.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/034 - Jedi Apprentice_The Threat Within.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/034 - Jedi Apprentice_The Threat Within.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/035 - Jedi Apprentice_The Followers.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/035 - Jedi Apprentice_The Followers.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/036 - Secrets of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/036 - Secrets of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/037 - The Rise and Fall of Darth Vader.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/3 - Rise of the Empire Era/037 - The Rise and Fall of Darth Vader.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/139 - The Swarm.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/139 - The Swarm.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/120 - Shadow Games.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/120 - Shadow Games.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/121 - Episode IV_A New Hope.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/121 - Episode IV_A New Hope.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/122 - Tales From Mos Eisley Cantina.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/122 - Tales From Mos Eisley Cantina.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/123 - Scoundrels.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/123 - Scoundrels.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/124 - Rebel Force_Target.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/124 - Rebel Force_Target.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/125 - Rebel Force_Hostage.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/125 - Rebel Force_Hostage.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/126 - Rebel Force_Renegade.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/126 - Rebel Force_Renegade.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/127 - Rebel Force_Firefight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/127 - Rebel Force_Firefight.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/128 - Rebel Force_Trapped.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/128 - Rebel Force_Trapped.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/129 - Allegiance.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/129 - Allegiance.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/130 - Rebel Force_Uprising.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/130 - Rebel Force_Uprising.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/131 - Eaten Alive.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/131 - Eaten Alive.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/132 - City of the Dead.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/132 - City of the Dead.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/133 - Planet Plague.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/133 - Planet Plague.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/134 - The Nightmare Machine.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/134 - The Nightmare Machine.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/135 - Ghost of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/135 - Ghost of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/136 - Army of Terror.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/136 - Army of Terror.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/137 - Choices of One.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/137 - Choices of One.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/138 - The Brain Spiders.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/138 - The Brain Spiders.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/140 - Spore.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/140 - Spore.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/141 - The Doomsday Ship.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/141 - The Doomsday Ship.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/142 - Clones.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/142 - Clones.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/143 - The Hunger.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/143 - The Hunger.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/144 - Honor Among Thieves.epub": {"files": ["OEBPS/content.opf", "OEBPS/Text/cover.xhtml", "OEBPS/Images/cover.jpg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/144 - Honor Among Thieves.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/145 - Galaxies_The Ruins of Dantooine.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/145 - Galaxies_The Ruins of Dantooine.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/146 - Splinter of the Mind's Eye.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/146 - Splinter of the Mind's Eye.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/147 - Empire and Rebellion_Razor's Edge.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/147 - Empire and Rebellion_Razor's Edge.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/148 - Episode V_The Empire Strikes Back.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/148 - Episode V_The Empire Strikes Back.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/149 - Tales of the Bounty Hunters.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/149 - Tales of the Bounty Hunters.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/150 - Shadows of the Empire.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/150 - Shadows of the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/151 - Tales From the Empire.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/151 - Tales From the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/152 - Tales From Jabba's Palace.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/152 - Tales From Jabba's Palace.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/153 - Episode VI_Return of the Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/153 - Episode VI_Return of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/154 - The Bounty Hunter Wars_The Mandalorian Armor.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/154 - The Bounty Hunter Wars_The Mandalorian Armor.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/155 - The Bounty Hunter Wars_Slave Ship.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/155 - The Bounty Hunter Wars_Slave Ship.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/156 - The Truce at Bakura.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/156 - The Truce at Bakura.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/157 - The Bounty Hunter Wars_Hard Merchandise.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/4 - Rebellion Era/157 - The Bounty Hunter Wars_Hard Merchandise.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/158 - Tales From the New Republic.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/158 - Tales From the New Republic.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/159 - Luke Skywalker and the Shadows of Mindor.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/159 - Luke Skywalker and the Shadows of Mindor.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/160 - Jedi Prince_The Glove of Darth Vader.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/160 - Jedi Prince_The Glove of Darth Vader.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/161 - Jedi Prince_The Lost City of the Jedi.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/161 - Jedi Prince_The Lost City of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/162 - Jedi Prince_Zorba the Hutt's Revenge.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/162 - Jedi Prince_Zorba the Hutt's Revenge.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/163 - Jedi Prince_Mission From Mount Yoda.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/163 - Jedi Prince_Mission From Mount Yoda.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/164 - Jedi Prince_Queen of the Empire.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/164 - Jedi Prince_Queen of the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/165 - Jedi Prince_Prophets of the Dark Side.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/165 - Jedi Prince_Prophets of the Dark Side.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/166 - Dark Forces_Rebel Agent.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/166 - Dark Forces_Rebel Agent.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/167 - Dark Forces_ Jedi Knight.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/167 - Dark Forces_ Jedi Knight.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/168 - X-Wing_Rogue Squadron.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/168 - X-Wing_Rogue Squadron.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/169 - X-Wing_Wedge's Gamble.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/169 - X-Wing_Wedge's Gamble.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/170 - X-Wing_The Krytos Trap.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/170 - X-Wing_The Krytos Trap.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/171 - X-Wing_The Bacta War.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/171 - X-Wing_The Bacta War.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/172 - X-Wing_Wraith Squadron.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/172 - X-Wing_Wraith Squadron.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/173 - X-Wing_Iron Fist.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/173 - X-Wing_Iron Fist.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/174 - X-Wing_Solo Command.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/174 - X-Wing_Solo Command.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/175 - The Courtship of Princess Leia.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/175 - The Courtship of Princess Leia.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/177 - Tatooine Ghost.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/177 - Tatooine Ghost.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/178 - Thrawn_Heir to the Empire.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/178 - Thrawn_Heir to the Empire.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/179 - Thrawn_Dark Force Rising.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/179 - Thrawn_Dark Force Rising.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/180 - Thrawn_The Last Command.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/180 - Thrawn_The Last Command.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/181 - X-Wing_Isard's Revenge.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/181 - X-Wing_Isard's Revenge.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/182 - The Jedi Academy_Jedi Search.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/182 - The Jedi Academy_Jedi Search.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/183 - The Jedi Academy_Dark Apprentice.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/183 - The Jedi Academy_Dark Apprentice.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/184 - The Jedi Academy_Champions of the Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/184 - The Jedi Academy_Champions of the Force.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/185 - I, Jedi.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/185 - I, Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/186 - Children of the Jedi.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/186 - Children of the Jedi.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/187 - Darksaber.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/187 - Darksaber.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/188 - X-Wing_Starfighters of Adumar.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/188 - X-Wing_Starfighters of Adumar.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/189 - Planet of Twilight.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/189 - Planet of Twilight.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/191 - The Black Fleet Crisis_Before the Storm.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/191 - The Black Fleet Crisis_Before the Storm.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/192 - The Black Fleet Crisis_Shield of Lies.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/192 - The Black Fleet Crisis_Shield of Lies.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/193 - The Black Fleet Crisis_Tyrant's Test.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/193 - The Black Fleet Crisis_Tyrant's Test.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/194 - The New Rebellion.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/194 - The New Rebellion.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/195 - The Corellian_Ambush at Corellia.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/195 - The Corellian_Ambush at Corellia.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/196 - The Corellian_Assault at Selonia.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/196 - The Corellian_Assault at Selonia.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/197 - The Corellian_Showdown at Centerpoint.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/197 - The Corellian_Showdown at Centerpoint.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/198 - The Hand of Thrawn_Specter of the Past.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/198 - The Hand of Thrawn_Specter of the Past.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/199 - The Hand of Thrawn_Vision of the Future.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/199 - The Hand of Thrawn_Vision of the Future.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/200 - Scourge.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/200 - Scourge.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/201 - Junior Jedi Knights_The Golden Globe.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/201 - Junior Jedi Knights_The Golden Globe.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/202 - Junior Jedi Knights_Lyric's World.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/202 - Junior Jedi Knights_Lyric's World.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/203 - Junior Jedi Knights_Promises.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/203 - Junior Jedi Knights_Promises.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/176 - A Forest Apart.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/176 - A Forest Apart.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/190 - The Crystal Star.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/190 - The Crystal Star.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/204 - Junior Jedi Knights_Anakin's Quest.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/204 - Junior Jedi Knights_Anakin's Quest.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/205 - Junior Jedi Knights_Vader's Fortress.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/205 - Junior Jedi Knights_Vader's Fortress.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/206 - Junior Jedi Knights_Kenobi's Blade.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/206 - Junior Jedi Knights_Kenobi's Blade.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/207 - Fool's Bargain.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/207 - Fool's Bargain.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/208 - Survivor's Quest.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/208 - Survivor's Quest.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/209 - Young Jedi Knights_Heirs of the Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/209 - Young Jedi Knights_Heirs of the Force.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/210 - Young Jedi Knights_Shadow Academy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/210 - Young Jedi Knights_Shadow Academy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/211 - Young Jedi Knights_The Lost Ones.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/211 - Young Jedi Knights_The Lost Ones.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/212 - Young Jedi Knights_Lightsabers.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/212 - Young Jedi Knights_Lightsabers.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/213 - Young Jedi Knights_Darkest Knight.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/213 - Young Jedi Knights_Darkest Knight.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/214 - Young Jedi Knights_Jedi Under Siege.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/214 - Young Jedi Knights_Jedi Under Siege.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/215 - Young Jedi Knights_Shards of Alderaann.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/215 - Young Jedi Knights_Shards of Alderaann.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/216 - Young Jedi Knights_Diversity Alliance.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/216 - Young Jedi Knights_Diversity Alliance.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/217 - Young Jedi Knights_Delusions of Grandeur.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/217 - Young Jedi Knights_Delusions of Grandeur.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/218 - Young Jedi Knights_Jedi Bounty.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/218 - Young Jedi Knights_Jedi Bounty.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/219 - Young Jedi Knights_The Emperor's Plague.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/219 - Young Jedi Knights_The Emperor's Plague.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/220 - Young Jedi Knights_Return to Ord Mantell.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/220 - Young Jedi Knights_Return to Ord Mantell.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/221 - Young Jedi Knights_Trouble on Cloud City.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/221 - Young Jedi Knights_Trouble on Cloud City.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/222 - Young Jedi Knights_Crisis at Crystal Reef.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/5 - New Republic Era/222 - Young Jedi Knights_Crisis at Crystal Reef.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/237 - The New Jedi Order_Traitor.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/237 - The New Jedi Order_Traitor.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/223 - Boba Fett_A Practical Man.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/223 - Boba Fett_A Practical Man.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/224 - The New Jedi Order_Vector Prime.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/224 - The New Jedi Order_Vector Prime.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/225 - The New Jedi Order_Dark Tide I - Onslaught.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/225 - The New Jedi Order_Dark Tide I - Onslaught.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/226 - The New Jedi Order_Dark Tide II - Ruin.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/226 - The New Jedi Order_Dark Tide II - Ruin.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/227 - The New Jedi Order_Agents of Chaos I - Hero's Trial.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/227 - The New Jedi Order_Agents of Chaos I - Hero's Trial.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/228 - The New Jedi Order_Agents of Chaos II - Jedi Eclipse.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/228 - The New Jedi Order_Agents of Chaos II - Jedi Eclipse.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/229 - The New Jedi Order_Balance Point.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/229 - The New Jedi Order_Balance Point.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/230 - The New Jedi Order_Recovery.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/230 - The New Jedi Order_Recovery.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/231 - The New Jedi Order_Edge of Victory I - Conquest.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/231 - The New Jedi Order_Edge of Victory I - Conquest.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/232 - The New Jedi Order_Edge of Victory II - Rebirth.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/232 - The New Jedi Order_Edge of Victory II - Rebirth.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/233 - The New Jedi Order_Star by Star.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/233 - The New Jedi Order_Star by Star.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/234 - The New Jedi Order_Dark Journey.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/234 - The New Jedi Order_Dark Journey.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/235 - The New Jedi Order_Enemy Lines I - Rebel Dream.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/235 - The New Jedi Order_Enemy Lines I - Rebel Dream.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/236 - The New Jedi Order_Enemy Lines II - Rebel Stand.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/236 - The New Jedi Order_Enemy Lines II - Rebel Stand.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/238 - The New Jedi Order_Destiny's Way.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/238 - The New Jedi Order_Destiny's Way.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/239 - The New Jedi Order_Ylesia.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/239 - The New Jedi Order_Ylesia.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/240 - The New Jedi Order_Force Heretic I - Remnant.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/240 - The New Jedi Order_Force Heretic I - Remnant.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/241 - The New Jedi Order_Force Heretic II - Refugee.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/241 - The New Jedi Order_Force Heretic II - Refugee.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/242 - The New Jedi Order_Force Heretic III - Reunion.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/242 - The New Jedi Order_Force Heretic III - Reunion.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/243 - The New Jedi Order_The Final Prophecy.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/243 - The New Jedi Order_The Final Prophecy.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/244 - The New Jedi Order_The Unifying Force.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/244 - The New Jedi Order_The Unifying Force.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/245 - Dark Nest_The Joiner King.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/245 - Dark Nest_The Joiner King.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/246 - Dark Nest_The Unseen Queen.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/246 - Dark Nest_The Unseen Queen.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/247 - Dark Nest_The Swarm War.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/6 - New Jedi Order Era/247 - Dark Nest_The Swarm War.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/248 - Legacy of the Force_Betrayal.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/248 - Legacy of the Force_Betrayal.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/249- Legacy of the Force_Bloodlines.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/249- Legacy of the Force_Bloodlines.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/250 - Legacy of the Force_Tempest.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/250 - Legacy of the Force_Tempest.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/251 - Legacy of the Force_Exile.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/251 - Legacy of the Force_Exile.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/252 - Legacy of the Force_Sacrifice.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/252 - Legacy of the Force_Sacrifice.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/253 - Legacy of the Force_Inferno.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/253 - Legacy of the Force_Inferno.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/254 - Legacy of the Force_Fury.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/254 - Legacy of the Force_Fury.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/255 - Legacy of the Force_Revelation.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/255 - Legacy of the Force_Revelation.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/256 - Legacy of the Force_Invincible.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/256 - Legacy of the Force_Invincible.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/257 - Crosscurrent.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/257 - Crosscurrent.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/258 - Riptide.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/258 - Riptide.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/259 - Millennium Falcon.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/259 - Millennium Falcon.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/260 - Fate of the Jedi_Outcast.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/260 - Fate of the Jedi_Outcast.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/261 - Fate of the Jedi_Omen.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/261 - Fate of the Jedi_Omen.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/262 - Fate of the Jedi_Abyss.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/262 - Fate of the Jedi_Abyss.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/263 - Fate of the Jedi_Backlash.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/263 - Fate of the Jedi_Backlash.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/264 - Fate of the Jedi_Allies.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/264 - Fate of the Jedi_Allies.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/265 - Fate of the Jedi_Vortex.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/265 - Fate of the Jedi_Vortex.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/266 - Fate of the Jedi_Conviction.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/266 - Fate of the Jedi_Conviction.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/267 - Fate of the Jedi_Ascension.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/267 - Fate of the Jedi_Ascension.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/268 - Fate of the Jedi_Apocalypse.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/268 - Fate of the Jedi_Apocalypse.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/269 - X-Wing_Mercy Kill.epub": {"files": ["content.opf", "cover1.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/269 - X-Wing_Mercy Kill.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/270 - Crucible.epub": {"files": ["content.opf", "cover.jpeg"], "path": "/home/raelon/Projects/pyShelf/books/Books/Star Wars/7 - Legacy Era/270 - Crucible.epub"}, "/home/raelon/Projects/pyShelf/books/Books/Ubuntu Server Essentials - 6685 [ECLiPSE]/Ubuntu Server Essentials.epub": {"files": ["OEBPS/cover/cover.jpg", "OEBPS/cover.html", "OEBPS/content.opf"], "path": "/home/raelon/Projects/pyShelf/books/Books/Ubuntu Server Essentials - 6685 [ECLiPSE]/Ubuntu Server Essentials.epub"}}
diff --git a/src/backend/empty_bookshelf.sql b/src/backend/empty_bookshelf.sql
new file mode 100644
index 0000000..baad193
--- /dev/null
+++ b/src/backend/empty_bookshelf.sql
@@ -0,0 +1,2 @@
+delete from books;
+delete from sqlite_sequence where name='books';
diff --git a/frontend/interface/__init__.py b/src/backend/lib/__init__.py
old mode 100644
new mode 100755
similarity index 100%
rename from frontend/interface/__init__.py
rename to src/backend/lib/__init__.py
diff --git a/app/lib/api_hooks.py b/src/backend/lib/api_hooks.py
old mode 100644
new mode 100755
similarity index 56%
rename from app/lib/api_hooks.py
rename to src/backend/lib/api_hooks.py
index 239f88a..54fc80f
--- a/app/lib/api_hooks.py
+++ b/src/backend/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/src/backend/lib/config.py b/src/backend/lib/config.py
new file mode 100755
index 0000000..9372742
--- /dev/null
+++ b/src/backend/lib/config.py
@@ -0,0 +1,34 @@
+import json
+import os
+import pathlib
+import sys
+
+
+class Config:
+ """
+ Main System Configuration
+ """
+
+ _fp = "config.json"
+
+ def __init__(self, root):
+ _cp = pathlib.Path.joinpath(root, self._fp)
+ _data = self.open_file(_cp)
+ 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.catalogue_db = "data/catalogue.db"
+ self.catalogue_db = str(root) + "/" + _data["DATABASE"]
+ self.file_array = [
+ self.book_shelf,
+ self.catalogue_db,
+ ]
+ self.root = root
+ self.auto_scan = True
+
+ def open_file(self, _cp):
+ with open(str(_cp), "r") as read_file:
+ data = json.load(read_file)
+ return data
diff --git a/src/backend/lib/library.py b/src/backend/lib/library.py
new file mode 100755
index 0000000..449cd1e
--- /dev/null
+++ b/src/backend/lib/library.py
@@ -0,0 +1,155 @@
+#!/usr/bin/python
+import json
+import os
+import pathlib
+import re
+import zipfile
+
+from bs4 import BeautifulSoup
+
+from .api_hooks import DuckDuckGo
+from .config import Config
+from .storage import Storage
+
+# config = Config()
+
+
+class Catalogue:
+ """Decodes and stores book information"""
+
+ """Step One: filter_books"""
+
+ def __init__(self, config):
+ 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.root_dir = config.root
+ self.book_folder = config.book_path
+ self.book_shelf = config.book_shelf
+ self._book_list_expanded = None
+ self.books = None
+ self.db_pointer = config.catalogue_db
+
+ def scan_folder(self, _path=None):
+ if _path is not None:
+ folder = _path
+ elif os.path.isdir(str(self.root_dir) + "/" + self.book_folder):
+ folder = str(self.root_dir) + "/" + self.book_folder
+ else:
+ folder = self.book_folder
+ for f in os.listdir(folder):
+ _path = os.path.abspath(folder + "/" + f)
+ if os.path.isdir(_path.strip() + "/"):
+ self.file_list.append(self.scan_folder(_path))
+ else:
+ self.file_list.append(_path)
+
+ 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")
+ try:
+ self.books = list(filter(regx.search, filter(None, self.file_list)))
+ except TypeError as e:
+ print(e)
+ self._book_list_expanded = {}
+ 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)
+ return self._book_list_expanded
+
+ @staticmethod
+ def process_book(book):
+ """Return dictionary of epub file contents"""
+ 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 is None:
+ 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)
+ 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(self.db_pointer)
+ stored = db.book_paths_list()
+ closed = db.close()
+ 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])
+ 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(self.db_pointer)
+ 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/src/backend/lib/pyShelf.py b/src/backend/lib/pyShelf.py
new file mode 100755
index 0000000..3e41029
--- /dev/null
+++ b/src/backend/lib/pyShelf.py
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+import os
+import time
+
+from .config import Config
+from .storage import Storage
+
+# config = Config()
+# Storage = Storage()
+
+
+class InitFiles:
+ """First run file creation operations"""
+
+ def __init__(self, file_array):
+ print("Checking for program files")
+ for _pointer in file_array:
+ time.sleep(1)
+ if not os.path.isfile(_pointer):
+ self.CreateFile(_pointer)
+ print("%s created" % _pointer)
+ else:
+ print("%s present" % _pointer)
+ time.sleep(1)
+ print("File check complete.")
+
+ 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 BookDisplay:
+ """All functions related to displaying book information in the HTML UI"""
+
+ def __init__(self, **kwargs):
+ """
+ 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
+ try:
+ self.screen_size = kwargs["screen_size"]
+ except Exception:
+ self.screen_size = [900, 600]
+
+ 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(self.screen_size[0] // x) * int(
+ self.screen_size[1] // y
+ )
diff --git a/app/lib/storage.py b/src/backend/lib/storage.py
old mode 100644
new mode 100755
similarity index 52%
rename from app/lib/storage.py
rename to src/backend/lib/storage.py
index 196cc95..f652865
--- a/app/lib/storage.py
+++ b/src/backend/lib/storage.py
@@ -3,18 +3,21 @@ import sqlite3
import sys
# sys.path.insert(1, '../')
-from config import Config
+from .config import Config
-db_pointer = Config().catalogue_db
+# db_pointer = Config().catalogue_db
class Storage:
"""Contains all methods for system storage"""
- def __init__(self):
+ def __init__(self, db_pointer):
+ # Optionaly pass db_file to specify another db or for testing
+ if db_pointer is None:
+ db_pointer = Config().catalogue_db
self.db_file = db_pointer
self.database()
- self.create_tables()
+ # self.create_tables()
def database(self):
"""Create database cursor"""
@@ -23,14 +26,16 @@ class Storage:
self.cursor = self.db.cursor()
return True
except Exception as e:
+ print(self.db_file)
+ print(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)'''
+ q_create = """CREATE TABLE books(title text, author text,
+ categories text null, cover blob null, pages int null, progress int null,
+ file_name text)"""
try:
self.cursor.execute(q_check)
except sqlite3.OperationalError as e:
@@ -41,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, file_name) values (?, ?, ?, ?)'''
+ 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
@@ -58,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/src/backend/pyShelf_ScanLibrary.py b/src/backend/pyShelf_ScanLibrary.py
new file mode 100755
index 0000000..656626e
--- /dev/null
+++ b/src/backend/pyShelf_ScanLibrary.py
@@ -0,0 +1,22 @@
+#!/usr/bin/python
+import os
+import sys
+import time
+
+from .lib.config import Config
+from .lib.library import Catalogue
+from .lib.pyShelf import InitFiles
+
+sys.path.append(os.path.abspath("."))
+
+
+def execute_scan(root):
+ _t1 = time.time()
+ config = Config(root) # Get configuration settings
+ InitFiles(config.file_array) # Initialize file system
+ catalogue = Catalogue(config) # Open the Catalogue
+ catalogue.import_books()
+ _t2 = time.time()
+ scan_time = round(_t2 - _t1)
+ print("Scan Completed.")
+ print("Scan Time %s seconds" % scan_time)
diff --git a/app/pyproject.toml b/src/backend/pyproject.toml
old mode 100644
new mode 100755
similarity index 100%
rename from app/pyproject.toml
rename to src/backend/pyproject.toml
diff --git a/frontend/interface/migrations/__init__.py b/src/backend/tests/__init__.py
old mode 100644
new mode 100755
similarity index 100%
rename from frontend/interface/migrations/__init__.py
rename to src/backend/tests/__init__.py
diff --git a/src/backend/tests/config_test.py b/src/backend/tests/config_test.py
new file mode 100755
index 0000000..4d67b2f
--- /dev/null
+++ b/src/backend/tests/config_test.py
@@ -0,0 +1,16 @@
+import os
+
+from ..lib.config import Config
+
+
+class TestConfig:
+ config = Config(os.path.abspath(os.path.curdir))
+
+ def test_book_dir(self):
+ assert os.path.isdir(self.config.book_path)
+
+ def test_title(self):
+ assert "pyShelf" in self.config.TITLE
+
+ def test_version(self):
+ assert self.config.VERSION is not None
diff --git a/src/backend/tests/library_test.py b/src/backend/tests/library_test.py
new file mode 100755
index 0000000..e2d98a8
--- /dev/null
+++ b/src/backend/tests/library_test.py
@@ -0,0 +1,34 @@
+import json
+import os
+
+from ..lib.config import Config
+from ..lib.library import Catalogue
+
+
+class Test_Config(Config):
+ def __init__(self):
+ Config.__init__(self, "config.json")
+ _data = self.open_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("."))
+
+ def filter_books(self):
+ self.book_shelf = "app/" + self.book_shelf
+ return super().filter_books()
+
+
+class TestCatalogue:
+ root = os.path.abspath(os.path.curdir)
+ config = Test_Config()
+
+ def test_filter_books(self):
+ book_list = Test_Catalogue().filter_books()
+ assert len(book_list) > 0
diff --git a/src/data/shelf.json b/src/data/shelf.json
new file mode 100644
index 0000000..e69de29
diff --git a/src/db.sqlite3 b/src/db.sqlite3
new file mode 100755
index 0000000..5818d2b
Binary files /dev/null and b/src/db.sqlite3 differ
diff --git a/src/frontend/__init__.py b/src/frontend/__init__.py
new file mode 100755
index 0000000..e69de29
diff --git a/src/frontend/settings.py b/src/frontend/settings.py
new file mode 100755
index 0000000..8747a60
--- /dev/null
+++ b/src/frontend/settings.py
@@ -0,0 +1,125 @@
+"""
+Django settings for frontend project.
+
+Generated by 'django-admin startproject' using Django 2.2.7.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/2.2/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# 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"
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = False
+
+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",
+ "debug_toolbar",
+]
+
+MIDDLEWARE = [
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "debug_toolbar.middleware.DebugToolbarMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
+]
+
+INTERNAL_IPS = [
+ # ...
+ "127.0.0.1",
+ # ...
+]
+
+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",
+ ],
+ },
+ },
+]
+
+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"),
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
+
+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",},
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/2.2/topics/i18n/
+
+LANGUAGE_CODE = "en-us"
+
+TIME_ZONE = "UTC"
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/2.2/howto/static-files/
+
+STATIC_URL = "/static/"
+STATIC_ROOT = os.path.join(BASE_DIR, "interface/static/")
diff --git a/frontend/frontend/urls.py b/src/frontend/urls.py
old mode 100644
new mode 100755
similarity index 58%
rename from frontend/frontend/urls.py
rename to src/frontend/urls.py
index 3de8e64..924328a
--- a/frontend/frontend/urls.py
+++ b/src/frontend/urls.py
@@ -13,11 +13,22 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
+from django.conf import settings
from django.contrib import admin
-from django.urls import path
+from django.urls import include, path, re_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"),
+ # path('', views.download, name="download")
+ path("", views.download, name="download"),
]
+if settings.DEBUG:
+ import debug_toolbar
+
+ urlpatterns = [
+ path("__debug__/", include(debug_toolbar.urls)),
+ # For django versions before 2.0:
+ # url(r'^__debug__/', include(debug_toolbar.urls)),
+ ] + urlpatterns
diff --git a/frontend/frontend/wsgi.py b/src/frontend/wsgi.py
old mode 100644
new mode 100755
similarity index 82%
rename from frontend/frontend/wsgi.py
rename to src/frontend/wsgi.py
index ad6c6b9..2ab6822
--- a/frontend/frontend/wsgi.py
+++ b/src/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/src/interface/__init__.py b/src/interface/__init__.py
new file mode 100755
index 0000000..e69de29
diff --git a/frontend/interface/admin.py b/src/interface/admin.py
old mode 100644
new mode 100755
similarity index 53%
rename from frontend/interface/admin.py
rename to src/interface/admin.py
index 8c38f3f..409487d
--- a/frontend/interface/admin.py
+++ b/src/interface/admin.py
@@ -1,3 +1,6 @@
from django.contrib import admin
+from .models import Books
+
# Register your models here.
+admin.site.register(Books)
diff --git a/frontend/interface/apps.py b/src/interface/apps.py
old mode 100644
new mode 100755
similarity index 75%
rename from frontend/interface/apps.py
rename to src/interface/apps.py
index 7810300..0c7d2c4
--- a/frontend/interface/apps.py
+++ b/src/interface/apps.py
@@ -2,4 +2,4 @@ from django.apps import AppConfig
class InterfaceConfig(AppConfig):
- name = 'interface'
+ name = "interface"
diff --git a/src/interface/migrations/__init__.py b/src/interface/migrations/__init__.py
new file mode 100755
index 0000000..e69de29
diff --git a/src/interface/models.py b/src/interface/models.py
new file mode 100755
index 0000000..b2f5951
--- /dev/null
+++ b/src/interface/models.py
@@ -0,0 +1,34 @@
+from django.db import models
+
+# Create your models here.
+
+
+class Books(models.Model):
+ """
+ pyShelfs Book Database class
+ :param title: Book title
+ :param author: Author
+ :param categories: Categories <-- Not implemented
+ :param cover: Cover image BinaryField
+ :param pages: # of pages <-- Not implemented
+ :param progress: Reader percentage <-- Not implented
+ :param file_name: Path to book
+ """
+
+ class Meta:
+ db_table = "books"
+
+ def __str__(self):
+ return self.title
+
+ title = models.CharField(max_length=255)
+ author = models.CharField(max_length=255, null=True)
+ categories = models.CharField(max_length=255, null=True)
+ cover = models.BinaryField(null=True, editable=True)
+ pages = models.IntegerField(null=True)
+ progress = models.IntegerField(null=True)
+ file_name = models.CharField(max_length=255, null=False)
+
+ def get_absolute_url(self):
+ """Returns the url to access a particular instance of MyModelName."""
+ return reverse("model-detail-view", args=[str(self.id)])
diff --git a/src/interface/static/admin/css/autocomplete.css b/src/interface/static/admin/css/autocomplete.css
new file mode 100644
index 0000000..3ef95d1
--- /dev/null
+++ b/src/interface/static/admin/css/autocomplete.css
@@ -0,0 +1,260 @@
+select.admin-autocomplete {
+ width: 20em;
+}
+
+.select2-container--admin-autocomplete.select2-container {
+ min-height: 30px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single,
+.select2-container--admin-autocomplete .select2-selection--multiple {
+ min-height: 30px;
+ padding: 0;
+}
+
+.select2-container--admin-autocomplete.select2-container--focus .select2-selection,
+.select2-container--admin-autocomplete.select2-container--open .select2-selection {
+ border-color: #999;
+ min-height: 30px;
+}
+
+.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--single,
+.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--single {
+ padding: 0;
+}
+
+.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--multiple,
+.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--multiple {
+ padding: 0;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single {
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
+ color: #444;
+ line-height: 30px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__clear {
+ cursor: pointer;
+ float: right;
+ font-weight: bold;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
+ color: #999;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
+ height: 26px;
+ position: absolute;
+ top: 1px;
+ right: 1px;
+ width: 20px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow b {
+ border-color: #888 transparent transparent transparent;
+ border-style: solid;
+ border-width: 5px 4px 0 4px;
+ height: 0;
+ left: 50%;
+ margin-left: -4px;
+ margin-top: -2px;
+ position: absolute;
+ top: 50%;
+ width: 0;
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__clear {
+ float: left;
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+ left: 1px;
+ right: auto;
+}
+
+.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
+ background-color: #eee;
+ cursor: default;
+}
+
+.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single .select2-selection__clear {
+ display: none;
+}
+
+.select2-container--admin-autocomplete.select2-container--open .select2-selection--single .select2-selection__arrow b {
+ border-color: transparent transparent #888 transparent;
+ border-width: 0 4px 5px 4px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple {
+ background-color: white;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ cursor: text;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered {
+ box-sizing: border-box;
+ list-style: none;
+ margin: 0;
+ padding: 0 5px;
+ width: 100%;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
+ list-style: none;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
+ color: #999;
+ margin-top: 5px;
+ float: left;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__clear {
+ cursor: pointer;
+ float: right;
+ font-weight: bold;
+ margin: 5px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
+ background-color: #e4e4e4;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ cursor: default;
+ float: left;
+ margin-right: 5px;
+ margin-top: 5px;
+ padding: 0 5px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
+ color: #999;
+ cursor: pointer;
+ display: inline-block;
+ font-weight: bold;
+ margin-right: 2px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
+ color: #333;
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
+ float: right;
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+ margin-left: 5px;
+ margin-right: auto;
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+ margin-left: 2px;
+ margin-right: auto;
+}
+
+.select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
+ border: solid #999 1px;
+ outline: 0;
+}
+
+.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
+ background-color: #eee;
+ cursor: default;
+}
+
+.select2-container--admin-autocomplete.select2-container--disabled .select2-selection__choice__remove {
+ display: none;
+}
+
+.select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--multiple {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--multiple {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
+ border: 1px solid #ccc;
+}
+
+.select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
+ background: transparent;
+ border: none;
+ outline: 0;
+ box-shadow: none;
+ -webkit-appearance: textfield;
+}
+
+.select2-container--admin-autocomplete .select2-results > .select2-results__options {
+ max-height: 200px;
+ overflow-y: auto;
+}
+
+.select2-container--admin-autocomplete .select2-results__option[role=group] {
+ padding: 0;
+}
+
+.select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
+ color: #999;
+}
+
+.select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
+ background-color: #ddd;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option {
+ padding-left: 1em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__group {
+ padding-left: 0;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -1em;
+ padding-left: 2em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -2em;
+ padding-left: 3em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -3em;
+ padding-left: 4em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -4em;
+ padding-left: 5em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -5em;
+ padding-left: 6em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
+ background-color: #79aec8;
+ color: white;
+}
+
+.select2-container--admin-autocomplete .select2-results__group {
+ cursor: default;
+ display: block;
+ padding: 6px;
+}
diff --git a/src/interface/static/admin/css/base.css b/src/interface/static/admin/css/base.css
new file mode 100644
index 0000000..fd011a3
--- /dev/null
+++ b/src/interface/static/admin/css/base.css
@@ -0,0 +1,987 @@
+/*
+ DJANGO Admin styles
+*/
+
+@import url(fonts.css);
+
+body {
+ margin: 0;
+ padding: 0;
+ font-size: 14px;
+ font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
+ color: #333;
+ background: #fff;
+}
+
+/* LINKS */
+
+a:link, a:visited {
+ color: #447e9b;
+ text-decoration: none;
+}
+
+a:focus, a:hover {
+ color: #036;
+}
+
+a:focus {
+ text-decoration: underline;
+}
+
+a img {
+ border: none;
+}
+
+a.section:link, a.section:visited {
+ color: #fff;
+ text-decoration: none;
+}
+
+a.section:focus, a.section:hover {
+ text-decoration: underline;
+}
+
+/* GLOBAL DEFAULTS */
+
+p, ol, ul, dl {
+ margin: .2em 0 .8em 0;
+}
+
+p {
+ padding: 0;
+ line-height: 140%;
+}
+
+h1,h2,h3,h4,h5 {
+ font-weight: bold;
+}
+
+h1 {
+ margin: 0 0 20px;
+ font-weight: 300;
+ font-size: 20px;
+ color: #666;
+}
+
+h2 {
+ font-size: 16px;
+ margin: 1em 0 .5em 0;
+}
+
+h2.subhead {
+ font-weight: normal;
+ margin-top: 0;
+}
+
+h3 {
+ font-size: 14px;
+ margin: .8em 0 .3em 0;
+ color: #666;
+ font-weight: bold;
+}
+
+h4 {
+ font-size: 12px;
+ margin: 1em 0 .8em 0;
+ padding-bottom: 3px;
+}
+
+h5 {
+ font-size: 10px;
+ margin: 1.5em 0 .5em 0;
+ color: #666;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+ul li {
+ list-style-type: square;
+ padding: 1px 0;
+}
+
+li ul {
+ margin-bottom: 0;
+}
+
+li, dt, dd {
+ font-size: 13px;
+ line-height: 20px;
+}
+
+dt {
+ font-weight: bold;
+ margin-top: 4px;
+}
+
+dd {
+ margin-left: 0;
+}
+
+form {
+ margin: 0;
+ padding: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+ border: none;
+ border-top: 1px solid #eee;
+}
+
+blockquote {
+ font-size: 11px;
+ color: #777;
+ margin-left: 2px;
+ padding-left: 10px;
+ border-left: 5px solid #ddd;
+}
+
+code, pre {
+ font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
+ color: #666;
+ font-size: 12px;
+}
+
+pre.literal-block {
+ margin: 10px;
+ background: #eee;
+ padding: 6px 8px;
+}
+
+code strong {
+ color: #930;
+}
+
+hr {
+ clear: both;
+ color: #eee;
+ background-color: #eee;
+ height: 1px;
+ border: none;
+ margin: 0;
+ padding: 0;
+ font-size: 1px;
+ line-height: 1px;
+}
+
+/* TEXT STYLES & MODIFIERS */
+
+.small {
+ font-size: 11px;
+}
+
+.tiny {
+ font-size: 10px;
+}
+
+p.tiny {
+ margin-top: -2px;
+}
+
+.mini {
+ font-size: 10px;
+}
+
+p.mini {
+ margin-top: -3px;
+}
+
+.help, p.help, form p.help, div.help, form div.help, div.help li {
+ font-size: 11px;
+ color: #999;
+}
+
+div.help ul {
+ margin-bottom: 0;
+}
+
+.help-tooltip {
+ cursor: help;
+}
+
+p img, h1 img, h2 img, h3 img, h4 img, td img {
+ vertical-align: middle;
+}
+
+.quiet, a.quiet:link, a.quiet:visited {
+ color: #999;
+ font-weight: normal;
+}
+
+.float-right {
+ float: right;
+}
+
+.float-left {
+ float: left;
+}
+
+.clear {
+ clear: both;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-right {
+ text-align: right;
+}
+
+.example {
+ margin: 10px 0;
+ padding: 5px 10px;
+ background: #efefef;
+}
+
+.nowrap {
+ white-space: nowrap;
+}
+
+/* TABLES */
+
+table {
+ border-collapse: collapse;
+ border-color: #ccc;
+}
+
+td, th {
+ font-size: 13px;
+ line-height: 16px;
+ border-bottom: 1px solid #eee;
+ vertical-align: top;
+ padding: 8px;
+ font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;
+}
+
+th {
+ font-weight: 600;
+ text-align: left;
+}
+
+thead th,
+tfoot td {
+ color: #666;
+ padding: 5px 10px;
+ font-size: 11px;
+ background: #fff;
+ border: none;
+ border-top: 1px solid #eee;
+ border-bottom: 1px solid #eee;
+}
+
+tfoot td {
+ border-bottom: none;
+ border-top: 1px solid #eee;
+}
+
+thead th.required {
+ color: #000;
+}
+
+tr.alt {
+ background: #f6f6f6;
+}
+
+.row1 {
+ background: #fff;
+}
+
+.row2 {
+ background: #f9f9f9;
+}
+
+/* SORTABLE TABLES */
+
+thead th {
+ padding: 5px 10px;
+ line-height: normal;
+ text-transform: uppercase;
+ background: #f6f6f6;
+}
+
+thead th a:link, thead th a:visited {
+ color: #666;
+}
+
+thead th.sorted {
+ background: #eee;
+}
+
+thead th.sorted .text {
+ padding-right: 42px;
+}
+
+table thead th .text span {
+ padding: 8px 10px;
+ display: block;
+}
+
+table thead th .text a {
+ display: block;
+ cursor: pointer;
+ padding: 8px 10px;
+}
+
+table thead th .text a:focus, table thead th .text a:hover {
+ background: #eee;
+}
+
+thead th.sorted a.sortremove {
+ visibility: hidden;
+}
+
+table thead th.sorted:hover a.sortremove {
+ visibility: visible;
+}
+
+table thead th.sorted .sortoptions {
+ display: block;
+ padding: 9px 5px 0 5px;
+ float: right;
+ text-align: right;
+}
+
+table thead th.sorted .sortpriority {
+ font-size: .8em;
+ min-width: 12px;
+ text-align: center;
+ vertical-align: 3px;
+ margin-left: 2px;
+ margin-right: 2px;
+}
+
+table thead th.sorted .sortoptions a {
+ position: relative;
+ width: 14px;
+ height: 14px;
+ display: inline-block;
+ background: url(../img/sorting-icons.svg) 0 0 no-repeat;
+ background-size: 14px auto;
+}
+
+table thead th.sorted .sortoptions a.sortremove {
+ background-position: 0 0;
+}
+
+table thead th.sorted .sortoptions a.sortremove:after {
+ content: '\\';
+ position: absolute;
+ top: -6px;
+ left: 3px;
+ font-weight: 200;
+ font-size: 18px;
+ color: #999;
+}
+
+table thead th.sorted .sortoptions a.sortremove:focus:after,
+table thead th.sorted .sortoptions a.sortremove:hover:after {
+ color: #447e9b;
+}
+
+table thead th.sorted .sortoptions a.sortremove:focus,
+table thead th.sorted .sortoptions a.sortremove:hover {
+ background-position: 0 -14px;
+}
+
+table thead th.sorted .sortoptions a.ascending {
+ background-position: 0 -28px;
+}
+
+table thead th.sorted .sortoptions a.ascending:focus,
+table thead th.sorted .sortoptions a.ascending:hover {
+ background-position: 0 -42px;
+}
+
+table thead th.sorted .sortoptions a.descending {
+ top: 1px;
+ background-position: 0 -56px;
+}
+
+table thead th.sorted .sortoptions a.descending:focus,
+table thead th.sorted .sortoptions a.descending:hover {
+ background-position: 0 -70px;
+}
+
+/* FORM DEFAULTS */
+
+input, textarea, select, .form-row p, form .button {
+ margin: 2px 0;
+ padding: 2px 3px;
+ vertical-align: middle;
+ font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;
+ font-weight: normal;
+ font-size: 13px;
+}
+.form-row div.help {
+ padding: 2px 3px;
+}
+
+textarea {
+ vertical-align: top;
+}
+
+input[type=text], input[type=password], input[type=email], input[type=url],
+input[type=number], input[type=tel], textarea, select, .vTextField {
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ padding: 5px 6px;
+ margin-top: 0;
+}
+
+input[type=text]:focus, input[type=password]:focus, input[type=email]:focus,
+input[type=url]:focus, input[type=number]:focus, input[type=tel]:focus,
+textarea:focus, select:focus, .vTextField:focus {
+ border-color: #999;
+}
+
+select {
+ height: 30px;
+}
+
+select[multiple] {
+ /* Allow HTML size attribute to override the height in the rule above. */
+ height: auto;
+ min-height: 150px;
+}
+
+/* FORM BUTTONS */
+
+.button, input[type=submit], input[type=button], .submit-row input, a.button {
+ background: #79aec8;
+ padding: 10px 15px;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+}
+
+a.button {
+ padding: 4px 5px;
+}
+
+.button:active, input[type=submit]:active, input[type=button]:active,
+.button:focus, input[type=submit]:focus, input[type=button]:focus,
+.button:hover, input[type=submit]:hover, input[type=button]:hover {
+ background: #609ab6;
+}
+
+.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
+ opacity: 0.4;
+}
+
+.button.default, input[type=submit].default, .submit-row input.default {
+ float: right;
+ border: none;
+ font-weight: 400;
+ background: #417690;
+}
+
+.button.default:active, input[type=submit].default:active,
+.button.default:focus, input[type=submit].default:focus,
+.button.default:hover, input[type=submit].default:hover {
+ background: #205067;
+}
+
+.button[disabled].default,
+input[type=submit][disabled].default,
+input[type=button][disabled].default {
+ opacity: 0.4;
+}
+
+
+/* MODULES */
+
+.module {
+ border: none;
+ margin-bottom: 30px;
+ background: #fff;
+}
+
+.module p, .module ul, .module h3, .module h4, .module dl, .module pre {
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+.module blockquote {
+ margin-left: 12px;
+}
+
+.module ul, .module ol {
+ margin-left: 1.5em;
+}
+
+.module h3 {
+ margin-top: .6em;
+}
+
+.module h2, .module caption, .inline-group h2 {
+ margin: 0;
+ padding: 8px;
+ font-weight: 400;
+ font-size: 13px;
+ text-align: left;
+ background: #79aec8;
+ color: #fff;
+}
+
+.module caption,
+.inline-group h2 {
+ font-size: 12px;
+ letter-spacing: 0.5px;
+ text-transform: uppercase;
+}
+
+.module table {
+ border-collapse: collapse;
+}
+
+/* MESSAGES & ERRORS */
+
+ul.messagelist {
+ padding: 0;
+ margin: 0;
+}
+
+ul.messagelist li {
+ display: block;
+ font-weight: 400;
+ font-size: 13px;
+ padding: 10px 10px 10px 65px;
+ margin: 0 0 10px 0;
+ background: #dfd url(../img/icon-yes.svg) 40px 12px no-repeat;
+ background-size: 16px auto;
+ color: #333;
+}
+
+ul.messagelist li.warning {
+ background: #ffc url(../img/icon-alert.svg) 40px 14px no-repeat;
+ background-size: 14px auto;
+}
+
+ul.messagelist li.error {
+ background: #ffefef url(../img/icon-no.svg) 40px 12px no-repeat;
+ background-size: 16px auto;
+}
+
+.errornote {
+ font-size: 14px;
+ font-weight: 700;
+ display: block;
+ padding: 10px 12px;
+ margin: 0 0 10px 0;
+ color: #ba2121;
+ border: 1px solid #ba2121;
+ border-radius: 4px;
+ background-color: #fff;
+ background-position: 5px 12px;
+}
+
+ul.errorlist {
+ margin: 0 0 4px;
+ padding: 0;
+ color: #ba2121;
+ background: #fff;
+}
+
+ul.errorlist li {
+ font-size: 13px;
+ display: block;
+ margin-bottom: 4px;
+}
+
+ul.errorlist li:first-child {
+ margin-top: 0;
+}
+
+ul.errorlist li a {
+ color: inherit;
+ text-decoration: underline;
+}
+
+td ul.errorlist {
+ margin: 0;
+ padding: 0;
+}
+
+td ul.errorlist li {
+ margin: 0;
+}
+
+.form-row.errors {
+ margin: 0;
+ border: none;
+ border-bottom: 1px solid #eee;
+ background: none;
+}
+
+.form-row.errors ul.errorlist li {
+ padding-left: 0;
+}
+
+.errors input, .errors select, .errors textarea {
+ border: 1px solid #ba2121;
+}
+
+div.system-message {
+ background: #ffc;
+ margin: 10px;
+ padding: 6px 8px;
+ font-size: .8em;
+}
+
+div.system-message p.system-message-title {
+ padding: 4px 5px 4px 25px;
+ margin: 0;
+ color: #c11;
+ background: #ffefef url(../img/icon-no.svg) 5px 5px no-repeat;
+}
+
+.description {
+ font-size: 12px;
+ padding: 5px 0 0 12px;
+}
+
+/* BREADCRUMBS */
+
+div.breadcrumbs {
+ background: #79aec8;
+ padding: 10px 40px;
+ border: none;
+ font-size: 14px;
+ color: #c4dce8;
+ text-align: left;
+}
+
+div.breadcrumbs a {
+ color: #fff;
+}
+
+div.breadcrumbs a:focus, div.breadcrumbs a:hover {
+ color: #c4dce8;
+}
+
+/* ACTION ICONS */
+
+.viewlink, .inlineviewlink {
+ padding-left: 16px;
+ background: url(../img/icon-viewlink.svg) 0 1px no-repeat;
+}
+
+.addlink {
+ padding-left: 16px;
+ background: url(../img/icon-addlink.svg) 0 1px no-repeat;
+}
+
+.changelink, .inlinechangelink {
+ padding-left: 16px;
+ background: url(../img/icon-changelink.svg) 0 1px no-repeat;
+}
+
+.deletelink {
+ padding-left: 16px;
+ background: url(../img/icon-deletelink.svg) 0 1px no-repeat;
+}
+
+a.deletelink:link, a.deletelink:visited {
+ color: #CC3434;
+}
+
+a.deletelink:focus, a.deletelink:hover {
+ color: #993333;
+ text-decoration: none;
+}
+
+/* OBJECT TOOLS */
+
+.object-tools {
+ font-size: 10px;
+ font-weight: bold;
+ padding-left: 0;
+ float: right;
+ position: relative;
+ margin-top: -48px;
+}
+
+.form-row .object-tools {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ float: none;
+ height: 2em;
+ padding-left: 3.5em;
+}
+
+.object-tools li {
+ display: block;
+ float: left;
+ margin-left: 5px;
+ height: 16px;
+}
+
+.object-tools a {
+ border-radius: 15px;
+}
+
+.object-tools a:link, .object-tools a:visited {
+ display: block;
+ float: left;
+ padding: 3px 12px;
+ background: #999;
+ font-weight: 400;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: #fff;
+}
+
+.object-tools a:focus, .object-tools a:hover {
+ background-color: #417690;
+}
+
+.object-tools a:focus{
+ text-decoration: none;
+}
+
+.object-tools a.viewsitelink, .object-tools a.golink,.object-tools a.addlink {
+ background-repeat: no-repeat;
+ background-position: right 7px center;
+ padding-right: 26px;
+}
+
+.object-tools a.viewsitelink, .object-tools a.golink {
+ background-image: url(../img/tooltag-arrowright.svg);
+}
+
+.object-tools a.addlink {
+ background-image: url(../img/tooltag-add.svg);
+}
+
+/* OBJECT HISTORY */
+
+table#change-history {
+ width: 100%;
+}
+
+table#change-history tbody th {
+ width: 16em;
+}
+
+/* PAGE STRUCTURE */
+
+#container {
+ position: relative;
+ width: 100%;
+ min-width: 980px;
+ padding: 0;
+}
+
+#content {
+ padding: 20px 40px;
+}
+
+.dashboard #content {
+ width: 600px;
+}
+
+#content-main {
+ float: left;
+ width: 100%;
+}
+
+#content-related {
+ float: right;
+ width: 260px;
+ position: relative;
+ margin-right: -300px;
+}
+
+#footer {
+ clear: both;
+ padding: 10px;
+}
+
+/* COLUMN TYPES */
+
+.colMS {
+ margin-right: 300px;
+}
+
+.colSM {
+ margin-left: 300px;
+}
+
+.colSM #content-related {
+ float: left;
+ margin-right: 0;
+ margin-left: -300px;
+}
+
+.colSM #content-main {
+ float: right;
+}
+
+.popup .colM {
+ width: auto;
+}
+
+/* HEADER */
+
+#header {
+ width: auto;
+ height: auto;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 40px;
+ background: #417690;
+ color: #ffc;
+ overflow: hidden;
+}
+
+#header a:link, #header a:visited {
+ color: #fff;
+}
+
+#header a:focus , #header a:hover {
+ text-decoration: underline;
+}
+
+#branding {
+ float: left;
+}
+
+#branding h1 {
+ padding: 0;
+ margin: 0 20px 0 0;
+ font-weight: 300;
+ font-size: 24px;
+ color: #f5dd5d;
+}
+
+#branding h1, #branding h1 a:link, #branding h1 a:visited {
+ color: #f5dd5d;
+}
+
+#branding h2 {
+ padding: 0 10px;
+ font-size: 14px;
+ margin: -8px 0 8px 0;
+ font-weight: normal;
+ color: #ffc;
+}
+
+#branding a:hover {
+ text-decoration: none;
+}
+
+#user-tools {
+ float: right;
+ padding: 0;
+ margin: 0 0 0 20px;
+ font-weight: 300;
+ font-size: 11px;
+ letter-spacing: 0.5px;
+ text-transform: uppercase;
+ text-align: right;
+}
+
+#user-tools a {
+ border-bottom: 1px solid rgba(255, 255, 255, 0.25);
+}
+
+#user-tools a:focus, #user-tools a:hover {
+ text-decoration: none;
+ border-bottom-color: #79aec8;
+ color: #79aec8;
+}
+
+/* SIDEBAR */
+
+#content-related {
+ background: #f8f8f8;
+}
+
+#content-related .module {
+ background: none;
+}
+
+#content-related h3 {
+ font-size: 14px;
+ color: #666;
+ padding: 0 16px;
+ margin: 0 0 16px;
+}
+
+#content-related h4 {
+ font-size: 13px;
+}
+
+#content-related p {
+ padding-left: 16px;
+ padding-right: 16px;
+}
+
+#content-related .actionlist {
+ padding: 0;
+ margin: 16px;
+}
+
+#content-related .actionlist li {
+ line-height: 1.2;
+ margin-bottom: 10px;
+ padding-left: 18px;
+}
+
+#content-related .module h2 {
+ background: none;
+ padding: 16px;
+ margin-bottom: 16px;
+ border-bottom: 1px solid #eaeaea;
+ font-size: 18px;
+ color: #333;
+}
+
+.delete-confirmation form input[type="submit"] {
+ background: #ba2121;
+ border-radius: 4px;
+ padding: 10px 15px;
+ color: #fff;
+}
+
+.delete-confirmation form input[type="submit"]:active,
+.delete-confirmation form input[type="submit"]:focus,
+.delete-confirmation form input[type="submit"]:hover {
+ background: #a41515;
+}
+
+.delete-confirmation form .cancel-link {
+ display: inline-block;
+ vertical-align: middle;
+ height: 15px;
+ line-height: 15px;
+ background: #ddd;
+ border-radius: 4px;
+ padding: 10px 15px;
+ color: #333;
+ margin: 0 0 0 10px;
+}
+
+.delete-confirmation form .cancel-link:active,
+.delete-confirmation form .cancel-link:focus,
+.delete-confirmation form .cancel-link:hover {
+ background: #ccc;
+}
+
+/* POPUP */
+.popup #content {
+ padding: 20px;
+}
+
+.popup #container {
+ min-width: 0;
+}
+
+.popup #header {
+ padding: 10px 20px;
+}
diff --git a/src/interface/static/admin/css/changelists.css b/src/interface/static/admin/css/changelists.css
new file mode 100644
index 0000000..17690a3
--- /dev/null
+++ b/src/interface/static/admin/css/changelists.css
@@ -0,0 +1,344 @@
+/* CHANGELISTS */
+
+#changelist {
+ position: relative;
+ width: 100%;
+}
+
+#changelist table {
+ width: 100%;
+}
+
+.change-list .hiddenfields { display:none; }
+
+.change-list .filtered table {
+ border-right: none;
+}
+
+.change-list .filtered {
+ min-height: 400px;
+}
+
+.change-list .filtered .results, .change-list .filtered .paginator,
+.filtered #toolbar, .filtered div.xfull {
+ margin-right: 280px;
+ width: auto;
+}
+
+.change-list .filtered table tbody th {
+ padding-right: 1em;
+}
+
+#changelist-form .results {
+ overflow-x: auto;
+}
+
+#changelist .toplinks {
+ border-bottom: 1px solid #ddd;
+}
+
+#changelist .paginator {
+ color: #666;
+ border-bottom: 1px solid #eee;
+ background: #fff;
+ overflow: hidden;
+}
+
+/* CHANGELIST TABLES */
+
+#changelist table thead th {
+ padding: 0;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+
+#changelist table thead th.action-checkbox-column {
+ width: 1.5em;
+ text-align: center;
+}
+
+#changelist table tbody td.action-checkbox {
+ text-align: center;
+}
+
+#changelist table tfoot {
+ color: #666;
+}
+
+/* TOOLBAR */
+
+#changelist #toolbar {
+ padding: 8px 10px;
+ margin-bottom: 15px;
+ border-top: 1px solid #eee;
+ border-bottom: 1px solid #eee;
+ background: #f8f8f8;
+ color: #666;
+}
+
+#changelist #toolbar form input {
+ border-radius: 4px;
+ font-size: 14px;
+ padding: 5px;
+ color: #333;
+}
+
+#changelist #toolbar form #searchbar {
+ height: 19px;
+ border: 1px solid #ccc;
+ padding: 2px 5px;
+ margin: 0;
+ vertical-align: top;
+ font-size: 13px;
+}
+
+#changelist #toolbar form #searchbar:focus {
+ border-color: #999;
+}
+
+#changelist #toolbar form input[type="submit"] {
+ border: 1px solid #ccc;
+ padding: 2px 10px;
+ margin: 0;
+ vertical-align: middle;
+ background: #fff;
+ box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
+ cursor: pointer;
+ color: #333;
+}
+
+#changelist #toolbar form input[type="submit"]:focus,
+#changelist #toolbar form input[type="submit"]:hover {
+ border-color: #999;
+}
+
+#changelist #changelist-search img {
+ vertical-align: middle;
+ margin-right: 4px;
+}
+
+/* FILTER COLUMN */
+
+#changelist-filter {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 1000;
+ width: 240px;
+ background: #f8f8f8;
+ border-left: none;
+ margin: 0;
+}
+
+#changelist-filter h2 {
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ padding: 5px 15px;
+ margin-bottom: 12px;
+ border-bottom: none;
+}
+
+#changelist-filter h3 {
+ font-weight: 400;
+ font-size: 14px;
+ padding: 0 15px;
+ margin-bottom: 10px;
+}
+
+#changelist-filter ul {
+ margin: 5px 0;
+ padding: 0 15px 15px;
+ border-bottom: 1px solid #eaeaea;
+}
+
+#changelist-filter ul:last-child {
+ border-bottom: none;
+ padding-bottom: none;
+}
+
+#changelist-filter li {
+ list-style-type: none;
+ margin-left: 0;
+ padding-left: 0;
+}
+
+#changelist-filter a {
+ display: block;
+ color: #999;
+ text-overflow: ellipsis;
+ overflow-x: hidden;
+}
+
+#changelist-filter li.selected {
+ border-left: 5px solid #eaeaea;
+ padding-left: 10px;
+ margin-left: -15px;
+}
+
+#changelist-filter li.selected a {
+ color: #5b80b2;
+}
+
+#changelist-filter a:focus, #changelist-filter a:hover,
+#changelist-filter li.selected a:focus,
+#changelist-filter li.selected a:hover {
+ color: #036;
+}
+
+/* DATE DRILLDOWN */
+
+.change-list ul.toplinks {
+ display: block;
+ float: left;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+}
+
+.change-list ul.toplinks li {
+ padding: 3px 6px;
+ font-weight: bold;
+ list-style-type: none;
+ display: inline-block;
+}
+
+.change-list ul.toplinks .date-back a {
+ color: #999;
+}
+
+.change-list ul.toplinks .date-back a:focus,
+.change-list ul.toplinks .date-back a:hover {
+ color: #036;
+}
+
+/* PAGINATOR */
+
+.paginator {
+ font-size: 13px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ line-height: 22px;
+ margin: 0;
+ border-top: 1px solid #ddd;
+}
+
+.paginator a:link, .paginator a:visited {
+ padding: 2px 6px;
+ background: #79aec8;
+ text-decoration: none;
+ color: #fff;
+}
+
+.paginator a.showall {
+ padding: 0;
+ border: none;
+ background: none;
+ color: #5b80b2;
+}
+
+.paginator a.showall:focus, .paginator a.showall:hover {
+ background: none;
+ color: #036;
+}
+
+.paginator .end {
+ margin-right: 6px;
+}
+
+.paginator .this-page {
+ padding: 2px 6px;
+ font-weight: bold;
+ font-size: 13px;
+ vertical-align: top;
+}
+
+.paginator a:focus, .paginator a:hover {
+ color: white;
+ background: #036;
+}
+
+/* ACTIONS */
+
+.filtered .actions {
+ margin-right: 280px;
+ border-right: none;
+}
+
+#changelist table input {
+ margin: 0;
+ vertical-align: baseline;
+}
+
+#changelist table tbody tr.selected {
+ background-color: #FFFFCC;
+}
+
+#changelist .actions {
+ padding: 10px;
+ background: #fff;
+ border-top: none;
+ border-bottom: none;
+ line-height: 24px;
+ color: #999;
+}
+
+#changelist .actions.selected {
+ background: #fffccf;
+ border-top: 1px solid #fffee8;
+ border-bottom: 1px solid #edecd6;
+}
+
+#changelist .actions span.all,
+#changelist .actions span.action-counter,
+#changelist .actions span.clear,
+#changelist .actions span.question {
+ font-size: 13px;
+ margin: 0 0.5em;
+ display: none;
+}
+
+#changelist .actions:last-child {
+ border-bottom: none;
+}
+
+#changelist .actions select {
+ vertical-align: top;
+ height: 24px;
+ background: none;
+ color: #000;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 14px;
+ padding: 0 0 0 4px;
+ margin: 0;
+ margin-left: 10px;
+}
+
+#changelist .actions select:focus {
+ border-color: #999;
+}
+
+#changelist .actions label {
+ display: inline-block;
+ vertical-align: middle;
+ font-size: 13px;
+}
+
+#changelist .actions .button {
+ font-size: 13px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background: #fff;
+ box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
+ cursor: pointer;
+ height: 24px;
+ line-height: 1;
+ padding: 4px 8px;
+ margin: 0;
+ color: #333;
+}
+
+#changelist .actions .button:focus, #changelist .actions .button:hover {
+ border-color: #999;
+}
diff --git a/src/interface/static/admin/css/dashboard.css b/src/interface/static/admin/css/dashboard.css
new file mode 100644
index 0000000..1560c7b
--- /dev/null
+++ b/src/interface/static/admin/css/dashboard.css
@@ -0,0 +1,27 @@
+/* DASHBOARD */
+
+.dashboard .module table th {
+ width: 100%;
+}
+
+.dashboard .module table td {
+ white-space: nowrap;
+}
+
+.dashboard .module table td a {
+ display: block;
+ padding-right: .6em;
+}
+
+/* RECENT ACTIONS MODULE */
+
+.module ul.actionlist {
+ margin-left: 0;
+}
+
+ul.actionlist li {
+ list-style-type: none;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -o-text-overflow: ellipsis;
+}
diff --git a/src/interface/static/admin/css/fonts.css b/src/interface/static/admin/css/fonts.css
new file mode 100644
index 0000000..c837e01
--- /dev/null
+++ b/src/interface/static/admin/css/fonts.css
@@ -0,0 +1,20 @@
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-Bold-webfont.woff');
+ font-weight: 700;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-Regular-webfont.woff');
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: url('../fonts/Roboto-Light-webfont.woff');
+ font-weight: 300;
+ font-style: normal;
+}
diff --git a/src/interface/static/admin/css/forms.css b/src/interface/static/admin/css/forms.css
new file mode 100644
index 0000000..62a093f
--- /dev/null
+++ b/src/interface/static/admin/css/forms.css
@@ -0,0 +1,532 @@
+@import url('widgets.css');
+
+/* FORM ROWS */
+
+.form-row {
+ overflow: hidden;
+ padding: 10px;
+ font-size: 13px;
+ border-bottom: 1px solid #eee;
+}
+
+.form-row img, .form-row input {
+ vertical-align: middle;
+}
+
+.form-row label input[type="checkbox"] {
+ margin-top: 0;
+ vertical-align: 0;
+}
+
+form .form-row p {
+ padding-left: 0;
+}
+
+.hidden {
+ display: none;
+}
+
+/* FORM LABELS */
+
+label {
+ font-weight: normal;
+ color: #666;
+ font-size: 13px;
+}
+
+.required label, label.required {
+ font-weight: bold;
+ color: #333;
+}
+
+/* RADIO BUTTONS */
+
+form ul.radiolist li {
+ list-style-type: none;
+}
+
+form ul.radiolist label {
+ float: none;
+ display: inline;
+}
+
+form ul.radiolist input[type="radio"] {
+ margin: -2px 4px 0 0;
+ padding: 0;
+}
+
+form ul.inline {
+ margin-left: 0;
+ padding: 0;
+}
+
+form ul.inline li {
+ float: left;
+ padding-right: 7px;
+}
+
+/* ALIGNED FIELDSETS */
+
+.aligned label {
+ display: block;
+ padding: 4px 10px 0 0;
+ float: left;
+ width: 160px;
+ word-wrap: break-word;
+ line-height: 1;
+}
+
+.aligned label:not(.vCheckboxLabel):after {
+ content: '';
+ display: inline-block;
+ vertical-align: middle;
+ height: 26px;
+}
+
+.aligned label + p, .aligned label + div.help, .aligned label + div.readonly {
+ padding: 6px 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 170px;
+}
+
+.aligned ul label {
+ display: inline;
+ float: none;
+ width: auto;
+}
+
+.aligned .form-row input {
+ margin-bottom: 0;
+}
+
+.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField {
+ width: 350px;
+}
+
+form .aligned ul {
+ margin-left: 160px;
+ padding-left: 10px;
+}
+
+form .aligned ul.radiolist {
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+}
+
+form .aligned p.help,
+form .aligned div.help {
+ clear: left;
+ margin-top: 0;
+ margin-left: 160px;
+ padding-left: 10px;
+}
+
+form .aligned label + p.help,
+form .aligned label + div.help {
+ margin-left: 0;
+ padding-left: 0;
+}
+
+form .aligned p.help:last-child,
+form .aligned div.help:last-child {
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+
+form .aligned input + p.help,
+form .aligned textarea + p.help,
+form .aligned select + p.help,
+form .aligned input + div.help,
+form .aligned textarea + div.help,
+form .aligned select + div.help {
+ margin-left: 160px;
+ padding-left: 10px;
+}
+
+form .aligned ul li {
+ list-style: none;
+}
+
+form .aligned table p {
+ margin-left: 0;
+ padding-left: 0;
+}
+
+.aligned .vCheckboxLabel {
+ float: none;
+ width: auto;
+ display: inline-block;
+ vertical-align: -3px;
+ padding: 0 0 5px 5px;
+}
+
+.aligned .vCheckboxLabel + p.help,
+.aligned .vCheckboxLabel + div.help {
+ margin-top: -4px;
+}
+
+.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField {
+ width: 610px;
+}
+
+.checkbox-row p.help,
+.checkbox-row div.help {
+ margin-left: 0;
+ padding-left: 0;
+}
+
+fieldset .fieldBox {
+ float: left;
+ margin-right: 20px;
+}
+
+/* WIDE FIELDSETS */
+
+.wide label {
+ width: 200px;
+}
+
+form .wide p,
+form .wide input + p.help,
+form .wide input + div.help {
+ margin-left: 200px;
+}
+
+form .wide p.help,
+form .wide div.help {
+ padding-left: 38px;
+}
+
+form div.help ul {
+ padding-left: 0;
+ margin-left: 0;
+}
+
+.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField {
+ width: 450px;
+}
+
+/* COLLAPSED FIELDSETS */
+
+fieldset.collapsed * {
+ display: none;
+}
+
+fieldset.collapsed h2, fieldset.collapsed {
+ display: block;
+}
+
+fieldset.collapsed {
+ border: 1px solid #eee;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+fieldset.collapsed h2 {
+ background: #f8f8f8;
+ color: #666;
+}
+
+fieldset .collapse-toggle {
+ color: #fff;
+}
+
+fieldset.collapsed .collapse-toggle {
+ background: transparent;
+ display: inline;
+ color: #447e9b;
+}
+
+/* MONOSPACE TEXTAREAS */
+
+fieldset.monospace textarea {
+ font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
+}
+
+/* SUBMIT ROW */
+
+.submit-row {
+ padding: 12px 14px;
+ margin: 0 0 20px;
+ background: #f8f8f8;
+ border: 1px solid #eee;
+ border-radius: 4px;
+ text-align: right;
+ overflow: hidden;
+}
+
+body.popup .submit-row {
+ overflow: auto;
+}
+
+.submit-row input {
+ height: 35px;
+ line-height: 15px;
+ margin: 0 0 0 5px;
+}
+
+.submit-row input.default {
+ margin: 0 0 0 8px;
+ text-transform: uppercase;
+}
+
+.submit-row p {
+ margin: 0.3em;
+}
+
+.submit-row p.deletelink-box {
+ float: left;
+ margin: 0;
+}
+
+.submit-row a.deletelink {
+ display: block;
+ background: #ba2121;
+ border-radius: 4px;
+ padding: 10px 15px;
+ height: 15px;
+ line-height: 15px;
+ color: #fff;
+}
+
+.submit-row a.closelink {
+ display: inline-block;
+ background: #bbbbbb;
+ border-radius: 4px;
+ padding: 10px 15px;
+ height: 15px;
+ line-height: 15px;
+ margin: 0 0 0 5px;
+ color: #fff;
+}
+
+.submit-row a.deletelink:focus,
+.submit-row a.deletelink:hover,
+.submit-row a.deletelink:active {
+ background: #a41515;
+}
+
+.submit-row a.closelink:focus,
+.submit-row a.closelink:hover,
+.submit-row a.closelink:active {
+ background: #aaaaaa;
+}
+
+/* CUSTOM FORM FIELDS */
+
+.vSelectMultipleField {
+ vertical-align: top;
+}
+
+.vCheckboxField {
+ border: none;
+}
+
+.vDateField, .vTimeField {
+ margin-right: 2px;
+ margin-bottom: 4px;
+}
+
+.vDateField {
+ min-width: 6.85em;
+}
+
+.vTimeField {
+ min-width: 4.7em;
+}
+
+.vURLField {
+ width: 30em;
+}
+
+.vLargeTextField, .vXMLLargeTextField {
+ width: 48em;
+}
+
+.flatpages-flatpage #id_content {
+ height: 40.2em;
+}
+
+.module table .vPositiveSmallIntegerField {
+ width: 2.2em;
+}
+
+.vTextField, .vUUIDField {
+ width: 20em;
+}
+
+.vIntegerField {
+ width: 5em;
+}
+
+.vBigIntegerField {
+ width: 10em;
+}
+
+.vForeignKeyRawIdAdminField {
+ width: 5em;
+}
+
+/* INLINES */
+
+.inline-group {
+ padding: 0;
+ margin: 0 0 30px;
+}
+
+.inline-group thead th {
+ padding: 8px 10px;
+}
+
+.inline-group .aligned label {
+ width: 160px;
+}
+
+.inline-related {
+ position: relative;
+}
+
+.inline-related h3 {
+ margin: 0;
+ color: #666;
+ padding: 5px;
+ font-size: 13px;
+ background: #f8f8f8;
+ border-top: 1px solid #eee;
+ border-bottom: 1px solid #eee;
+}
+
+.inline-related h3 span.delete {
+ float: right;
+}
+
+.inline-related h3 span.delete label {
+ margin-left: 2px;
+ font-size: 11px;
+}
+
+.inline-related fieldset {
+ margin: 0;
+ background: #fff;
+ border: none;
+ width: 100%;
+}
+
+.inline-related fieldset.module h3 {
+ margin: 0;
+ padding: 2px 5px 3px 5px;
+ font-size: 11px;
+ text-align: left;
+ font-weight: bold;
+ background: #bcd;
+ color: #fff;
+}
+
+.inline-group .tabular fieldset.module {
+ border: none;
+}
+
+.inline-related.tabular fieldset.module table {
+ width: 100%;
+}
+
+.last-related fieldset {
+ border: none;
+}
+
+.inline-group .tabular tr.has_original td {
+ padding-top: 2em;
+}
+
+.inline-group .tabular tr td.original {
+ padding: 2px 0 0 0;
+ width: 0;
+ _position: relative;
+}
+
+.inline-group .tabular th.original {
+ width: 0px;
+ padding: 0;
+}
+
+.inline-group .tabular td.original p {
+ position: absolute;
+ left: 0;
+ height: 1.1em;
+ padding: 2px 9px;
+ overflow: hidden;
+ font-size: 9px;
+ font-weight: bold;
+ color: #666;
+ _width: 700px;
+}
+
+.inline-group ul.tools {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+
+.inline-group ul.tools li {
+ display: inline;
+ padding: 0 5px;
+}
+
+.inline-group div.add-row,
+.inline-group .tabular tr.add-row td {
+ color: #666;
+ background: #f8f8f8;
+ padding: 8px 10px;
+ border-bottom: 1px solid #eee;
+}
+
+.inline-group .tabular tr.add-row td {
+ padding: 8px 10px;
+ border-bottom: 1px solid #eee;
+}
+
+.inline-group ul.tools a.add,
+.inline-group div.add-row a,
+.inline-group .tabular tr.add-row td a {
+ background: url(../img/icon-addlink.svg) 0 1px no-repeat;
+ padding-left: 16px;
+ font-size: 12px;
+}
+
+.empty-form {
+ display: none;
+}
+
+/* RELATED FIELD ADD ONE / LOOKUP */
+
+.add-another, .related-lookup {
+ margin-left: 5px;
+ display: inline-block;
+ vertical-align: middle;
+ background-repeat: no-repeat;
+ background-size: 14px;
+}
+
+.add-another {
+ width: 16px;
+ height: 16px;
+ background-image: url(../img/icon-addlink.svg);
+}
+
+.related-lookup {
+ width: 16px;
+ height: 16px;
+ background-image: url(../img/search.svg);
+}
+
+form .related-widget-wrapper ul {
+ display: inline-block;
+ margin-left: 0;
+ padding-left: 0;
+}
+
+.clearable-file-input input {
+ margin-top: 0;
+}
diff --git a/src/interface/static/admin/css/login.css b/src/interface/static/admin/css/login.css
new file mode 100644
index 0000000..2ec241c
--- /dev/null
+++ b/src/interface/static/admin/css/login.css
@@ -0,0 +1,79 @@
+/* LOGIN FORM */
+
+body.login {
+ background: #f8f8f8;
+}
+
+.login #header {
+ height: auto;
+ padding: 15px 16px;
+ justify-content: center;
+}
+
+.login #header h1 {
+ font-size: 18px;
+}
+
+.login #header h1 a {
+ color: #fff;
+}
+
+.login #content {
+ padding: 20px 20px 0;
+}
+
+.login #container {
+ background: #fff;
+ border: 1px solid #eaeaea;
+ border-radius: 4px;
+ overflow: hidden;
+ width: 28em;
+ min-width: 300px;
+ margin: 100px auto;
+}
+
+.login #content-main {
+ width: 100%;
+}
+
+.login .form-row {
+ padding: 4px 0;
+ float: left;
+ width: 100%;
+ border-bottom: none;
+}
+
+.login .form-row label {
+ padding-right: 0.5em;
+ line-height: 2em;
+ font-size: 1em;
+ clear: both;
+ color: #333;
+}
+
+.login .form-row #id_username, .login .form-row #id_password {
+ clear: both;
+ padding: 8px;
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.login span.help {
+ font-size: 10px;
+ display: block;
+}
+
+.login .submit-row {
+ clear: both;
+ padding: 1em 0 0 9.4em;
+ margin: 0;
+ border: none;
+ background: none;
+ text-align: left;
+}
+
+.login .password-reset-link {
+ text-align: center;
+}
diff --git a/src/interface/static/admin/css/responsive.css b/src/interface/static/admin/css/responsive.css
new file mode 100644
index 0000000..5b0d1ec
--- /dev/null
+++ b/src/interface/static/admin/css/responsive.css
@@ -0,0 +1,992 @@
+/* Tablets */
+
+input[type="submit"], button {
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+@media (max-width: 1024px) {
+ /* Basic */
+
+ html {
+ -webkit-text-size-adjust: 100%;
+ }
+
+ td, th {
+ padding: 10px;
+ font-size: 14px;
+ }
+
+ .small {
+ font-size: 12px;
+ }
+
+ /* Layout */
+
+ #container {
+ min-width: 0;
+ }
+
+ #content {
+ padding: 20px 30px 30px;
+ }
+
+ div.breadcrumbs {
+ padding: 10px 30px;
+ }
+
+ /* Header */
+
+ #header {
+ flex-direction: column;
+ padding: 15px 30px;
+ justify-content: flex-start;
+ }
+
+ #branding h1 {
+ margin: 0 0 8px;
+ font-size: 20px;
+ line-height: 1.2;
+ }
+
+ #user-tools {
+ margin: 0;
+ font-weight: 400;
+ line-height: 1.85;
+ text-align: left;
+ }
+
+ #user-tools a {
+ display: inline-block;
+ line-height: 1.4;
+ }
+
+ /* Dashboard */
+
+ .dashboard #content {
+ width: auto;
+ }
+
+ #content-related {
+ margin-right: -290px;
+ }
+
+ .colSM #content-related {
+ margin-left: -290px;
+ }
+
+ .colMS {
+ margin-right: 290px;
+ }
+
+ .colSM {
+ margin-left: 290px;
+ }
+
+ .dashboard .module table td a {
+ padding-right: 0;
+ }
+
+ td .changelink, td .addlink {
+ font-size: 13px;
+ }
+
+ /* Changelist */
+
+ #changelist #toolbar {
+ border: none;
+ padding: 15px;
+ }
+
+ #changelist-search > div {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-wrap: wrap;
+ flex-wrap: wrap;
+ max-width: 480px;
+ }
+
+ #changelist-search label {
+ line-height: 22px;
+ }
+
+ #changelist #toolbar form #searchbar {
+ -webkit-flex: 1 0 auto;
+ flex: 1 0 auto;
+ width: 0;
+ height: 22px;
+ margin: 0 10px 0 6px;
+ }
+
+ #changelist-search .quiet {
+ width: 100%;
+ margin: 5px 0 0 25px;
+ }
+
+ #changelist .actions {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 15px 0;
+ }
+
+ #changelist .actions.selected {
+ border: none;
+ }
+
+ #changelist .actions label {
+ display: flex;
+ }
+
+ #changelist .actions select {
+ background: #fff;
+ }
+
+ #changelist .actions .button {
+ min-width: 48px;
+ margin: 0 10px;
+ }
+
+ #changelist .actions span.all,
+ #changelist .actions span.clear,
+ #changelist .actions span.question,
+ #changelist .actions span.action-counter {
+ font-size: 11px;
+ margin: 0 10px 0 0;
+ }
+
+ #changelist-filter {
+ width: 200px;
+ }
+
+ .change-list .filtered .results,
+ .change-list .filtered .paginator,
+ .filtered #toolbar,
+ .filtered .actions,
+ .filtered div.xfull {
+ margin-right: 230px;
+ }
+
+ #changelist .paginator {
+ border-top-color: #eee;
+ }
+
+ #changelist .results + .paginator {
+ border-top: none;
+ }
+
+ /* Forms */
+
+ label {
+ font-size: 14px;
+ }
+
+ .form-row input[type=text],
+ .form-row input[type=password],
+ .form-row input[type=email],
+ .form-row input[type=url],
+ .form-row input[type=tel],
+ .form-row input[type=number],
+ .form-row textarea,
+ .form-row select,
+ .form-row .vTextField {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 6px 8px;
+ min-height: 36px;
+ font-size: 14px;
+ }
+
+ .form-row select {
+ height: 36px;
+ }
+
+ .form-row select[multiple] {
+ height: auto;
+ min-height: 0;
+ }
+
+ fieldset .fieldBox {
+ float: none;
+ margin: 0 -10px;
+ padding: 0 10px;
+ }
+
+ fieldset .fieldBox + .fieldBox {
+ margin-top: 10px;
+ padding-top: 10px;
+ border-top: 1px solid #eee;
+ }
+
+ textarea {
+ max-width: 518px;
+ max-height: 120px;
+ }
+
+ .aligned label {
+ padding-top: 6px;
+ }
+
+ .aligned .add-another,
+ .aligned .related-lookup,
+ .aligned .datetimeshortcuts,
+ .aligned .related-lookup + strong {
+ align-self: center;
+ margin-left: 15px;
+ }
+
+ form .aligned ul.radiolist {
+ margin-left: 2px;
+ }
+
+ /* Related widget */
+
+ .related-widget-wrapper {
+ float: none;
+ }
+
+ .related-widget-wrapper-link + .selector {
+ max-width: calc(100% - 30px);
+ margin-right: 15px;
+ }
+
+ select + .related-widget-wrapper-link,
+ .related-widget-wrapper-link + .related-widget-wrapper-link {
+ margin-left: 10px;
+ }
+
+ /* Selector */
+
+ .selector {
+ display: flex;
+ width: 100%;
+ }
+
+ .selector .selector-filter {
+ display: flex;
+ align-items: center;
+ }
+
+ .selector .selector-filter label {
+ margin: 0 8px 0 0;
+ }
+
+ .selector .selector-filter input {
+ width: auto;
+ min-height: 0;
+ flex: 1 1;
+ }
+
+ .selector-available, .selector-chosen {
+ width: auto;
+ flex: 1 1;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .selector select {
+ width: 100%;
+ flex: 1 0 auto;
+ margin-bottom: 5px;
+ }
+
+ .selector ul.selector-chooser {
+ width: 26px;
+ height: 52px;
+ padding: 2px 0;
+ margin: auto 15px;
+ border-radius: 20px;
+ transform: translateY(-10px);
+ }
+
+ .selector-add, .selector-remove {
+ width: 20px;
+ height: 20px;
+ background-size: 20px auto;
+ }
+
+ .selector-add {
+ background-position: 0 -120px;
+ }
+
+ .selector-remove {
+ background-position: 0 -80px;
+ }
+
+ a.selector-chooseall, a.selector-clearall {
+ align-self: center;
+ }
+
+ .stacked {
+ flex-direction: column;
+ max-width: 480px;
+ }
+
+ .stacked > * {
+ flex: 0 1 auto;
+ }
+
+ .stacked select {
+ margin-bottom: 0;
+ }
+
+ .stacked .selector-available, .stacked .selector-chosen {
+ width: auto;
+ }
+
+ .stacked ul.selector-chooser {
+ width: 52px;
+ height: 26px;
+ padding: 0 2px;
+ margin: 15px auto;
+ transform: none;
+ }
+
+ .stacked .selector-chooser li {
+ padding: 3px;
+ }
+
+ .stacked .selector-add, .stacked .selector-remove {
+ background-size: 20px auto;
+ }
+
+ .stacked .selector-add {
+ background-position: 0 -40px;
+ }
+
+ .stacked .active.selector-add {
+ background-position: 0 -60px;
+ }
+
+ .stacked .selector-remove {
+ background-position: 0 0;
+ }
+
+ .stacked .active.selector-remove {
+ background-position: 0 -20px;
+ }
+
+ .help-tooltip, .selector .help-icon {
+ display: none;
+ }
+
+ form .form-row p.datetime {
+ width: 100%;
+ }
+
+ .datetime input {
+ width: 50%;
+ max-width: 120px;
+ }
+
+ .datetime span {
+ font-size: 13px;
+ }
+
+ .datetime .timezonewarning {
+ display: block;
+ font-size: 11px;
+ color: #999;
+ }
+
+ .datetimeshortcuts {
+ color: #ccc;
+ }
+
+ .inline-group {
+ overflow: auto;
+ }
+
+ /* Messages */
+
+ ul.messagelist li {
+ padding-left: 55px;
+ background-position: 30px 12px;
+ }
+
+ ul.messagelist li.error {
+ background-position: 30px 12px;
+ }
+
+ ul.messagelist li.warning {
+ background-position: 30px 14px;
+ }
+
+ /* Login */
+
+ .login #header {
+ padding: 15px 20px;
+ }
+
+ .login #branding h1 {
+ margin: 0;
+ }
+
+ /* GIS */
+
+ div.olMap {
+ max-width: calc(100vw - 30px);
+ max-height: 300px;
+ }
+
+ .olMap + .clear_features {
+ display: block;
+ margin-top: 10px;
+ }
+
+ /* Docs */
+
+ .module table.xfull {
+ width: 100%;
+ }
+
+ pre.literal-block {
+ overflow: auto;
+ }
+}
+
+/* Mobile */
+
+@media (max-width: 767px) {
+ /* Layout */
+
+ #header, #content, #footer {
+ padding: 15px;
+ }
+
+ #footer:empty {
+ padding: 0;
+ }
+
+ div.breadcrumbs {
+ padding: 10px 15px;
+ }
+
+ /* Dashboard */
+
+ .colMS, .colSM {
+ margin: 0;
+ }
+
+ #content-related, .colSM #content-related {
+ width: 100%;
+ margin: 0;
+ }
+
+ #content-related .module {
+ margin-bottom: 0;
+ }
+
+ #content-related .module h2 {
+ padding: 10px 15px;
+ font-size: 16px;
+ }
+
+ /* Changelist */
+
+ #changelist {
+ display: flex;
+ flex-direction: column;
+ }
+
+ #changelist #toolbar {
+ order: 1;
+ padding: 10px;
+ }
+
+ #changelist .xfull {
+ order: 2;
+ }
+
+ #changelist-form {
+ order: 3;
+ }
+
+ #changelist-filter {
+ order: 4;
+ }
+
+ #changelist .actions label {
+ flex: 1 1;
+ }
+
+ #changelist .actions select {
+ flex: 1 0;
+ width: 100%;
+ }
+
+ #changelist .actions span {
+ flex: 1 0 100%;
+ }
+
+ .change-list .filtered .results, .change-list .filtered .paginator,
+ .filtered #toolbar, .filtered .actions, .filtered div.xfull {
+ margin-right: 0;
+ }
+
+ #changelist-filter {
+ position: static;
+ width: auto;
+ margin-top: 30px;
+ }
+
+ .object-tools {
+ float: none;
+ margin: 0 0 15px;
+ padding: 0;
+ overflow: hidden;
+ }
+
+ .object-tools li {
+ height: auto;
+ margin-left: 0;
+ }
+
+ .object-tools li + li {
+ margin-left: 15px;
+ }
+
+ /* Forms */
+
+ .form-row {
+ padding: 15px 0;
+ }
+
+ .aligned .form-row,
+ .aligned .form-row > div {
+ display: flex;
+ flex-wrap: wrap;
+ max-width: 100vw;
+ }
+
+ .aligned .form-row > div {
+ width: calc(100vw - 30px);
+ }
+
+ textarea {
+ max-width: none;
+ }
+
+ .vURLField {
+ width: auto;
+ }
+
+ fieldset .fieldBox + .fieldBox {
+ margin-top: 15px;
+ padding-top: 15px;
+ }
+
+ fieldset.collapsed .form-row {
+ display: none;
+ }
+
+ .aligned label {
+ width: 100%;
+ padding: 0 0 10px;
+ }
+
+ .aligned label:after {
+ max-height: 0;
+ }
+
+ .aligned .form-row input,
+ .aligned .form-row select,
+ .aligned .form-row textarea {
+ flex: 1 1 auto;
+ max-width: 100%;
+ }
+
+ .aligned .checkbox-row {
+ align-items: center;
+ }
+
+ .aligned .checkbox-row input {
+ flex: 0 1 auto;
+ margin: 0;
+ }
+
+ .aligned .vCheckboxLabel {
+ flex: 1 0;
+ padding: 1px 0 0 5px;
+ }
+
+ .aligned label + p,
+ .aligned label + div.help,
+ .aligned label + div.readonly {
+ padding: 0;
+ margin-left: 0;
+ }
+
+ .aligned p.file-upload {
+ margin-left: 0;
+ font-size: 13px;
+ }
+
+ span.clearable-file-input {
+ margin-left: 15px;
+ }
+
+ span.clearable-file-input label {
+ font-size: 13px;
+ padding-bottom: 0;
+ }
+
+ .aligned .timezonewarning {
+ flex: 1 0 100%;
+ margin-top: 5px;
+ }
+
+ form .aligned .form-row div.help {
+ width: 100%;
+ margin: 5px 0 0;
+ padding: 0;
+ }
+
+ form .aligned ul {
+ margin-left: 0;
+ padding-left: 0;
+ }
+
+ form .aligned ul.radiolist {
+ margin-right: 15px;
+ margin-bottom: -3px;
+ }
+
+ form .aligned ul.radiolist li + li {
+ margin-top: 5px;
+ }
+
+ /* Related widget */
+
+ .related-widget-wrapper {
+ width: 100%;
+ display: flex;
+ align-items: flex-start;
+ }
+
+ .related-widget-wrapper .selector {
+ order: 1;
+ }
+
+ .related-widget-wrapper > a {
+ order: 2;
+ }
+
+ .related-widget-wrapper .radiolist ~ a {
+ align-self: flex-end;
+ }
+
+ .related-widget-wrapper > select ~ a {
+ align-self: center;
+ }
+
+ select + .related-widget-wrapper-link,
+ .related-widget-wrapper-link + .related-widget-wrapper-link {
+ margin-left: 15px;
+ }
+
+ /* Selector */
+
+ .selector {
+ flex-direction: column;
+ }
+
+ .selector > * {
+ float: none;
+ }
+
+ .selector-available, .selector-chosen {
+ margin-bottom: 0;
+ flex: 1 1 auto;
+ }
+
+ .selector select {
+ max-height: 96px;
+ }
+
+ .selector ul.selector-chooser {
+ display: block;
+ float: none;
+ width: 52px;
+ height: 26px;
+ padding: 0 2px;
+ margin: 15px auto 20px;
+ transform: none;
+ }
+
+ .selector ul.selector-chooser li {
+ float: left;
+ }
+
+ .selector-remove {
+ background-position: 0 0;
+ }
+
+ .selector-add {
+ background-position: 0 -40px;
+ }
+
+ /* Inlines */
+
+ .inline-group[data-inline-type="stacked"] .inline-related {
+ border: 2px solid #eee;
+ border-radius: 4px;
+ margin-top: 15px;
+ overflow: auto;
+ }
+
+ .inline-group[data-inline-type="stacked"] .inline-related > * {
+ box-sizing: border-box;
+ }
+
+ .inline-group[data-inline-type="stacked"] .inline-related + .inline-related {
+ margin-top: 30px;
+ }
+
+ .inline-group[data-inline-type="stacked"] .inline-related .module {
+ padding: 0 10px;
+ }
+
+ .inline-group[data-inline-type="stacked"] .inline-related .module .form-row:last-child {
+ border-bottom: none;
+ }
+
+ .inline-group[data-inline-type="stacked"] .inline-related h3 {
+ padding: 10px;
+ border-top-width: 0;
+ border-bottom-width: 2px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ }
+
+ .inline-group[data-inline-type="stacked"] .inline-related h3 .inline_label {
+ margin-right: auto;
+ }
+
+ .inline-group[data-inline-type="stacked"] .inline-related h3 span.delete {
+ float: none;
+ flex: 1 1 100%;
+ margin-top: 5px;
+ }
+
+ .inline-group[data-inline-type="stacked"] .aligned .form-row > div:not([class]) {
+ width: 100%;
+ }
+
+ .inline-group[data-inline-type="stacked"] .aligned label {
+ width: 100%;
+ }
+
+ .inline-group[data-inline-type="stacked"] div.add-row {
+ margin-top: 15px;
+ border: 1px solid #eee;
+ border-radius: 4px;
+ }
+
+ .inline-group div.add-row,
+ .inline-group .tabular tr.add-row td {
+ padding: 0;
+ }
+
+ .inline-group div.add-row a,
+ .inline-group .tabular tr.add-row td a {
+ display: block;
+ padding: 8px 10px 8px 26px;
+ background-position: 8px 9px;
+ }
+
+ /* Submit row */
+
+ .submit-row {
+ padding: 10px 10px 0;
+ margin: 0 0 15px;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .submit-row > * {
+ width: 100%;
+ }
+
+ .submit-row input, .submit-row input.default, .submit-row a, .submit-row a.closelink {
+ float: none;
+ margin: 0 0 10px;
+ text-align: center;
+ }
+
+ .submit-row a.closelink {
+ padding: 10px 0;
+ }
+
+ .submit-row p.deletelink-box {
+ order: 4;
+ }
+
+ /* Messages */
+
+ ul.messagelist li {
+ padding-left: 40px;
+ background-position: 15px 12px;
+ }
+
+ ul.messagelist li.error {
+ background-position: 15px 12px;
+ }
+
+ ul.messagelist li.warning {
+ background-position: 15px 14px;
+ }
+
+ /* Paginator */
+
+ .paginator .this-page, .paginator a:link, .paginator a:visited {
+ padding: 4px 10px;
+ }
+
+ /* Login */
+
+ body.login {
+ padding: 0 15px;
+ }
+
+ .login #container {
+ width: auto;
+ max-width: 480px;
+ margin: 50px auto;
+ }
+
+ .login #header,
+ .login #content {
+ padding: 15px;
+ }
+
+ .login #content-main {
+ float: none;
+ }
+
+ .login .form-row {
+ padding: 0;
+ }
+
+ .login .form-row + .form-row {
+ margin-top: 15px;
+ }
+
+ .login .form-row label {
+ display: block;
+ margin: 0 0 5px;
+ padding: 0;
+ line-height: 1.2;
+ }
+
+ .login .submit-row {
+ padding: 15px 0 0;
+ }
+
+ .login br, .login .submit-row label {
+ display: none;
+ }
+
+ .login .submit-row input {
+ margin: 0;
+ text-transform: uppercase;
+ }
+
+ .errornote {
+ margin: 0 0 20px;
+ padding: 8px 12px;
+ font-size: 13px;
+ }
+
+ /* Calendar and clock */
+
+ .calendarbox, .clockbox {
+ position: fixed !important;
+ top: 50% !important;
+ left: 50% !important;
+ transform: translate(-50%, -50%);
+ margin: 0;
+ border: none;
+ overflow: visible;
+ }
+
+ .calendarbox:before, .clockbox:before {
+ content: '';
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ width: 100vw;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0.75);
+ transform: translate(-50%, -50%);
+ }
+
+ .calendarbox > *, .clockbox > * {
+ position: relative;
+ z-index: 1;
+ }
+
+ .calendarbox > div:first-child {
+ z-index: 2;
+ }
+
+ .calendarbox .calendar, .clockbox h2 {
+ border-radius: 4px 4px 0 0;
+ overflow: hidden;
+ }
+
+ .calendarbox .calendar-cancel, .clockbox .calendar-cancel {
+ border-radius: 0 0 4px 4px;
+ overflow: hidden;
+ }
+
+ .calendar-shortcuts {
+ padding: 10px 0;
+ font-size: 12px;
+ line-height: 12px;
+ }
+
+ .calendar-shortcuts a {
+ margin: 0 4px;
+ }
+
+ .timelist a {
+ background: #fff;
+ padding: 4px;
+ }
+
+ .calendar-cancel {
+ padding: 8px 10px;
+ }
+
+ .clockbox h2 {
+ padding: 8px 15px;
+ }
+
+ .calendar caption {
+ padding: 10px;
+ }
+
+ .calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
+ z-index: 1;
+ top: 10px;
+ }
+
+ /* History */
+
+ table#change-history tbody th, table#change-history tbody td {
+ font-size: 13px;
+ word-break: break-word;
+ }
+
+ table#change-history tbody th {
+ width: auto;
+ }
+
+ /* Docs */
+
+ table.model tbody th, table.model tbody td {
+ font-size: 13px;
+ word-break: break-word;
+ }
+}
diff --git a/src/interface/static/admin/css/responsive_rtl.css b/src/interface/static/admin/css/responsive_rtl.css
new file mode 100644
index 0000000..f999cb1
--- /dev/null
+++ b/src/interface/static/admin/css/responsive_rtl.css
@@ -0,0 +1,84 @@
+/* TABLETS */
+
+@media (max-width: 1024px) {
+ [dir="rtl"] .colMS {
+ margin-right: 0;
+ }
+
+ [dir="rtl"] #user-tools {
+ text-align: right;
+ }
+
+ [dir="rtl"] #changelist .actions label {
+ padding-left: 10px;
+ padding-right: 0;
+ }
+
+ [dir="rtl"] #changelist .actions select {
+ margin-left: 0;
+ margin-right: 15px;
+ }
+
+ [dir="rtl"] .change-list .filtered .results,
+ [dir="rtl"] .change-list .filtered .paginator,
+ [dir="rtl"] .filtered #toolbar,
+ [dir="rtl"] .filtered div.xfull,
+ [dir="rtl"] .filtered .actions {
+ margin-right: 0;
+ margin-left: 230px;
+ }
+
+ [dir="rtl"] .inline-group ul.tools a.add,
+ [dir="rtl"] .inline-group div.add-row a,
+ [dir="rtl"] .inline-group .tabular tr.add-row td a {
+ padding: 8px 26px 8px 10px;
+ background-position: calc(100% - 8px) 9px;
+ }
+
+ [dir="rtl"] .related-widget-wrapper-link + .selector {
+ margin-right: 0;
+ margin-left: 15px;
+ }
+
+ [dir="rtl"] .selector .selector-filter label {
+ margin-right: 0;
+ margin-left: 8px;
+ }
+
+ [dir="rtl"] .object-tools li {
+ float: right;
+ }
+
+ [dir="rtl"] .object-tools li + li {
+ margin-left: 0;
+ margin-right: 15px;
+ }
+
+ [dir="rtl"] .dashboard .module table td a {
+ padding-left: 0;
+ padding-right: 16px;
+ }
+}
+
+/* MOBILE */
+
+@media (max-width: 767px) {
+ [dir="rtl"] .change-list .filtered .results,
+ [dir="rtl"] .change-list .filtered .paginator,
+ [dir="rtl"] .filtered #toolbar,
+ [dir="rtl"] .filtered div.xfull,
+ [dir="rtl"] .filtered .actions {
+ margin-left: 0;
+ }
+
+ [dir="rtl"] .aligned .add-another,
+ [dir="rtl"] .aligned .related-lookup,
+ [dir="rtl"] .aligned .datetimeshortcuts {
+ margin-left: 0;
+ margin-right: 15px;
+ }
+
+ [dir="rtl"] .aligned ul {
+ margin-right: 0;
+ }
+}
diff --git a/src/interface/static/admin/css/rtl.css b/src/interface/static/admin/css/rtl.css
new file mode 100644
index 0000000..b9e26bf
--- /dev/null
+++ b/src/interface/static/admin/css/rtl.css
@@ -0,0 +1,269 @@
+body {
+ direction: rtl;
+}
+
+/* LOGIN */
+
+.login .form-row {
+ float: right;
+}
+
+.login .form-row label {
+ float: right;
+ padding-left: 0.5em;
+ padding-right: 0;
+ text-align: left;
+}
+
+.login .submit-row {
+ clear: both;
+ padding: 1em 9.4em 0 0;
+}
+
+/* GLOBAL */
+
+th {
+ text-align: right;
+}
+
+.module h2, .module caption {
+ text-align: right;
+}
+
+.module ul, .module ol {
+ margin-left: 0;
+ margin-right: 1.5em;
+}
+
+.viewlink, .addlink, .changelink {
+ padding-left: 0;
+ padding-right: 16px;
+ background-position: 100% 1px;
+}
+
+.deletelink {
+ padding-left: 0;
+ padding-right: 16px;
+ background-position: 100% 1px;
+}
+
+.object-tools {
+ float: left;
+}
+
+thead th:first-child,
+tfoot td:first-child {
+ border-left: none;
+}
+
+/* LAYOUT */
+
+#user-tools {
+ right: auto;
+ left: 0;
+ text-align: left;
+}
+
+div.breadcrumbs {
+ text-align: right;
+}
+
+#content-main {
+ float: right;
+}
+
+#content-related {
+ float: left;
+ margin-left: -300px;
+ margin-right: auto;
+}
+
+.colMS {
+ margin-left: 300px;
+ margin-right: 0;
+}
+
+/* SORTABLE TABLES */
+
+table thead th.sorted .sortoptions {
+ float: left;
+}
+
+thead th.sorted .text {
+ padding-right: 0;
+ padding-left: 42px;
+}
+
+/* dashboard styles */
+
+.dashboard .module table td a {
+ padding-left: .6em;
+ padding-right: 16px;
+}
+
+/* changelists styles */
+
+.change-list .filtered table {
+ border-left: none;
+ border-right: 0px none;
+}
+
+#changelist-filter {
+ right: auto;
+ left: 0;
+ border-left: none;
+ border-right: none;
+}
+
+.change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull {
+ margin-right: 0;
+ margin-left: 280px;
+}
+
+#changelist-filter li.selected {
+ border-left: none;
+ padding-left: 10px;
+ margin-left: 0;
+ border-right: 5px solid #eaeaea;
+ padding-right: 10px;
+ margin-right: -15px;
+}
+
+.filtered .actions {
+ margin-left: 280px;
+ margin-right: 0;
+}
+
+#changelist table tbody td:first-child, #changelist table tbody th:first-child {
+ border-right: none;
+ border-left: none;
+}
+
+/* FORMS */
+
+.aligned label {
+ padding: 0 0 3px 1em;
+ float: right;
+}
+
+.submit-row {
+ text-align: left
+}
+
+.submit-row p.deletelink-box {
+ float: right;
+}
+
+.submit-row input.default {
+ margin-left: 0;
+}
+
+.vDateField, .vTimeField {
+ margin-left: 2px;
+}
+
+.aligned .form-row input {
+ margin-left: 5px;
+}
+
+form .aligned p.help, form .aligned div.help {
+ clear: right;
+}
+
+form .aligned ul {
+ margin-right: 163px;
+ margin-left: 0;
+}
+
+form ul.inline li {
+ float: right;
+ padding-right: 0;
+ padding-left: 7px;
+}
+
+input[type=submit].default, .submit-row input.default {
+ float: left;
+}
+
+fieldset .fieldBox {
+ float: right;
+ margin-left: 20px;
+ margin-right: 0;
+}
+
+.errorlist li {
+ background-position: 100% 12px;
+ padding: 0;
+}
+
+.errornote {
+ background-position: 100% 12px;
+ padding: 10px 12px;
+}
+
+/* WIDGETS */
+
+.calendarnav-previous {
+ top: 0;
+ left: auto;
+ right: 10px;
+}
+
+.calendarnav-next {
+ top: 0;
+ right: auto;
+ left: 10px;
+}
+
+.calendar caption, .calendarbox h2 {
+ text-align: center;
+}
+
+.selector {
+ float: right;
+}
+
+.selector .selector-filter {
+ text-align: right;
+}
+
+.inline-deletelink {
+ float: left;
+}
+
+form .form-row p.datetime {
+ overflow: hidden;
+}
+
+.related-widget-wrapper {
+ float: right;
+}
+
+/* MISC */
+
+.inline-related h2, .inline-group h2 {
+ text-align: right
+}
+
+.inline-related h3 span.delete {
+ padding-right: 20px;
+ padding-left: inherit;
+ left: 10px;
+ right: inherit;
+ float:left;
+}
+
+.inline-related h3 span.delete label {
+ margin-left: inherit;
+ margin-right: 2px;
+}
+
+/* IE7 specific bug fixes */
+
+div.colM {
+ position: relative;
+}
+
+.submit-row input {
+ float: left;
+}
diff --git a/src/interface/static/admin/css/vendor/select2/LICENSE-SELECT2.md b/src/interface/static/admin/css/vendor/select2/LICENSE-SELECT2.md
new file mode 100644
index 0000000..86c7c29
--- /dev/null
+++ b/src/interface/static/admin/css/vendor/select2/LICENSE-SELECT2.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012-2015 Kevin Brown, Igor Vaynberg, and Select2 contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/interface/static/admin/css/vendor/select2/select2.css b/src/interface/static/admin/css/vendor/select2/select2.css
new file mode 100644
index 0000000..447b2b8
--- /dev/null
+++ b/src/interface/static/admin/css/vendor/select2/select2.css
@@ -0,0 +1,484 @@
+.select2-container {
+ box-sizing: border-box;
+ display: inline-block;
+ margin: 0;
+ position: relative;
+ vertical-align: middle; }
+ .select2-container .select2-selection--single {
+ box-sizing: border-box;
+ cursor: pointer;
+ display: block;
+ height: 28px;
+ user-select: none;
+ -webkit-user-select: none; }
+ .select2-container .select2-selection--single .select2-selection__rendered {
+ display: block;
+ padding-left: 8px;
+ padding-right: 20px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap; }
+ .select2-container .select2-selection--single .select2-selection__clear {
+ position: relative; }
+ .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
+ padding-right: 8px;
+ padding-left: 20px; }
+ .select2-container .select2-selection--multiple {
+ box-sizing: border-box;
+ cursor: pointer;
+ display: block;
+ min-height: 32px;
+ user-select: none;
+ -webkit-user-select: none; }
+ .select2-container .select2-selection--multiple .select2-selection__rendered {
+ display: inline-block;
+ overflow: hidden;
+ padding-left: 8px;
+ text-overflow: ellipsis;
+ white-space: nowrap; }
+ .select2-container .select2-search--inline {
+ float: left; }
+ .select2-container .select2-search--inline .select2-search__field {
+ box-sizing: border-box;
+ border: none;
+ font-size: 100%;
+ margin-top: 5px;
+ padding: 0; }
+ .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
+ -webkit-appearance: none; }
+
+.select2-dropdown {
+ background-color: white;
+ border: 1px solid #aaa;
+ border-radius: 4px;
+ box-sizing: border-box;
+ display: block;
+ position: absolute;
+ left: -100000px;
+ width: 100%;
+ z-index: 1051; }
+
+.select2-results {
+ display: block; }
+
+.select2-results__options {
+ list-style: none;
+ margin: 0;
+ padding: 0; }
+
+.select2-results__option {
+ padding: 6px;
+ user-select: none;
+ -webkit-user-select: none; }
+ .select2-results__option[aria-selected] {
+ cursor: pointer; }
+
+.select2-container--open .select2-dropdown {
+ left: 0; }
+
+.select2-container--open .select2-dropdown--above {
+ border-bottom: none;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0; }
+
+.select2-container--open .select2-dropdown--below {
+ border-top: none;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0; }
+
+.select2-search--dropdown {
+ display: block;
+ padding: 4px; }
+ .select2-search--dropdown .select2-search__field {
+ padding: 4px;
+ width: 100%;
+ box-sizing: border-box; }
+ .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
+ -webkit-appearance: none; }
+ .select2-search--dropdown.select2-search--hide {
+ display: none; }
+
+.select2-close-mask {
+ border: 0;
+ margin: 0;
+ padding: 0;
+ display: block;
+ position: fixed;
+ left: 0;
+ top: 0;
+ min-height: 100%;
+ min-width: 100%;
+ height: auto;
+ width: auto;
+ opacity: 0;
+ z-index: 99;
+ background-color: #fff;
+ filter: alpha(opacity=0); }
+
+.select2-hidden-accessible {
+ border: 0 !important;
+ clip: rect(0 0 0 0) !important;
+ height: 1px !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ padding: 0 !important;
+ position: absolute !important;
+ width: 1px !important; }
+
+.select2-container--default .select2-selection--single {
+ background-color: #fff;
+ border: 1px solid #aaa;
+ border-radius: 4px; }
+ .select2-container--default .select2-selection--single .select2-selection__rendered {
+ color: #444;
+ line-height: 28px; }
+ .select2-container--default .select2-selection--single .select2-selection__clear {
+ cursor: pointer;
+ float: right;
+ font-weight: bold; }
+ .select2-container--default .select2-selection--single .select2-selection__placeholder {
+ color: #999; }
+ .select2-container--default .select2-selection--single .select2-selection__arrow {
+ height: 26px;
+ position: absolute;
+ top: 1px;
+ right: 1px;
+ width: 20px; }
+ .select2-container--default .select2-selection--single .select2-selection__arrow b {
+ border-color: #888 transparent transparent transparent;
+ border-style: solid;
+ border-width: 5px 4px 0 4px;
+ height: 0;
+ left: 50%;
+ margin-left: -4px;
+ margin-top: -2px;
+ position: absolute;
+ top: 50%;
+ width: 0; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
+ float: left; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+ left: 1px;
+ right: auto; }
+
+.select2-container--default.select2-container--disabled .select2-selection--single {
+ background-color: #eee;
+ cursor: default; }
+ .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
+ display: none; }
+
+.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
+ border-color: transparent transparent #888 transparent;
+ border-width: 0 4px 5px 4px; }
+
+.select2-container--default .select2-selection--multiple {
+ background-color: white;
+ border: 1px solid #aaa;
+ border-radius: 4px;
+ cursor: text; }
+ .select2-container--default .select2-selection--multiple .select2-selection__rendered {
+ box-sizing: border-box;
+ list-style: none;
+ margin: 0;
+ padding: 0 5px;
+ width: 100%; }
+ .select2-container--default .select2-selection--multiple .select2-selection__rendered li {
+ list-style: none; }
+ .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
+ color: #999;
+ margin-top: 5px;
+ float: left; }
+ .select2-container--default .select2-selection--multiple .select2-selection__clear {
+ cursor: pointer;
+ float: right;
+ font-weight: bold;
+ margin-top: 5px;
+ margin-right: 10px; }
+ .select2-container--default .select2-selection--multiple .select2-selection__choice {
+ background-color: #e4e4e4;
+ border: 1px solid #aaa;
+ border-radius: 4px;
+ cursor: default;
+ float: left;
+ margin-right: 5px;
+ margin-top: 5px;
+ padding: 0 5px; }
+ .select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
+ color: #999;
+ cursor: pointer;
+ display: inline-block;
+ font-weight: bold;
+ margin-right: 2px; }
+ .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
+ color: #333; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
+ float: right; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+ margin-left: 5px;
+ margin-right: auto; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+ margin-left: 2px;
+ margin-right: auto; }
+
+.select2-container--default.select2-container--focus .select2-selection--multiple {
+ border: solid black 1px;
+ outline: 0; }
+
+.select2-container--default.select2-container--disabled .select2-selection--multiple {
+ background-color: #eee;
+ cursor: default; }
+
+.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
+ display: none; }
+
+.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0; }
+
+.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0; }
+
+.select2-container--default .select2-search--dropdown .select2-search__field {
+ border: 1px solid #aaa; }
+
+.select2-container--default .select2-search--inline .select2-search__field {
+ background: transparent;
+ border: none;
+ outline: 0;
+ box-shadow: none;
+ -webkit-appearance: textfield; }
+
+.select2-container--default .select2-results > .select2-results__options {
+ max-height: 200px;
+ overflow-y: auto; }
+
+.select2-container--default .select2-results__option[role=group] {
+ padding: 0; }
+
+.select2-container--default .select2-results__option[aria-disabled=true] {
+ color: #999; }
+
+.select2-container--default .select2-results__option[aria-selected=true] {
+ background-color: #ddd; }
+
+.select2-container--default .select2-results__option .select2-results__option {
+ padding-left: 1em; }
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__group {
+ padding-left: 0; }
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -1em;
+ padding-left: 2em; }
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -2em;
+ padding-left: 3em; }
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -3em;
+ padding-left: 4em; }
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -4em;
+ padding-left: 5em; }
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+ margin-left: -5em;
+ padding-left: 6em; }
+
+.select2-container--default .select2-results__option--highlighted[aria-selected] {
+ background-color: #5897fb;
+ color: white; }
+
+.select2-container--default .select2-results__group {
+ cursor: default;
+ display: block;
+ padding: 6px; }
+
+.select2-container--classic .select2-selection--single {
+ background-color: #f7f7f7;
+ border: 1px solid #aaa;
+ border-radius: 4px;
+ outline: 0;
+ background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
+ background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
+ background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+ .select2-container--classic .select2-selection--single:focus {
+ border: 1px solid #5897fb; }
+ .select2-container--classic .select2-selection--single .select2-selection__rendered {
+ color: #444;
+ line-height: 28px; }
+ .select2-container--classic .select2-selection--single .select2-selection__clear {
+ cursor: pointer;
+ float: right;
+ font-weight: bold;
+ margin-right: 10px; }
+ .select2-container--classic .select2-selection--single .select2-selection__placeholder {
+ color: #999; }
+ .select2-container--classic .select2-selection--single .select2-selection__arrow {
+ background-color: #ddd;
+ border: none;
+ border-left: 1px solid #aaa;
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ height: 26px;
+ position: absolute;
+ top: 1px;
+ right: 1px;
+ width: 20px;
+ background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+ background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+ background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
+ .select2-container--classic .select2-selection--single .select2-selection__arrow b {
+ border-color: #888 transparent transparent transparent;
+ border-style: solid;
+ border-width: 5px 4px 0 4px;
+ height: 0;
+ left: 50%;
+ margin-left: -4px;
+ margin-top: -2px;
+ position: absolute;
+ top: 50%;
+ width: 0; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
+ float: left; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+ border: none;
+ border-right: 1px solid #aaa;
+ border-radius: 0;
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ left: 1px;
+ right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--single {
+ border: 1px solid #5897fb; }
+ .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
+ background: transparent;
+ border: none; }
+ .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
+ border-color: transparent transparent #888 transparent;
+ border-width: 0 4px 5px 4px; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
+ border-top: none;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
+ background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
+ background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
+ border-bottom: none;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
+ background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
+ background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
+
+.select2-container--classic .select2-selection--multiple {
+ background-color: white;
+ border: 1px solid #aaa;
+ border-radius: 4px;
+ cursor: text;
+ outline: 0; }
+ .select2-container--classic .select2-selection--multiple:focus {
+ border: 1px solid #5897fb; }
+ .select2-container--classic .select2-selection--multiple .select2-selection__rendered {
+ list-style: none;
+ margin: 0;
+ padding: 0 5px; }
+ .select2-container--classic .select2-selection--multiple .select2-selection__clear {
+ display: none; }
+ .select2-container--classic .select2-selection--multiple .select2-selection__choice {
+ background-color: #e4e4e4;
+ border: 1px solid #aaa;
+ border-radius: 4px;
+ cursor: default;
+ float: left;
+ margin-right: 5px;
+ margin-top: 5px;
+ padding: 0 5px; }
+ .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
+ color: #888;
+ cursor: pointer;
+ display: inline-block;
+ font-weight: bold;
+ margin-right: 2px; }
+ .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
+ color: #555; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+ float: right; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+ margin-left: 5px;
+ margin-right: auto; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+ margin-left: 2px;
+ margin-right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--multiple {
+ border: 1px solid #5897fb; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
+ border-top: none;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0; }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
+ border-bottom: none;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0; }
+
+.select2-container--classic .select2-search--dropdown .select2-search__field {
+ border: 1px solid #aaa;
+ outline: 0; }
+
+.select2-container--classic .select2-search--inline .select2-search__field {
+ outline: 0;
+ box-shadow: none; }
+
+.select2-container--classic .select2-dropdown {
+ background-color: white;
+ border: 1px solid transparent; }
+
+.select2-container--classic .select2-dropdown--above {
+ border-bottom: none; }
+
+.select2-container--classic .select2-dropdown--below {
+ border-top: none; }
+
+.select2-container--classic .select2-results > .select2-results__options {
+ max-height: 200px;
+ overflow-y: auto; }
+
+.select2-container--classic .select2-results__option[role=group] {
+ padding: 0; }
+
+.select2-container--classic .select2-results__option[aria-disabled=true] {
+ color: grey; }
+
+.select2-container--classic .select2-results__option--highlighted[aria-selected] {
+ background-color: #3875d7;
+ color: white; }
+
+.select2-container--classic .select2-results__group {
+ cursor: default;
+ display: block;
+ padding: 6px; }
+
+.select2-container--classic.select2-container--open .select2-dropdown {
+ border-color: #5897fb; }
diff --git a/src/interface/static/admin/css/vendor/select2/select2.min.css b/src/interface/static/admin/css/vendor/select2/select2.min.css
new file mode 100644
index 0000000..76de04d
--- /dev/null
+++ b/src/interface/static/admin/css/vendor/select2/select2.min.css
@@ -0,0 +1 @@
+.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb}
diff --git a/src/interface/static/admin/css/widgets.css b/src/interface/static/admin/css/widgets.css
new file mode 100644
index 0000000..d3bd67a
--- /dev/null
+++ b/src/interface/static/admin/css/widgets.css
@@ -0,0 +1,565 @@
+/* SELECTOR (FILTER INTERFACE) */
+
+.selector {
+ width: 800px;
+ float: left;
+}
+
+.selector select {
+ width: 380px;
+ height: 17.2em;
+}
+
+.selector-available, .selector-chosen {
+ float: left;
+ width: 380px;
+ text-align: center;
+ margin-bottom: 5px;
+}
+
+.selector-chosen select {
+ border-top: none;
+}
+
+.selector-available h2, .selector-chosen h2 {
+ border: 1px solid #ccc;
+ border-radius: 4px 4px 0 0;
+}
+
+.selector-chosen h2 {
+ background: #79aec8;
+ color: #fff;
+}
+
+.selector .selector-available h2 {
+ background: #f8f8f8;
+ color: #666;
+}
+
+.selector .selector-filter {
+ background: white;
+ border: 1px solid #ccc;
+ border-width: 0 1px;
+ padding: 8px;
+ color: #999;
+ font-size: 10px;
+ margin: 0;
+ text-align: left;
+}
+
+.selector .selector-filter label,
+.inline-group .aligned .selector .selector-filter label {
+ float: left;
+ margin: 7px 0 0;
+ width: 18px;
+ height: 18px;
+ padding: 0;
+ overflow: hidden;
+ line-height: 1;
+}
+
+.selector .selector-available input {
+ width: 320px;
+ margin-left: 8px;
+}
+
+.selector ul.selector-chooser {
+ float: left;
+ width: 22px;
+ background-color: #eee;
+ border-radius: 10px;
+ margin: 10em 5px 0 5px;
+ padding: 0;
+}
+
+.selector-chooser li {
+ margin: 0;
+ padding: 3px;
+ list-style-type: none;
+}
+
+.selector select {
+ padding: 0 10px;
+ margin: 0 0 10px;
+ border-radius: 0 0 4px 4px;
+}
+
+.selector-add, .selector-remove {
+ width: 16px;
+ height: 16px;
+ display: block;
+ text-indent: -3000px;
+ overflow: hidden;
+ cursor: default;
+ opacity: 0.3;
+}
+
+.active.selector-add, .active.selector-remove {
+ opacity: 1;
+}
+
+.active.selector-add:hover, .active.selector-remove:hover {
+ cursor: pointer;
+}
+
+.selector-add {
+ background: url(../img/selector-icons.svg) 0 -96px no-repeat;
+}
+
+.active.selector-add:focus, .active.selector-add:hover {
+ background-position: 0 -112px;
+}
+
+.selector-remove {
+ background: url(../img/selector-icons.svg) 0 -64px no-repeat;
+}
+
+.active.selector-remove:focus, .active.selector-remove:hover {
+ background-position: 0 -80px;
+}
+
+a.selector-chooseall, a.selector-clearall {
+ display: inline-block;
+ height: 16px;
+ text-align: left;
+ margin: 1px auto 3px;
+ overflow: hidden;
+ font-weight: bold;
+ line-height: 16px;
+ color: #666;
+ text-decoration: none;
+ opacity: 0.3;
+}
+
+a.active.selector-chooseall:focus, a.active.selector-clearall:focus,
+a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
+ color: #447e9b;
+}
+
+a.active.selector-chooseall, a.active.selector-clearall {
+ opacity: 1;
+}
+
+a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
+ cursor: pointer;
+}
+
+a.selector-chooseall {
+ padding: 0 18px 0 0;
+ background: url(../img/selector-icons.svg) right -160px no-repeat;
+ cursor: default;
+}
+
+a.active.selector-chooseall:focus, a.active.selector-chooseall:hover {
+ background-position: 100% -176px;
+}
+
+a.selector-clearall {
+ padding: 0 0 0 18px;
+ background: url(../img/selector-icons.svg) 0 -128px no-repeat;
+ cursor: default;
+}
+
+a.active.selector-clearall:focus, a.active.selector-clearall:hover {
+ background-position: 0 -144px;
+}
+
+/* STACKED SELECTORS */
+
+.stacked {
+ float: left;
+ width: 490px;
+}
+
+.stacked select {
+ width: 480px;
+ height: 10.1em;
+}
+
+.stacked .selector-available, .stacked .selector-chosen {
+ width: 480px;
+}
+
+.stacked .selector-available {
+ margin-bottom: 0;
+}
+
+.stacked .selector-available input {
+ width: 422px;
+}
+
+.stacked ul.selector-chooser {
+ height: 22px;
+ width: 50px;
+ margin: 0 0 10px 40%;
+ background-color: #eee;
+ border-radius: 10px;
+}
+
+.stacked .selector-chooser li {
+ float: left;
+ padding: 3px 3px 3px 5px;
+}
+
+.stacked .selector-chooseall, .stacked .selector-clearall {
+ display: none;
+}
+
+.stacked .selector-add {
+ background: url(../img/selector-icons.svg) 0 -32px no-repeat;
+ cursor: default;
+}
+
+.stacked .active.selector-add {
+ background-position: 0 -48px;
+ cursor: pointer;
+}
+
+.stacked .selector-remove {
+ background: url(../img/selector-icons.svg) 0 0 no-repeat;
+ cursor: default;
+}
+
+.stacked .active.selector-remove {
+ background-position: 0 -16px;
+ cursor: pointer;
+}
+
+.selector .help-icon {
+ background: url(../img/icon-unknown.svg) 0 0 no-repeat;
+ display: inline-block;
+ vertical-align: middle;
+ margin: -2px 0 0 2px;
+ width: 13px;
+ height: 13px;
+}
+
+.selector .selector-chosen .help-icon {
+ background: url(../img/icon-unknown-alt.svg) 0 0 no-repeat;
+}
+
+.selector .search-label-icon {
+ background: url(../img/search.svg) 0 0 no-repeat;
+ display: inline-block;
+ height: 18px;
+ width: 18px;
+}
+
+/* DATE AND TIME */
+
+p.datetime {
+ line-height: 20px;
+ margin: 0;
+ padding: 0;
+ color: #666;
+ font-weight: bold;
+}
+
+.datetime span {
+ white-space: nowrap;
+ font-weight: normal;
+ font-size: 11px;
+ color: #ccc;
+}
+
+.datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
+ min-width: 0;
+ margin-left: 5px;
+ margin-bottom: 4px;
+}
+
+table p.datetime {
+ font-size: 11px;
+ margin-left: 0;
+ padding-left: 0;
+}
+
+.datetimeshortcuts .clock-icon, .datetimeshortcuts .date-icon {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ height: 16px;
+ width: 16px;
+ overflow: hidden;
+}
+
+.datetimeshortcuts .clock-icon {
+ background: url(../img/icon-clock.svg) 0 0 no-repeat;
+}
+
+.datetimeshortcuts a:focus .clock-icon,
+.datetimeshortcuts a:hover .clock-icon {
+ background-position: 0 -16px;
+}
+
+.datetimeshortcuts .date-icon {
+ background: url(../img/icon-calendar.svg) 0 0 no-repeat;
+ top: -1px;
+}
+
+.datetimeshortcuts a:focus .date-icon,
+.datetimeshortcuts a:hover .date-icon {
+ background-position: 0 -16px;
+}
+
+.timezonewarning {
+ font-size: 11px;
+ color: #999;
+}
+
+/* URL */
+
+p.url {
+ line-height: 20px;
+ margin: 0;
+ padding: 0;
+ color: #666;
+ font-size: 11px;
+ font-weight: bold;
+}
+
+.url a {
+ font-weight: normal;
+}
+
+/* FILE UPLOADS */
+
+p.file-upload {
+ line-height: 20px;
+ margin: 0;
+ padding: 0;
+ color: #666;
+ font-size: 11px;
+ font-weight: bold;
+}
+
+.aligned p.file-upload {
+ margin-left: 170px;
+}
+
+.file-upload a {
+ font-weight: normal;
+}
+
+.file-upload .deletelink {
+ margin-left: 5px;
+}
+
+span.clearable-file-input label {
+ color: #333;
+ font-size: 11px;
+ display: inline;
+ float: none;
+}
+
+/* CALENDARS & CLOCKS */
+
+.calendarbox, .clockbox {
+ margin: 5px auto;
+ font-size: 12px;
+ width: 19em;
+ text-align: center;
+ background: white;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
+ overflow: hidden;
+ position: relative;
+}
+
+.clockbox {
+ width: auto;
+}
+
+.calendar {
+ margin: 0;
+ padding: 0;
+}
+
+.calendar table {
+ margin: 0;
+ padding: 0;
+ border-collapse: collapse;
+ background: white;
+ width: 100%;
+}
+
+.calendar caption, .calendarbox h2 {
+ margin: 0;
+ text-align: center;
+ border-top: none;
+ background: #f5dd5d;
+ font-weight: 700;
+ font-size: 12px;
+ color: #333;
+}
+
+.calendar th {
+ padding: 8px 5px;
+ background: #f8f8f8;
+ border-bottom: 1px solid #ddd;
+ font-weight: 400;
+ font-size: 12px;
+ text-align: center;
+ color: #666;
+}
+
+.calendar td {
+ font-weight: 400;
+ font-size: 12px;
+ text-align: center;
+ padding: 0;
+ border-top: 1px solid #eee;
+ border-bottom: none;
+}
+
+.calendar td.selected a {
+ background: #79aec8;
+ color: #fff;
+}
+
+.calendar td.nonday {
+ background: #f8f8f8;
+}
+
+.calendar td.today a {
+ font-weight: 700;
+}
+
+.calendar td a, .timelist a {
+ display: block;
+ font-weight: 400;
+ padding: 6px;
+ text-decoration: none;
+ color: #444;
+}
+
+.calendar td a:focus, .timelist a:focus,
+.calendar td a:hover, .timelist a:hover {
+ background: #79aec8;
+ color: white;
+}
+
+.calendar td a:active, .timelist a:active {
+ background: #417690;
+ color: white;
+}
+
+.calendarnav {
+ font-size: 10px;
+ text-align: center;
+ color: #ccc;
+ margin: 0;
+ padding: 1px 3px;
+}
+
+.calendarnav a:link, #calendarnav a:visited,
+#calendarnav a:focus, #calendarnav a:hover {
+ color: #999;
+}
+
+.calendar-shortcuts {
+ background: white;
+ font-size: 11px;
+ line-height: 11px;
+ border-top: 1px solid #eee;
+ padding: 8px 0;
+ color: #ccc;
+}
+
+.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
+ display: block;
+ position: absolute;
+ top: 8px;
+ width: 15px;
+ height: 15px;
+ text-indent: -9999px;
+ padding: 0;
+}
+
+.calendarnav-previous {
+ left: 10px;
+ background: url(../img/calendar-icons.svg) 0 0 no-repeat;
+}
+
+.calendarbox .calendarnav-previous:focus,
+.calendarbox .calendarnav-previous:hover {
+ background-position: 0 -15px;
+}
+
+.calendarnav-next {
+ right: 10px;
+ background: url(../img/calendar-icons.svg) 0 -30px no-repeat;
+}
+
+.calendarbox .calendarnav-next:focus,
+.calendarbox .calendarnav-next:hover {
+ background-position: 0 -45px;
+}
+
+.calendar-cancel {
+ margin: 0;
+ padding: 4px 0;
+ font-size: 12px;
+ background: #eee;
+ border-top: 1px solid #ddd;
+ color: #333;
+}
+
+.calendar-cancel:focus, .calendar-cancel:hover {
+ background: #ddd;
+}
+
+.calendar-cancel a {
+ color: black;
+ display: block;
+}
+
+ul.timelist, .timelist li {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+.timelist a {
+ padding: 2px;
+}
+
+/* EDIT INLINE */
+
+.inline-deletelink {
+ float: right;
+ text-indent: -9999px;
+ background: url(../img/inline-delete.svg) 0 0 no-repeat;
+ width: 16px;
+ height: 16px;
+ border: 0px none;
+}
+
+.inline-deletelink:focus, .inline-deletelink:hover {
+ cursor: pointer;
+}
+
+/* RELATED WIDGET WRAPPER */
+.related-widget-wrapper {
+ float: left; /* display properly in form rows with multiple fields */
+ overflow: hidden; /* clear floated contents */
+}
+
+.related-widget-wrapper-link {
+ opacity: 0.3;
+}
+
+.related-widget-wrapper-link:link {
+ opacity: .8;
+}
+
+.related-widget-wrapper-link:link:focus,
+.related-widget-wrapper-link:link:hover {
+ opacity: 1;
+}
+
+select + .related-widget-wrapper-link,
+.related-widget-wrapper-link + .related-widget-wrapper-link {
+ margin-left: 7px;
+}
diff --git a/src/interface/static/admin/fonts/LICENSE.txt b/src/interface/static/admin/fonts/LICENSE.txt
new file mode 100644
index 0000000..75b5248
--- /dev/null
+++ b/src/interface/static/admin/fonts/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/src/interface/static/admin/fonts/README.txt b/src/interface/static/admin/fonts/README.txt
new file mode 100644
index 0000000..b247bef
--- /dev/null
+++ b/src/interface/static/admin/fonts/README.txt
@@ -0,0 +1,3 @@
+Roboto webfont source: https://www.google.com/fonts/specimen/Roboto
+WOFF files extracted using https://github.com/majodev/google-webfonts-helper
+Weights used in this project: Light (300), Regular (400), Bold (700)
diff --git a/src/interface/static/admin/fonts/Roboto-Bold-webfont.woff b/src/interface/static/admin/fonts/Roboto-Bold-webfont.woff
new file mode 100644
index 0000000..6e0f562
Binary files /dev/null and b/src/interface/static/admin/fonts/Roboto-Bold-webfont.woff differ
diff --git a/src/interface/static/admin/fonts/Roboto-Light-webfont.woff b/src/interface/static/admin/fonts/Roboto-Light-webfont.woff
new file mode 100644
index 0000000..b9e9918
Binary files /dev/null and b/src/interface/static/admin/fonts/Roboto-Light-webfont.woff differ
diff --git a/src/interface/static/admin/fonts/Roboto-Regular-webfont.woff b/src/interface/static/admin/fonts/Roboto-Regular-webfont.woff
new file mode 100644
index 0000000..96c1986
Binary files /dev/null and b/src/interface/static/admin/fonts/Roboto-Regular-webfont.woff differ
diff --git a/src/interface/static/admin/img/LICENSE b/src/interface/static/admin/img/LICENSE
new file mode 100644
index 0000000..a4faaa1
--- /dev/null
+++ b/src/interface/static/admin/img/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Code Charm Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/interface/static/admin/img/README.txt b/src/interface/static/admin/img/README.txt
new file mode 100644
index 0000000..4eb2e49
--- /dev/null
+++ b/src/interface/static/admin/img/README.txt
@@ -0,0 +1,7 @@
+All icons are taken from Font Awesome (http://fontawesome.io/) project.
+The Font Awesome font is licensed under the SIL OFL 1.1:
+- https://scripts.sil.org/OFL
+
+SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG
+Font-Awesome-SVG-PNG is licensed under the MIT license (see file license
+in current folder).
diff --git a/src/interface/static/admin/img/calendar-icons.svg b/src/interface/static/admin/img/calendar-icons.svg
new file mode 100644
index 0000000..dbf21c3
--- /dev/null
+++ b/src/interface/static/admin/img/calendar-icons.svg
@@ -0,0 +1,14 @@
+
diff --git a/src/interface/static/admin/img/gis/move_vertex_off.svg b/src/interface/static/admin/img/gis/move_vertex_off.svg
new file mode 100644
index 0000000..c9d16e0
--- /dev/null
+++ b/src/interface/static/admin/img/gis/move_vertex_off.svg
@@ -0,0 +1 @@
+
diff --git a/src/interface/static/admin/img/gis/move_vertex_on.svg b/src/interface/static/admin/img/gis/move_vertex_on.svg
new file mode 100644
index 0000000..12f81ae
--- /dev/null
+++ b/src/interface/static/admin/img/gis/move_vertex_on.svg
@@ -0,0 +1 @@
+
diff --git a/src/interface/static/admin/img/icon-addlink.svg b/src/interface/static/admin/img/icon-addlink.svg
new file mode 100644
index 0000000..e004fb1
--- /dev/null
+++ b/src/interface/static/admin/img/icon-addlink.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/icon-alert.svg b/src/interface/static/admin/img/icon-alert.svg
new file mode 100644
index 0000000..e51ea83
--- /dev/null
+++ b/src/interface/static/admin/img/icon-alert.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/icon-calendar.svg b/src/interface/static/admin/img/icon-calendar.svg
new file mode 100644
index 0000000..97910a9
--- /dev/null
+++ b/src/interface/static/admin/img/icon-calendar.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/interface/static/admin/img/icon-changelink.svg b/src/interface/static/admin/img/icon-changelink.svg
new file mode 100644
index 0000000..bbb137a
--- /dev/null
+++ b/src/interface/static/admin/img/icon-changelink.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/icon-clock.svg b/src/interface/static/admin/img/icon-clock.svg
new file mode 100644
index 0000000..bf9985d
--- /dev/null
+++ b/src/interface/static/admin/img/icon-clock.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/interface/static/admin/img/icon-deletelink.svg b/src/interface/static/admin/img/icon-deletelink.svg
new file mode 100644
index 0000000..4059b15
--- /dev/null
+++ b/src/interface/static/admin/img/icon-deletelink.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/icon-no.svg b/src/interface/static/admin/img/icon-no.svg
new file mode 100644
index 0000000..2e0d383
--- /dev/null
+++ b/src/interface/static/admin/img/icon-no.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/icon-unknown-alt.svg b/src/interface/static/admin/img/icon-unknown-alt.svg
new file mode 100644
index 0000000..1c6b99f
--- /dev/null
+++ b/src/interface/static/admin/img/icon-unknown-alt.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/icon-unknown.svg b/src/interface/static/admin/img/icon-unknown.svg
new file mode 100644
index 0000000..50b4f97
--- /dev/null
+++ b/src/interface/static/admin/img/icon-unknown.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/icon-viewlink.svg b/src/interface/static/admin/img/icon-viewlink.svg
new file mode 100644
index 0000000..a1ca1d3
--- /dev/null
+++ b/src/interface/static/admin/img/icon-viewlink.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/icon-yes.svg b/src/interface/static/admin/img/icon-yes.svg
new file mode 100644
index 0000000..5883d87
--- /dev/null
+++ b/src/interface/static/admin/img/icon-yes.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/inline-delete.svg b/src/interface/static/admin/img/inline-delete.svg
new file mode 100644
index 0000000..17d1ad6
--- /dev/null
+++ b/src/interface/static/admin/img/inline-delete.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/search.svg b/src/interface/static/admin/img/search.svg
new file mode 100644
index 0000000..c8c69b2
--- /dev/null
+++ b/src/interface/static/admin/img/search.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/selector-icons.svg b/src/interface/static/admin/img/selector-icons.svg
new file mode 100644
index 0000000..926b8e2
--- /dev/null
+++ b/src/interface/static/admin/img/selector-icons.svg
@@ -0,0 +1,34 @@
+
diff --git a/src/interface/static/admin/img/sorting-icons.svg b/src/interface/static/admin/img/sorting-icons.svg
new file mode 100644
index 0000000..7c31ec9
--- /dev/null
+++ b/src/interface/static/admin/img/sorting-icons.svg
@@ -0,0 +1,19 @@
+
diff --git a/src/interface/static/admin/img/tooltag-add.svg b/src/interface/static/admin/img/tooltag-add.svg
new file mode 100644
index 0000000..1ca64ae
--- /dev/null
+++ b/src/interface/static/admin/img/tooltag-add.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/img/tooltag-arrowright.svg b/src/interface/static/admin/img/tooltag-arrowright.svg
new file mode 100644
index 0000000..b664d61
--- /dev/null
+++ b/src/interface/static/admin/img/tooltag-arrowright.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/interface/static/admin/js/SelectBox.js b/src/interface/static/admin/js/SelectBox.js
new file mode 100644
index 0000000..2073f03
--- /dev/null
+++ b/src/interface/static/admin/js/SelectBox.js
@@ -0,0 +1,144 @@
+(function($) {
+ 'use strict';
+ var SelectBox = {
+ cache: {},
+ init: function(id) {
+ var box = document.getElementById(id);
+ var node;
+ SelectBox.cache[id] = [];
+ var cache = SelectBox.cache[id];
+ var boxOptions = box.options;
+ var boxOptionsLength = boxOptions.length;
+ for (var i = 0, j = boxOptionsLength; i < j; i++) {
+ node = boxOptions[i];
+ cache.push({value: node.value, text: node.text, displayed: 1});
+ }
+ },
+ redisplay: function(id) {
+ // Repopulate HTML select box from cache
+ var box = document.getElementById(id);
+ var node;
+ $(box).empty(); // clear all options
+ var new_options = box.outerHTML.slice(0, -9); // grab just the opening tag
+ var cache = SelectBox.cache[id];
+ for (var i = 0, j = cache.length; i < j; i++) {
+ node = cache[i];
+ if (node.displayed) {
+ var new_option = new Option(node.text, node.value, false, false);
+ // Shows a tooltip when hovering over the option
+ new_option.setAttribute("title", node.text);
+ new_options += new_option.outerHTML;
+ }
+ }
+ new_options += '';
+ box.outerHTML = new_options;
+ },
+ filter: function(id, text) {
+ // Redisplay the HTML select box, displaying only the choices containing ALL
+ // the words in text. (It's an AND search.)
+ var tokens = text.toLowerCase().split(/\s+/);
+ var node, token;
+ var cache = SelectBox.cache[id];
+ for (var i = 0, j = cache.length; i < j; i++) {
+ node = cache[i];
+ node.displayed = 1;
+ var node_text = node.text.toLowerCase();
+ var numTokens = tokens.length;
+ for (var k = 0; k < numTokens; k++) {
+ token = tokens[k];
+ if (node_text.indexOf(token) === -1) {
+ node.displayed = 0;
+ break; // Once the first token isn't found we're done
+ }
+ }
+ }
+ SelectBox.redisplay(id);
+ },
+ delete_from_cache: function(id, value) {
+ var node, delete_index = null;
+ var cache = SelectBox.cache[id];
+ for (var i = 0, j = cache.length; i < j; i++) {
+ node = cache[i];
+ if (node.value === value) {
+ delete_index = i;
+ break;
+ }
+ }
+ cache.splice(delete_index, 1);
+ },
+ add_to_cache: function(id, option) {
+ SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1});
+ },
+ cache_contains: function(id, value) {
+ // Check if an item is contained in the cache
+ var node;
+ var cache = SelectBox.cache[id];
+ for (var i = 0, j = cache.length; i < j; i++) {
+ node = cache[i];
+ if (node.value === value) {
+ return true;
+ }
+ }
+ return false;
+ },
+ move: function(from, to) {
+ var from_box = document.getElementById(from);
+ var option;
+ var boxOptions = from_box.options;
+ var boxOptionsLength = boxOptions.length;
+ for (var i = 0, j = boxOptionsLength; i < j; i++) {
+ option = boxOptions[i];
+ var option_value = option.value;
+ if (option.selected && SelectBox.cache_contains(from, option_value)) {
+ SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1});
+ SelectBox.delete_from_cache(from, option_value);
+ }
+ }
+ SelectBox.redisplay(from);
+ SelectBox.redisplay(to);
+ },
+ move_all: function(from, to) {
+ var from_box = document.getElementById(from);
+ var option;
+ var boxOptions = from_box.options;
+ var boxOptionsLength = boxOptions.length;
+ for (var i = 0, j = boxOptionsLength; i < j; i++) {
+ option = boxOptions[i];
+ var option_value = option.value;
+ if (SelectBox.cache_contains(from, option_value)) {
+ SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1});
+ SelectBox.delete_from_cache(from, option_value);
+ }
+ }
+ SelectBox.redisplay(from);
+ SelectBox.redisplay(to);
+ },
+ sort: function(id) {
+ SelectBox.cache[id].sort(function(a, b) {
+ a = a.text.toLowerCase();
+ b = b.text.toLowerCase();
+ try {
+ if (a > b) {
+ return 1;
+ }
+ if (a < b) {
+ return -1;
+ }
+ }
+ catch (e) {
+ // silently fail on IE 'unknown' exception
+ }
+ return 0;
+ } );
+ },
+ select_all: function(id) {
+ var box = document.getElementById(id);
+ var boxOptions = box.options;
+ var boxOptionsLength = boxOptions.length;
+ for (var i = 0; i < boxOptionsLength; i++) {
+ boxOptions[i].selected = 'selected';
+ }
+ }
+ };
+ window.SelectBox = SelectBox;
+})(django.jQuery);
diff --git a/src/interface/static/admin/js/SelectFilter2.js b/src/interface/static/admin/js/SelectFilter2.js
new file mode 100644
index 0000000..4221778
--- /dev/null
+++ b/src/interface/static/admin/js/SelectFilter2.js
@@ -0,0 +1,246 @@
+/*global SelectBox, gettext, interpolate, quickElement, SelectFilter*/
+/*
+SelectFilter2 - Turns a multiple-select box into a filter interface.
+
+Requires jQuery, core.js, and SelectBox.js.
+*/
+(function($) {
+ 'use strict';
+ function findForm(node) {
+ // returns the node of the form containing the given node
+ if (node.tagName.toLowerCase() !== 'form') {
+ return findForm(node.parentNode);
+ }
+ return node;
+ }
+
+ window.SelectFilter = {
+ init: function(field_id, field_name, is_stacked) {
+ if (field_id.match(/__prefix__/)) {
+ // Don't initialize on empty forms.
+ return;
+ }
+ var from_box = document.getElementById(field_id);
+ from_box.id += '_from'; // change its ID
+ from_box.className = 'filtered';
+
+ var ps = from_box.parentNode.getElementsByTagName('p');
+ for (var i = 0; i < ps.length; i++) {
+ if (ps[i].className.indexOf("info") !== -1) {
+ // Remove
, because it just gets in the way.
+ from_box.parentNode.removeChild(ps[i]);
+ } else if (ps[i].className.indexOf("help") !== -1) {
+ // Move help text up to the top so it isn't below the select
+ // boxes or wrapped off on the side to the right of the add
+ // button:
+ from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild);
+ }
+ }
+
+ //
");
+ addButton = $this.filter(":last").next().find("a");
+ }
+ }
+ addButton.on('click', function(e) {
+ e.preventDefault();
+ var template = $("#" + options.prefix + "-empty");
+ var row = template.clone(true);
+ row.removeClass(options.emptyCssClass)
+ .addClass(options.formCssClass)
+ .attr("id", options.prefix + "-" + nextIndex);
+ if (row.is("tr")) {
+ // If the forms are laid out in table rows, insert
+ // the remove button into the last table cell:
+ row.children(":last").append('