mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-26 10:00:37 +00:00
Merge branch 'master' into development
This commit is contained in:
commit
e0b8fe3b1a
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -31,7 +31,7 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
- OS: [e.g. Windows 10/Raspberry Pi OS]
|
- OS: [e.g. Windows 10/Raspberry Pi OS]
|
||||||
- Python version: [e.g. python2.7]
|
- Python version: [e.g. python2.7]
|
||||||
- Calibre-Web version: [e.g. 0.6.8 or 087c4c59 (git rev-parse --short HEAD)]:
|
- Calibre-Web version: [e.g. 0.6.8 or 087c4c59 (git rev-parse --short HEAD)]:
|
||||||
- Docker container: [None/Technosoft2000/Linuxuser]:
|
- Docker container: [None/Technosoft2000/LinuxServer]:
|
||||||
- Special Hardware: [e.g. Rasperry Pi Zero]
|
- Special Hardware: [e.g. Rasperry Pi Zero]
|
||||||
- Browser: [e.g. Chrome 83.0.4103.97, Safari 13.3.7, Firefox 68.0.1 ESR]
|
- Browser: [e.g. Chrome 83.0.4103.97, Safari 13.3.7, Firefox 68.0.1 ESR]
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ Calibre-Web is a web app providing a clean interface for browsing, reading and d
|
|||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
1. Install dependencies by running `pip3 install --target vendor -r requirements.txt` (python3.x) or `pip install --target vendor -r requirements.txt` (python2.7).
|
1. Install dependencies by running `pip3 install --target vendor -r requirements.txt` (python3.x). Alternativly set up a python virtual environment.
|
||||||
2. Execute the command: `python cps.py` (or `nohup python cps.py` - recommended if you want to exit the terminal window)
|
2. Execute the command: `python3 cps.py` (or `nohup python3 cps.py` - recommended if you want to exit the terminal window)
|
||||||
3. Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog
|
3. Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog
|
||||||
4. Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button\
|
4. Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button\
|
||||||
Optionally a Google Drive can be used to host the calibre library [-> Using Google Drive integration](https://github.com/janeczku/calibre-web/wiki/Configuration#using-google-drive-integration)
|
Optionally a Google Drive can be used to host the calibre library [-> Using Google Drive integration](https://github.com/janeczku/calibre-web/wiki/Configuration#using-google-drive-integration)
|
||||||
@ -48,7 +48,7 @@ Please note that running the above install command can fail on some versions of
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
python 3.x+, (Python 2.7+)
|
python 3.x+
|
||||||
|
|
||||||
Optionally, to enable on-the-fly conversion from one ebook format to another when using the send-to-kindle feature, or during editing of ebooks metadata:
|
Optionally, to enable on-the-fly conversion from one ebook format to another when using the send-to-kindle feature, or during editing of ebooks metadata:
|
||||||
|
|
||||||
|
2
cps.py
2
cps.py
@ -31,7 +31,7 @@ else:
|
|||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vendor'))
|
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vendor'))
|
||||||
|
|
||||||
|
|
||||||
from cps import create_app, config
|
from cps import create_app
|
||||||
from cps import web_server
|
from cps import web_server
|
||||||
from cps.opds import opds
|
from cps.opds import opds
|
||||||
from cps.web import web
|
from cps.web import web
|
||||||
|
@ -45,6 +45,7 @@ mimetypes.add_type('application/fb2+zip', '.fb2')
|
|||||||
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
|
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
|
||||||
mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
|
mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
|
||||||
mimetypes.add_type('application/vnd.amazon.ebook', '.azw')
|
mimetypes.add_type('application/vnd.amazon.ebook', '.azw')
|
||||||
|
mimetypes.add_type('application/x-mobi8-ebook', '.azw3')
|
||||||
mimetypes.add_type('application/x-cbr', '.cbr')
|
mimetypes.add_type('application/x-cbr', '.cbr')
|
||||||
mimetypes.add_type('application/x-cbz', '.cbz')
|
mimetypes.add_type('application/x-cbz', '.cbz')
|
||||||
mimetypes.add_type('application/x-cbt', '.cbt')
|
mimetypes.add_type('application/x-cbt', '.cbt')
|
||||||
@ -98,6 +99,9 @@ def create_app():
|
|||||||
cache_buster.init_cache_busting(app)
|
cache_buster.init_cache_busting(app)
|
||||||
|
|
||||||
log.info('Starting Calibre Web...')
|
log.info('Starting Calibre Web...')
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
log.info('Python2 is EOL since end of 2019, this version of Calibre-Web supporting Python2 please consider upgrading to Python3')
|
||||||
|
print('Python2 is EOL since end of 2019, this version of Calibre-Web supporting Python2 please consider upgrading to Python3')
|
||||||
Principal(app)
|
Principal(app)
|
||||||
lm.init_app(app)
|
lm.init_app(app)
|
||||||
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
||||||
|
@ -636,7 +636,7 @@ def pathchooser():
|
|||||||
"parentdir": parentdir,
|
"parentdir": parentdir,
|
||||||
"type": browse_for,
|
"type": browse_for,
|
||||||
"oldfile": oldfile,
|
"oldfile": oldfile,
|
||||||
"absolute": abs,
|
"absolute": absolute,
|
||||||
}
|
}
|
||||||
return json.dumps(context)
|
return json.dumps(context)
|
||||||
|
|
||||||
|
@ -110,7 +110,10 @@ if ipadress:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# handle and check user password argument
|
# handle and check user password argument
|
||||||
user_password = args.s or None
|
user_credentials = args.s or None
|
||||||
|
if user_credentials and ":" not in user_credentials:
|
||||||
|
print("No valid username:password format")
|
||||||
|
sys.exit(3)
|
||||||
|
|
||||||
# Handles enableing of filepicker
|
# Handles enableing of filepicker
|
||||||
filepicker = args.f or None
|
filepicker = args.f or None
|
||||||
|
@ -21,6 +21,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
# if installed via pip this variable is set to true
|
||||||
HOME_CONFIG = False
|
HOME_CONFIG = False
|
||||||
|
|
||||||
# Base dir is parent of current file, necessary if called from different folder
|
# Base dir is parent of current file, necessary if called from different folder
|
||||||
@ -130,7 +131,7 @@ def selected_roles(dictionary):
|
|||||||
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
|
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
|
||||||
'series_id, languages')
|
'series_id, languages')
|
||||||
|
|
||||||
STABLE_VERSION = {'version': '0.6.10 Beta'}
|
STABLE_VERSION = {'version': '0.6.11 Beta'}
|
||||||
|
|
||||||
NIGHTLY_VERSION = {}
|
NIGHTLY_VERSION = {}
|
||||||
NIGHTLY_VERSION[0] = '$Format:%H$'
|
NIGHTLY_VERSION[0] = '$Format:%H$'
|
||||||
|
12
cps/db.py
12
cps/db.py
@ -50,6 +50,8 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
use_unidecode = False
|
use_unidecode = False
|
||||||
|
|
||||||
|
log = logger.create()
|
||||||
|
|
||||||
cc_exceptions = ['datetime', 'comments', 'composite', 'series']
|
cc_exceptions = ['datetime', 'comments', 'composite', 'series']
|
||||||
cc_classes = {}
|
cc_classes = {}
|
||||||
|
|
||||||
@ -402,6 +404,9 @@ class AlchemyEncoder(json.JSONEncoder):
|
|||||||
el.append(ele.get())
|
el.append(ele.get())
|
||||||
else:
|
else:
|
||||||
el.append(json.dumps(ele, cls=AlchemyEncoder))
|
el.append(json.dumps(ele, cls=AlchemyEncoder))
|
||||||
|
if field == 'authors':
|
||||||
|
data = " & ".join(el)
|
||||||
|
else:
|
||||||
data = ",".join(el)
|
data = ",".join(el)
|
||||||
if data == '[]':
|
if data == '[]':
|
||||||
data = ""
|
data = ""
|
||||||
@ -619,11 +624,14 @@ class CalibreDB():
|
|||||||
.join(*join, isouter=True) \
|
.join(*join, isouter=True) \
|
||||||
.filter(db_filter) \
|
.filter(db_filter) \
|
||||||
.filter(self.common_filters(allow_show_archived))
|
.filter(self.common_filters(allow_show_archived))
|
||||||
|
try:
|
||||||
pagination = Pagination(page, pagesize,
|
pagination = Pagination(page, pagesize,
|
||||||
len(query.all()))
|
len(query.all()))
|
||||||
entries = query.order_by(*order).offset(off).limit(pagesize).all()
|
entries = query.order_by(*order).offset(off).limit(pagesize).all()
|
||||||
for book in entries:
|
except Exception as e:
|
||||||
book = self.order_authors(book)
|
log.debug_or_exception(e)
|
||||||
|
#for book in entries:
|
||||||
|
# book = self.order_authors(book)
|
||||||
return entries, randm, pagination
|
return entries, randm, pagination
|
||||||
|
|
||||||
# Orders all Authors in the list according to authors sort
|
# Orders all Authors in the list according to authors sort
|
||||||
|
@ -30,8 +30,8 @@ from uuid import uuid4
|
|||||||
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
|
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from sqlalchemy.exc import OperationalError
|
from sqlalchemy.exc import OperationalError, IntegrityError
|
||||||
|
from sqlite3 import OperationalError as sqliteOperationalError
|
||||||
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
|
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
|
||||||
from . import config, get_locale, ub, db
|
from . import config, get_locale, ub, db
|
||||||
from . import calibre_db
|
from . import calibre_db
|
||||||
@ -310,7 +310,6 @@ def delete_book(book_id, book_format, jsonResponse):
|
|||||||
|
|
||||||
|
|
||||||
def render_edit_book(book_id):
|
def render_edit_book(book_id):
|
||||||
calibre_db.update_title_sort(config)
|
|
||||||
cc = calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
cc = calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||||
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||||
if not book:
|
if not book:
|
||||||
@ -570,7 +569,7 @@ def upload_single_file(request, book, book_id):
|
|||||||
calibre_db.session.add(db_format)
|
calibre_db.session.add(db_format)
|
||||||
calibre_db.session.commit()
|
calibre_db.session.commit()
|
||||||
calibre_db.update_title_sort(config)
|
calibre_db.update_title_sort(config)
|
||||||
except OperationalError as e:
|
except (OperationalError, IntegrityError) as e:
|
||||||
calibre_db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
log.error('Database error: %s', e)
|
log.error('Database error: %s', e)
|
||||||
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
||||||
@ -607,12 +606,19 @@ def upload_cover(request, book):
|
|||||||
@edit_required
|
@edit_required
|
||||||
def edit_book(book_id):
|
def edit_book(book_id):
|
||||||
modif_date = False
|
modif_date = False
|
||||||
|
|
||||||
|
# create the function for sorting...
|
||||||
|
try:
|
||||||
|
calibre_db.update_title_sort(config)
|
||||||
|
except sqliteOperationalError as e:
|
||||||
|
log.debug_or_exception(e)
|
||||||
|
calibre_db.session.rollback()
|
||||||
|
|
||||||
# Show form
|
# Show form
|
||||||
if request.method != 'POST':
|
if request.method != 'POST':
|
||||||
return render_edit_book(book_id)
|
return render_edit_book(book_id)
|
||||||
|
|
||||||
# create the function for sorting...
|
|
||||||
calibre_db.update_title_sort(config)
|
|
||||||
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||||
|
|
||||||
# Book not found
|
# Book not found
|
||||||
@ -925,7 +931,7 @@ def upload():
|
|||||||
else:
|
else:
|
||||||
resp = {"location": url_for('web.show_book', book_id=book_id)}
|
resp = {"location": url_for('web.show_book', book_id=book_id)}
|
||||||
return Response(json.dumps(resp), mimetype='application/json')
|
return Response(json.dumps(resp), mimetype='application/json')
|
||||||
except OperationalError as e:
|
except (OperationalError, IntegrityError) as e:
|
||||||
calibre_db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
log.error("Database error: %s", e)
|
log.error("Database error: %s", e)
|
||||||
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
||||||
|
@ -394,7 +394,8 @@ def uploadFileToEbooksFolder(destFile, f):
|
|||||||
if len(existingFiles) > 0:
|
if len(existingFiles) > 0:
|
||||||
driveFile = existingFiles[0]
|
driveFile = existingFiles[0]
|
||||||
else:
|
else:
|
||||||
driveFile = drive.CreateFile({'title': x, 'parents': [{"kind": "drive#fileLink", 'id': parent['id']}],})
|
driveFile = drive.CreateFile({'title': x,
|
||||||
|
'parents': [{"kind": "drive#fileLink", 'id': parent['id']}], })
|
||||||
driveFile.SetContentFile(f)
|
driveFile.SetContentFile(f)
|
||||||
driveFile.Upload()
|
driveFile.Upload()
|
||||||
else:
|
else:
|
||||||
|
@ -32,7 +32,7 @@ from tempfile import gettempdir
|
|||||||
import requests
|
import requests
|
||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
from babel.units import format_unit
|
from babel.units import format_unit
|
||||||
from flask import send_from_directory, make_response, redirect, abort, url_for, send_file
|
from flask import send_from_directory, make_response, redirect, abort, url_for, request
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from sqlalchemy.sql.expression import true, false, and_, text
|
from sqlalchemy.sql.expression import true, false, and_, text
|
||||||
@ -68,6 +68,7 @@ try:
|
|||||||
except (ImportError, RuntimeError) as e:
|
except (ImportError, RuntimeError) as e:
|
||||||
log.debug('Cannot import Image, generating covers from non jpg files will not work: %s', e)
|
log.debug('Cannot import Image, generating covers from non jpg files will not work: %s', e)
|
||||||
use_IM = False
|
use_IM = False
|
||||||
|
MissingDelegateError = BaseException
|
||||||
|
|
||||||
|
|
||||||
# Convert existing book entry to new format
|
# Convert existing book entry to new format
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -43,7 +43,6 @@ from flask_login import current_user
|
|||||||
from werkzeug.datastructures import Headers
|
from werkzeug.datastructures import Headers
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.sql.expression import and_, or_
|
from sqlalchemy.sql.expression import and_, or_
|
||||||
from sqlalchemy.exc import OperationalError
|
|
||||||
from sqlalchemy.orm import load_only
|
from sqlalchemy.orm import load_only
|
||||||
from sqlalchemy.exc import StatementError
|
from sqlalchemy.exc import StatementError
|
||||||
import requests
|
import requests
|
||||||
@ -58,7 +57,7 @@ KOBO_FORMATS = {"KEPUB": ["KEPUB"], "EPUB": ["EPUB3", "EPUB"]}
|
|||||||
KOBO_STOREAPI_URL = "https://storeapi.kobo.com"
|
KOBO_STOREAPI_URL = "https://storeapi.kobo.com"
|
||||||
KOBO_IMAGEHOST_URL = "https://kbimages1-a.akamaihd.net"
|
KOBO_IMAGEHOST_URL = "https://kbimages1-a.akamaihd.net"
|
||||||
|
|
||||||
SYNC_ITEM_LIMIT = 5
|
SYNC_ITEM_LIMIT = 100
|
||||||
|
|
||||||
kobo = Blueprint("kobo", __name__, url_prefix="/kobo/<auth_token>")
|
kobo = Blueprint("kobo", __name__, url_prefix="/kobo/<auth_token>")
|
||||||
kobo_auth.disable_failed_auth_redirect_for_blueprint(kobo)
|
kobo_auth.disable_failed_auth_redirect_for_blueprint(kobo)
|
||||||
|
@ -66,7 +66,6 @@ from os import urandom
|
|||||||
from flask import g, Blueprint, url_for, abort, request
|
from flask import g, Blueprint, url_for, abort, request
|
||||||
from flask_login import login_user, login_required
|
from flask_login import login_user, login_required
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from sqlalchemy.exc import OperationalError
|
|
||||||
|
|
||||||
from . import logger, ub, lm
|
from . import logger, ub, lm
|
||||||
from .render_template import render_title_template
|
from .render_template import render_title_template
|
||||||
|
@ -44,10 +44,22 @@ logging.addLevelName(logging.CRITICAL, "CRIT")
|
|||||||
class _Logger(logging.Logger):
|
class _Logger(logging.Logger):
|
||||||
|
|
||||||
def debug_or_exception(self, message, *args, **kwargs):
|
def debug_or_exception(self, message, *args, **kwargs):
|
||||||
|
if sys.version_info > (3, 7):
|
||||||
if is_debug_enabled():
|
if is_debug_enabled():
|
||||||
self.exception(message, stacklevel=2, *args, **kwargs)
|
self.exception(message, stacklevel=2, *args, **kwargs)
|
||||||
else:
|
else:
|
||||||
self.error(message, stacklevel=2, *args, **kwargs)
|
self.error(message, stacklevel=2, *args, **kwargs)
|
||||||
|
elif sys.version_info > (3, 0):
|
||||||
|
if is_debug_enabled():
|
||||||
|
self.exception(message, stack_info=True, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
self.error(message, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
if is_debug_enabled():
|
||||||
|
self.exception(message, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
self.error(message, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def debug_no_auth(self, message, *args, **kwargs):
|
def debug_no_auth(self, message, *args, **kwargs):
|
||||||
if message.startswith("send: AUTH"):
|
if message.startswith("send: AUTH"):
|
||||||
|
@ -32,7 +32,6 @@ from flask_dance.contrib.github import make_github_blueprint, github
|
|||||||
from flask_dance.contrib.google import make_google_blueprint, google
|
from flask_dance.contrib.google import make_google_blueprint, google
|
||||||
from flask_login import login_user, current_user, login_required
|
from flask_login import login_user, current_user, login_required
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
from sqlalchemy.exc import OperationalError
|
|
||||||
|
|
||||||
from . import constants, logger, config, app, ub
|
from . import constants, logger, config, app, ub
|
||||||
|
|
||||||
|
@ -430,7 +430,12 @@ def check_auth(username, password):
|
|||||||
username = username.encode('utf-8')
|
username = username.encode('utf-8')
|
||||||
user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) ==
|
user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) ==
|
||||||
username.decode('utf-8').lower()).first()
|
username.decode('utf-8').lower()).first()
|
||||||
return bool(user and check_password_hash(str(user.password), password))
|
if bool(user and check_password_hash(str(user.password), password)):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||||
|
log.warning('OPDS Login failed for user "%s" IP-address: %s', username.decode('utf-8'), ipAdress)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def authenticate():
|
def authenticate():
|
||||||
|
@ -635,6 +635,14 @@ function init(filename) {
|
|||||||
// Focus the scrollable area so that keyboard scrolling work as expected
|
// Focus the scrollable area so that keyboard scrolling work as expected
|
||||||
$("#mainContent").focus();
|
$("#mainContent").focus();
|
||||||
|
|
||||||
|
$("#mainContent").swipe( {
|
||||||
|
swipeRight:function() {
|
||||||
|
showLeftPage();
|
||||||
|
},
|
||||||
|
swipeLeft:function() {
|
||||||
|
showRightPage();
|
||||||
|
},
|
||||||
|
});
|
||||||
$("#mainImage").click(function(evt) {
|
$("#mainImage").click(function(evt) {
|
||||||
// Firefox does not support offsetX/Y so we have to manually calculate
|
// Firefox does not support offsetX/Y so we have to manually calculate
|
||||||
// where the user clicked in the image.
|
// where the user clicked in the image.
|
||||||
|
@ -82,7 +82,6 @@ $(".container-fluid").bind('drop', function (e) {
|
|||||||
var files = e.originalEvent.dataTransfer.files;
|
var files = e.originalEvent.dataTransfer.files;
|
||||||
var test = $("#btn-upload")[0].accept;
|
var test = $("#btn-upload")[0].accept;
|
||||||
$(this).css('background', '');
|
$(this).css('background', '');
|
||||||
// var final = [];
|
|
||||||
const dt = new DataTransfer()
|
const dt = new DataTransfer()
|
||||||
jQuery.each(files, function (index, item) {
|
jQuery.each(files, function (index, item) {
|
||||||
if (test.indexOf(item.name.substr(item.name.lastIndexOf('.'))) !== -1) {
|
if (test.indexOf(item.name.substr(item.name.lastIndexOf('.'))) !== -1) {
|
||||||
|
@ -20,6 +20,34 @@ var reader;
|
|||||||
$("#bookmark, #show-Bookmarks").remove();
|
$("#bookmark, #show-Bookmarks").remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable swipe support
|
||||||
|
// I have no idea why swiperRight/swiperLeft from plugins is not working, events just don't get fired
|
||||||
|
var touchStart = 0;
|
||||||
|
var touchEnd = 0;
|
||||||
|
|
||||||
|
reader.rendition.on('touchstart', function(event) {
|
||||||
|
touchStart = event.changedTouches[0].screenX;
|
||||||
|
});
|
||||||
|
reader.rendition.on('touchend', function(event) {
|
||||||
|
touchEnd = event.changedTouches[0].screenX;
|
||||||
|
if (touchStart < touchEnd) {
|
||||||
|
if(reader.book.package.metadata.direction === "rtl") {
|
||||||
|
reader.rendition.next();
|
||||||
|
} else {
|
||||||
|
reader.rendition.prev();
|
||||||
|
}
|
||||||
|
// Swiped Right
|
||||||
|
}
|
||||||
|
if (touchStart > touchEnd) {
|
||||||
|
if(reader.book.package.metadata.direction === "rtl") {
|
||||||
|
reader.rendition.prev();
|
||||||
|
} else {
|
||||||
|
reader.rendition.next();
|
||||||
|
}
|
||||||
|
// Swiped Left
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} action - Add or remove bookmark
|
* @param {string} action - Add or remove bookmark
|
||||||
* @param {string|int} location - Location or zero
|
* @param {string|int} location - Location or zero
|
||||||
@ -43,3 +71,5 @@ var reader;
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if not filepicker %}
|
{% if not filepicker %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label id="filepicker-hint">{{_('To activate serverside filepicker start Calibre-Web with -f optionn')}}</label>
|
<label id="filepicker-hint">{{_('To activate serverside filepicker start Calibre-Web with -f option')}}</label>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if feature_support['gdrive'] %}
|
{% if feature_support['gdrive'] %}
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overlay"></div>
|
<div class="overlay"></div>
|
||||||
|
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/libs/jszip.min.js') }}">
|
<script src="{{ url_for('static', filename='js/libs/jszip.min.js') }}">
|
||||||
</script> <script src="{{ url_for('static', filename='js/libs/epub.min.js') }}"></script>
|
</script> <script src="{{ url_for('static', filename='js/libs/epub.min.js') }}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -91,10 +91,7 @@
|
|||||||
useBookmarks: "{{ g.user.is_authenticated | tojson }}"
|
useBookmarks: "{{ g.user.is_authenticated | tojson }}"
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/libs/jszip.min.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/libs/epub.min.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/libs/reader.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/libs/reader.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/reading/epub.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/reading/epub.js') }}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -13,14 +13,10 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/kthoom.css') }}" type="text/css"/>
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/kthoom.css') }}" type="text/css"/>
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/libs/plugins.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/io/bytestream.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/io/bytebuffer.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/io/bitstream.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/archive/archive.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/archive/rarvm.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/archive/unrar5.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/kthoom.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/kthoom.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/archive/archive.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
var updateArrows = function() {
|
var updateArrows = function() {
|
||||||
if ($('input[name="direction"]:checked').val() === "0") {
|
if ($('input[name="direction"]:checked').val() === "0") {
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
<!-- EPUBJS Renderer -->
|
<!-- EPUBJS Renderer -->
|
||||||
<!--<script src="../build/epub.js"></script>-->
|
<!--<script src="../build/epub.js"></script>-->
|
||||||
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/libs/plugins.js') }}"></script>
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
|
|
||||||
@ -97,6 +98,14 @@
|
|||||||
$( "#right" ).click(function() {
|
$( "#right" ).click(function() {
|
||||||
nextPage();
|
nextPage();
|
||||||
});
|
});
|
||||||
|
$("#readmain").swipe( {
|
||||||
|
swipeRight:function() {
|
||||||
|
prevPage();
|
||||||
|
},
|
||||||
|
swipeLeft:function() {
|
||||||
|
nextPage();
|
||||||
|
},
|
||||||
|
});
|
||||||
//bind mouse
|
//bind mouse
|
||||||
$(window).bind('DOMMouseScroll mousewheel', function(event) {
|
$(window).bind('DOMMouseScroll mousewheel', function(event) {
|
||||||
var delta = 0;
|
var delta = 0;
|
||||||
|
@ -98,7 +98,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-sm-6">
|
<div class="form-group col-sm-6">
|
||||||
<div><label for="include_extension">{{_('Extensions')}}</label></div>
|
<div><label for="include_extension">{{_('Extensions')}}</label></div>
|
||||||
<select id="include_extension" class="selectpicker" name="include_extension" id="include_extension" data-live-search="true" data-style="btn-primary" multiple>
|
<select class="selectpicker" name="include_extension" id="include_extension" data-live-search="true" data-style="btn-primary" multiple>
|
||||||
{% for extension in extensions %}
|
{% for extension in extensions %}
|
||||||
<option value="{{extension.format}}">{{extension.format}}</option>
|
<option value="{{extension.format}}">{{extension.format}}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -106,7 +106,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group col-sm-6">
|
<div class="form-group col-sm-6">
|
||||||
<div><label for="exclude_extension">{{_('Exclude Extensions')}}</label></div>
|
<div><label for="exclude_extension">{{_('Exclude Extensions')}}</label></div>
|
||||||
<select id="exclude_extension" class="selectpicker" name="exclude_extension" id="exclude_extension" data-live-search="true" data-style="btn-danger" multiple>
|
<select class="selectpicker" name="exclude_extension" id="exclude_extension" data-live-search="true" data-style="btn-danger" multiple>
|
||||||
{% for extension in extensions %}
|
{% for extension in extensions %}
|
||||||
<option value="{{extension.format}}">{{extension.format}}</option>
|
<option value="{{extension.format}}">{{extension.format}}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
25
cps/ub.py
25
cps/ub.py
@ -43,10 +43,11 @@ from sqlalchemy import Column, ForeignKey
|
|||||||
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, JSON
|
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, JSON
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
|
from sqlalchemy.sql.expression import func
|
||||||
from sqlalchemy.orm import backref, relationship, sessionmaker, Session, scoped_session
|
from sqlalchemy.orm import backref, relationship, sessionmaker, Session, scoped_session
|
||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
from . import constants, logger
|
from . import constants, logger, cli
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
@ -226,9 +227,6 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||||||
self.denied_column_value = data.denied_column_value
|
self.denied_column_value = data.denied_column_value
|
||||||
self.allowed_column_value = data.allowed_column_value
|
self.allowed_column_value = data.allowed_column_value
|
||||||
self.view_settings = data.view_settings
|
self.view_settings = data.view_settings
|
||||||
# Initialize flask_session once
|
|
||||||
if 'view' not in flask_session:
|
|
||||||
flask_session['view']={}
|
|
||||||
|
|
||||||
|
|
||||||
def role_admin(self):
|
def role_admin(self):
|
||||||
@ -247,14 +245,18 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def get_view_property(self, page, prop):
|
def get_view_property(self, page, prop):
|
||||||
|
if 'view' in flask_session:
|
||||||
if not flask_session['view'].get(page):
|
if not flask_session['view'].get(page):
|
||||||
return None
|
return None
|
||||||
return flask_session['view'][page].get(prop)
|
return flask_session['view'][page].get(prop)
|
||||||
|
return None
|
||||||
|
|
||||||
def set_view_property(self, page, prop, value):
|
def set_view_property(self, page, prop, value):
|
||||||
|
if 'view' in flask_session:
|
||||||
if not flask_session['view'].get(page):
|
if not flask_session['view'].get(page):
|
||||||
flask_session['view'][page] = dict()
|
flask_session['view'][page] = dict()
|
||||||
flask_session['view'][page][prop] = value
|
flask_session['view'][page][prop] = value
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Baseclass representing Shelfs in calibre-web in app.db
|
# Baseclass representing Shelfs in calibre-web in app.db
|
||||||
@ -680,6 +682,21 @@ def init_db(app_db_path):
|
|||||||
create_admin_user(session)
|
create_admin_user(session)
|
||||||
create_anonymous_user(session)
|
create_anonymous_user(session)
|
||||||
|
|
||||||
|
if cli.user_credentials:
|
||||||
|
username, password = cli.user_credentials.split(':')
|
||||||
|
user = session.query(User).filter(func.lower(User.nickname) == username.lower()).first()
|
||||||
|
if user:
|
||||||
|
user.password = generate_password_hash(password)
|
||||||
|
if session_commit() == "":
|
||||||
|
print("Password for user '{}' changed".format(username))
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("Failed changing password")
|
||||||
|
sys.exit(3)
|
||||||
|
else:
|
||||||
|
print("Username '{}' not valid, can't change password".format(username))
|
||||||
|
sys.exit(3)
|
||||||
|
|
||||||
|
|
||||||
def dispose():
|
def dispose():
|
||||||
global session
|
global session
|
||||||
|
24
cps/web.py
24
cps/web.py
@ -24,6 +24,7 @@ from __future__ import division, print_function, unicode_literals
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import chardet # dependency of requests
|
import chardet # dependency of requests
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
|||||||
|
|
||||||
from . import constants, logger, isoLanguages, services
|
from . import constants, logger, isoLanguages, services
|
||||||
from . import babel, db, ub, config, get_locale, app
|
from . import babel, db, ub, config, get_locale, app
|
||||||
from . import calibre_db, shelf
|
from . import calibre_db
|
||||||
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
||||||
from .helper import check_valid_domain, render_task_status, \
|
from .helper import check_valid_domain, render_task_status, \
|
||||||
get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \
|
get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \
|
||||||
@ -184,7 +185,7 @@ def toggle_read(book_id):
|
|||||||
calibre_db.session.commit()
|
calibre_db.session.commit()
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column)
|
log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column)
|
||||||
except OperationalError as e:
|
except (OperationalError, InvalidRequestError) as e:
|
||||||
calibre_db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
log.error(u"Read status could not set: %e", e)
|
log.error(u"Read status could not set: %e", e)
|
||||||
|
|
||||||
@ -1198,7 +1199,7 @@ def serve_book(book_id, book_format, anyname):
|
|||||||
book = calibre_db.get_book(book_id)
|
book = calibre_db.get_book(book_id)
|
||||||
data = calibre_db.get_book_format(book_id, book_format.upper())
|
data = calibre_db.get_book_format(book_id, book_format.upper())
|
||||||
if not data:
|
if not data:
|
||||||
abort(404)
|
return "File not in Database"
|
||||||
log.info('Serving book: %s', data.name)
|
log.info('Serving book: %s', data.name)
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
headers = Headers()
|
headers = Headers()
|
||||||
@ -1207,11 +1208,14 @@ def serve_book(book_id, book_format, anyname):
|
|||||||
return do_gdrive_download(df, headers, (book_format.upper() == 'TXT'))
|
return do_gdrive_download(df, headers, (book_format.upper() == 'TXT'))
|
||||||
else:
|
else:
|
||||||
if book_format.upper() == 'TXT':
|
if book_format.upper() == 'TXT':
|
||||||
|
try:
|
||||||
rawdata = open(os.path.join(config.config_calibre_dir, book.path, data.name + "." + book_format),
|
rawdata = open(os.path.join(config.config_calibre_dir, book.path, data.name + "." + book_format),
|
||||||
"rb").read()
|
"rb").read()
|
||||||
result = chardet.detect(rawdata)
|
result = chardet.detect(rawdata)
|
||||||
return make_response(
|
return make_response(
|
||||||
rawdata.decode(result['encoding']).encode('utf-8'))
|
rawdata.decode(result['encoding']).encode('utf-8'))
|
||||||
|
except FileNotFoundError:
|
||||||
|
return "File Not Found"
|
||||||
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
|
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
|
||||||
|
|
||||||
|
|
||||||
@ -1270,11 +1274,17 @@ def register():
|
|||||||
if config.config_register_email:
|
if config.config_register_email:
|
||||||
nickname = to_save["email"]
|
nickname = to_save["email"]
|
||||||
else:
|
else:
|
||||||
nickname = to_save["nickname"]
|
nickname = to_save.get('nickname', None)
|
||||||
if not nickname or not to_save["email"]:
|
if not nickname or not to_save.get("email", None):
|
||||||
flash(_(u"Please fill out all fields!"), category="error")
|
flash(_(u"Please fill out all fields!"), category="error")
|
||||||
return render_title_template('register.html', title=_(u"register"), page="register")
|
return render_title_template('register.html', title=_(u"register"), page="register")
|
||||||
|
#if to_save["email"].count("@") != 1 or not \
|
||||||
|
# Regex according to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#validation
|
||||||
|
if not re.search(r"^[\w.!#$%&'*+\\/=?^_`{|}~-]+@[\w](?:[\w-]{0,61}[\w])?(?:\.[\w](?:[\w-]{0,61}[\w])?)*$",
|
||||||
|
to_save["email"]):
|
||||||
|
flash(_(u"Invalid e-mail address format"), category="error")
|
||||||
|
log.warning('Registering failed for user "%s" e-mail address: %s', nickname, to_save["email"])
|
||||||
|
return render_title_template('register.html', title=_(u"register"), page="register")
|
||||||
|
|
||||||
existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == nickname
|
existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == nickname
|
||||||
.lower()).first()
|
.lower()).first()
|
||||||
@ -1300,7 +1310,7 @@ def register():
|
|||||||
return render_title_template('register.html', title=_(u"register"), page="register")
|
return render_title_template('register.html', title=_(u"register"), page="register")
|
||||||
else:
|
else:
|
||||||
flash(_(u"Your e-mail is not allowed to register"), category="error")
|
flash(_(u"Your e-mail is not allowed to register"), category="error")
|
||||||
log.warning('Registering failed for user "%s" e-mail address: %s', to_save['nickname'], to_save["email"])
|
log.warning('Registering failed for user "%s" e-mail address: %s', nickname, to_save["email"])
|
||||||
return render_title_template('register.html', title=_(u"register"), page="register")
|
return render_title_template('register.html', title=_(u"register"), page="register")
|
||||||
flash(_(u"Confirmation e-mail was send to your e-mail account."), category="success")
|
flash(_(u"Confirmation e-mail was send to your e-mail account."), category="success")
|
||||||
return redirect(url_for('web.login'))
|
return redirect(url_for('web.login'))
|
||||||
|
396
messages.pot
396
messages.pot
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
# GDrive Integration
|
# GDrive Integration
|
||||||
google-api-python-client==1.7.11,<1.8.0
|
google-api-python-client>=1.7.11,<1.8.0
|
||||||
gevent>=1.2.1,<20.6.0
|
gevent>=1.2.1,<20.6.0
|
||||||
greenlet>=0.4.12,<0.4.17
|
greenlet>=0.4.12,<0.4.17
|
||||||
httplib2>=0.9.2,<0.18.0
|
httplib2>=0.9.2,<0.18.0
|
||||||
@ -9,7 +9,7 @@ pyasn1-modules>=0.0.8,<0.3.0
|
|||||||
pyasn1>=0.1.9,<0.5.0
|
pyasn1>=0.1.9,<0.5.0
|
||||||
PyDrive2>=1.3.1,<1.8.0
|
PyDrive2>=1.3.1,<1.8.0
|
||||||
PyYAML>=3.12
|
PyYAML>=3.12
|
||||||
rsa==3.4.2,<4.1.0
|
rsa>=3.4.2,<4.1.0
|
||||||
six>=1.10.0,<1.15.0
|
six>=1.10.0,<1.15.0
|
||||||
|
|
||||||
# goodreads
|
# goodreads
|
||||||
@ -30,7 +30,7 @@ rarfile>=2.7
|
|||||||
|
|
||||||
# other
|
# other
|
||||||
natsort>=2.2.0,<7.1.0
|
natsort>=2.2.0,<7.1.0
|
||||||
git+https://github.com/OzzieIsaacs/comicapi.git@7aa91ea95ea7a919df6c6cac817865434a31c228#egg=comicapi
|
comicapi>= 2.1.3,<2.2.0
|
||||||
|
|
||||||
#Kobo integration
|
#Kobo integration
|
||||||
jsonschema>=3.2.0,<3.3.0
|
jsonschema>=3.2.0,<3.3.0
|
||||||
|
@ -6,7 +6,7 @@ singledispatch>=3.4.0.0,<3.5.0.0
|
|||||||
backports_abc>=0.4
|
backports_abc>=0.4
|
||||||
Flask>=1.0.2,<1.2.0
|
Flask>=1.0.2,<1.2.0
|
||||||
iso-639>=0.4.5,<0.5.0
|
iso-639>=0.4.5,<0.5.0
|
||||||
PyPDF2==1.26.0,<1.27.0
|
PyPDF2>=1.26.0,<1.27.0
|
||||||
pytz>=2016.10
|
pytz>=2016.10
|
||||||
requests>=2.11.1,<2.25.0
|
requests>=2.11.1,<2.25.0
|
||||||
SQLAlchemy>=1.3.0,<1.4.0
|
SQLAlchemy>=1.3.0,<1.4.0
|
||||||
|
77
setup.cfg
77
setup.cfg
@ -1,6 +1,3 @@
|
|||||||
[bdist_wheel]
|
|
||||||
universal = 1
|
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
name = calibreweb
|
name = calibreweb
|
||||||
url = https://github.com/janeczku/calibre-web
|
url = https://github.com/janeczku/calibre-web
|
||||||
@ -20,8 +17,6 @@ license_file = LICENSE
|
|||||||
classifiers =
|
classifiers =
|
||||||
Development Status :: 5 - Production/Stable
|
Development Status :: 5 - Production/Stable
|
||||||
License :: OSI Approved :: GNU Affero General Public License v3
|
License :: OSI Approved :: GNU Affero General Public License v3
|
||||||
Programming Language :: Python :: 2
|
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
Programming Language :: Python :: 3
|
||||||
Programming Language :: Python :: 3.5
|
Programming Language :: Python :: 3.5
|
||||||
Programming Language :: Python :: 3.6
|
Programming Language :: Python :: 3.6
|
||||||
@ -31,57 +26,61 @@ keywords =
|
|||||||
calibre
|
calibre
|
||||||
calibre-web
|
calibre-web
|
||||||
library
|
library
|
||||||
python_requires = >=2.6
|
python_requires = >=3.0
|
||||||
|
|
||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
cps = calibreweb:main
|
cps = calibreweb:main
|
||||||
[options]
|
[options]
|
||||||
include_package_data = True
|
include_package_data = True
|
||||||
dependency_links = comicapi @ git+https://github.com/OzzieIsaacs/comicapi.git@5346716578b2843f54d522f44d01bc8d25001d24#egg=comicapi
|
|
||||||
install_requires =
|
install_requires =
|
||||||
Babel >= 1.3
|
Babel>=1.3, <2.9
|
||||||
Flask-Babel >= 0.11.1
|
Flask-Babel>=0.11.1,<2.1.0
|
||||||
Flask-Login >= 0.3.2
|
Flask-Login>=0.3.2,<0.5.1
|
||||||
Flask-Principal >= 0.3.2
|
Flask-Principal>=0.3.2,<0.5.1
|
||||||
singledispatch >= 3.4.0.0
|
singledispatch>=3.4.0.0,<3.5.0.0
|
||||||
backports_abc>=0.4
|
backports_abc>=0.4
|
||||||
Flask >= 0.11
|
Flask>=1.0.2,<1.2.0
|
||||||
iso-639 >= 0.4.5
|
iso-639>=0.4.5,<0.5.0
|
||||||
PyPDF2 == 1.26.0
|
PyPDF2>=1.26.0,<1.27.0
|
||||||
pytz>=2016.10
|
pytz>=2016.10
|
||||||
requests >= 2.11.1
|
requests>=2.11.1,<2.25.0
|
||||||
SQLAlchemy >= 1.1.0
|
SQLAlchemy>=1.3.0,<1.4.0
|
||||||
tornado >= 4.1
|
tornado>=4.1,<6.2
|
||||||
unidecode >= 0.04.19
|
Wand>=0.4.4,<0.7.0
|
||||||
|
unidecode>=0.04.19,<1.2.0
|
||||||
|
|
||||||
[options.extras_require]
|
[options.extras_require]
|
||||||
gdrive =
|
gdrive =
|
||||||
google-api-python-client == 1.6.1
|
google-api-python-client>=1.7.11,<1.8.0
|
||||||
google-api-python-client==1.6.1
|
gevent>=1.2.1,<20.6.0
|
||||||
gevent==1.2.1
|
greenlet>=0.4.12,<0.4.17
|
||||||
greenlet==0.4.12
|
httplib2>=0.9.2,<0.18.0
|
||||||
httplib2==0.9.2
|
oauth2client>=4.0.0,<4.1.4
|
||||||
oauth2client==4.0.0
|
uritemplate>=3.0.0,<3.1.0
|
||||||
uritemplate==3.0.0
|
pyasn1-modules>=0.0.8,<0.3.0
|
||||||
pyasn1-modules==0.0.8
|
pyasn1>=0.1.9,<0.5.0
|
||||||
pyasn1==0.1.9
|
PyDrive2>=1.3.1,<1.8.0
|
||||||
PyDrive==1.3.1
|
PyYAML>=3.12
|
||||||
PyYAML==3.12
|
rsa>=3.4.2,<4.1.0
|
||||||
rsa==3.4.2
|
six>=1.10.0,<1.15.0
|
||||||
six==1.10.0
|
|
||||||
goodreads =
|
goodreads =
|
||||||
goodreads >= 0.3.2
|
goodreads>=0.3.2,<0.4.0
|
||||||
python-Levenshtein>=0.12.0
|
python-Levenshtein>=0.12.0,<0.13.0
|
||||||
|
ldap =
|
||||||
|
python-ldap>=3.0.0,<3.3.0
|
||||||
|
Flask-SimpleLDAP>=1.4.0,<1.5.0
|
||||||
|
oauth =
|
||||||
|
Flask-Dance>=1.4.0,<3.1.0
|
||||||
|
SQLAlchemy-Utils>=0.33.5,<0.37.0
|
||||||
metadata =
|
metadata =
|
||||||
lxml>=3.8.0
|
lxml>=3.8.0,<4.6.0
|
||||||
Pillow>=4.0.0
|
|
||||||
rarfile>=2.7
|
rarfile>=2.7
|
||||||
Wand >= 0.4.4
|
|
||||||
comics =
|
comics =
|
||||||
natsort>=2.2.0
|
natsort>=2.2.0
|
||||||
# find solution for this should belong to comics
|
comicapi>= 2.1.3,<2.2.0
|
||||||
# comicapi @ git+https://github.com/OzzieIsaacs/comicapi/archive/5346716578b2843f54d522f44d01bc8d25001d24.zip#egg=comicapi
|
kobo =
|
||||||
|
jsonschema>=3.2.0,<3.3.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user