import argparse import bs4 import epubfile import flask; from flask import request import hashlib import jinja2 import mimetypes import sys from voussoirkit import betterhelp from voussoirkit import cacheclass from voussoirkit import pathclass from voussoirkit import vlogging log = vlogging.get_logger(__name__, 'epubserver') books = cacheclass.Cache(maxlen=100) site = flask.Flask(__name__) def md5_string(s): return hashlib.md5(s.encode('utf-8')).hexdigest() def navpoint_to_li(soup, navpoint, srcbase, depth): li = soup.new_tag('li') a = soup.new_tag('a') a['href'] = '/' + srcbase.join(navpoint.content['src']).absolute_path a['class'] = f'toc_depth_{depth}' a['data-depth'] = str(depth) a.string = navpoint.navLabel.text.strip() children = list(navpoint.find_all('navPoint', recursive=False)) if children: details = soup.new_tag('details') summary = soup.new_tag('summary') summary.append(a) details.append(summary) li.append(details) ol = soup.new_tag('ol') ol['class'] = f'toc_depth_{depth+1}' for navpoint in children: ol.append(navpoint_to_li(soup, navpoint, srcbase, depth+1)) details.append(ol) else: li.append(a) return li def prepare_reader(book): reader = bs4.BeautifulSoup('''
'''.replace('{BOOK_ID}', md5_string(book.root_directory.normcase)), 'html.parser') ncx_id = book.get_ncx() toc = reader.find('section', {'id': 'table_of_contents'}) if ncx_id: ncx_filepath = book.get_filepath(ncx_id) ncx = book.read_file(ncx_id, soup=True) srcbase = ncx_filepath.parent for navpoint in list(ncx.navMap.find_all('navPoint', recursive=False)): toc.ol.append(navpoint_to_li(reader, navpoint, srcbase, depth=1)) book._reader = reader def get_book(epub_path): if epub_path in books: return books[epub_path] book = epubfile.Epub(epub_path, read_only=True) book._epubserver_manifest = book.get_texts(soup=True, skip_nav=True) print(book._epubserver_manifest) book._epubserver_manifest_ids = [x['id'] for x in book._epubserver_manifest] book._epubserver_reverse_manifest = { book.get_filepath(item['id']): item for item in book._epubserver_manifest } prepare_reader(book) books[epub_path] = book return book def epubserver_flask(port, *args, **kwargs): @site.route('/') def root(): response = jinja2.Template(''' ''').render() return response @site.route('/') def mainroute(path): if path.endswith('.epub'): path += '/' try: ix = path.lower().index('.epub/') except ValueError: return flask.abort(404, 'URL does not contain epub.') else: epub_path = path[:ix+5] request_path = path[ix+6:] try: book = get_book(epub_path) except FileNotFoundError: return flask.abort(404, 'Could not open book.') if request_path == '': return str(book._reader) if request_path in book._epubserver_manifest: return book.read_file(request_path) filepath = book.root_directory.join(request_path) if filepath not in book.root_directory: return flask.abort(404) try: log.debug('Trying filepath %s', filepath) content = book._fopen(filepath, 'rb').read() except Exception: return flask.abort(404) manifest_item = book._epubserver_reverse_manifest.get(filepath) if manifest_item is not None: mime = manifest_item['media-type'] else: mime = mimetypes.guess_type(request_path)[0] response = flask.make_response(content) if mime: response.headers['Content-Type'] = mime else: response.headers['Content-Type'] = None response.headers['Cache-Control'] = 'max-age=600' return response site.run(host='0.0.0.0', port=port) def epubserver_argparse(args): epubserver_flask(args.port) return 0 @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser( description=''' ''', ) parser.add_argument( 'port', type=int, ) parser.set_defaults(func=epubserver_argparse) return betterhelp.go(parser, argv) if __name__ == '__main__': raise SystemExit(main(sys.argv[1:]))