Commit 5dd08e43 authored by Michael Knepher's avatar Michael Knepher

Merge remote-tracking branch 'upstream/master'

parents 2120d729 2f8c8c3a
......@@ -37,6 +37,7 @@ from . import config_sql, logger, cache_buster, cli, ub, db
from .reverseproxy import ReverseProxied
from .server import WebServer
mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml')
mimetypes.add_type('application/epub+zip', '.epub')
......@@ -59,7 +60,7 @@ app = Flask(__name__)
app.config.update(
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
REMEMBER_COOKIE_SAMESITE='Lax',
REMEMBER_COOKIE_SAMESITE='Lax', # will be available in flask-login 0.5.1 earliest
)
......@@ -82,6 +83,8 @@ log = logger.create()
from . import services
calibre_db = db.CalibreDB()
def create_app():
app.wsgi_app = ReverseProxied(app.wsgi_app)
# For python2 convert path to unicode
......@@ -98,7 +101,8 @@ def create_app():
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
web_server.init_app(app, config)
db.setup_db(config)
calibre_db.setup_db(config, cli.settingspath)
calibre_db.start()
babel.init_app(app)
_BABEL_TRANSLATIONS.update(str(item) for item in babel.list_translations())
......
......@@ -30,7 +30,7 @@ import babel, pytz, requests, sqlalchemy
import werkzeug, flask, flask_login, flask_principal, jinja2
from flask_babel import gettext as _
from . import db, converter, uploader, server, isoLanguages, constants
from . import db, calibre_db, converter, uploader, server, isoLanguages, constants
from .web import render_title_template
try:
from flask_login import __version__ as flask_loginVersion
......@@ -85,10 +85,12 @@ _VERSIONS.update(uploader.get_versions())
@about.route("/stats")
@flask_login.login_required
def stats():
counter = db.session.query(db.Books).count()
authors = db.session.query(db.Authors).count()
categorys = db.session.query(db.Tags).count()
series = db.session.query(db.Series).count()
_VERSIONS['ebook converter'] = _(converter.get_version())
counter = calibre_db.session.query(db.Books).count()
authors = calibre_db.session.query(db.Authors).count()
categorys = calibre_db.session.query(db.Tags).count()
series = calibre_db.session.query(db.Series).count()
_VERSIONS['ebook converter'] = _(converter.get_calibre_version())
_VERSIONS['unrar'] = _(converter.get_unrar_version())
_VERSIONS['kepubify'] = _(converter.get_kepubify_version())
return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=_VERSIONS,
categorycounter=categorys, seriecounter=series, title=_(u"Statistics"), page="stat")
This diff is collapsed.
......@@ -18,10 +18,17 @@
from __future__ import division, print_function, unicode_literals
import os
import io
from . import logger, isoLanguages
from .constants import BookMeta
try:
from PIL import Image as PILImage
use_PIL = True
except ImportError as e:
use_PIL = False
log = logger.create()
......@@ -29,18 +36,43 @@ log = logger.create()
try:
from comicapi.comicarchive import ComicArchive, MetaDataStyle
use_comic_meta = True
try:
from comicapi import __version__ as comic_version
except (ImportError):
comic_version = ''
except ImportError as e:
log.debug('cannot import comicapi, extracting comic metadata will not work: %s', e)
log.debug('Cannot import comicapi, extracting comic metadata will not work: %s', e)
import zipfile
import tarfile
try:
import rarfile
use_rarfile = True
except ImportError as e:
log.debug('cannot import rarfile, extracting cover files from rar files will not work: %s', e)
log.debug('Cannot import rarfile, extracting cover files from rar files will not work: %s', e)
use_rarfile = False
use_comic_meta = False
def _cover_processing(tmp_file_name, img, extension):
if use_PIL:
# convert to jpg because calibre only supports jpg
if extension in ('.png', '.webp'):
imgc = PILImage.open(io.BytesIO(img))
im = imgc.convert('RGB')
tmp_bytesio = io.BytesIO()
im.save(tmp_bytesio, format='JPEG')
img = tmp_bytesio.getvalue()
prefix = os.path.dirname(tmp_file_name)
if img:
tmp_cover_name = prefix + '/cover.jpg'
image = open(tmp_cover_name, 'wb')
image.write(img)
image.close()
else:
tmp_cover_name = None
return tmp_cover_name
def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
cover_data = extension = None
......@@ -50,7 +82,7 @@ def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg' or extension == '.jpeg':
if extension in ('.jpg', '.jpeg', '.png', '.webp'):
cover_data = archive.getPage(index)
break
else:
......@@ -60,7 +92,7 @@ def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg' or extension == '.jpeg':
if extension in ('.jpg', '.jpeg', '.png', '.webp'):
cover_data = cf.read(name)
break
elif original_file_extension.upper() == '.CBT':
......@@ -69,7 +101,7 @@ def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg' or extension == '.jpeg':
if extension in ('.jpg', '.jpeg', '.png', '.webp'):
cover_data = cf.extractfile(name).read()
break
elif original_file_extension.upper() == '.CBR' and use_rarfile:
......@@ -80,21 +112,12 @@ def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg' or extension == '.jpeg':
if extension in ('.jpg', '.jpeg', '.png', '.webp'):
cover_data = cf.read(name)
break
except Exception as e:
log.debug('Rarfile failed with error: %s', e)
prefix = os.path.dirname(tmp_file_name)
if cover_data:
tmp_cover_name = prefix + '/cover' + extension
image = open(tmp_cover_name, 'wb')
image.write(cover_data)
image.close()
else:
tmp_cover_name = None
return tmp_cover_name
return _cover_processing(tmp_file_name, cover_data, extension)
def get_comic_info(tmp_file_path, original_file_name, original_file_extension, rarExceutable):
......
......@@ -53,6 +53,7 @@ class _Settings(_Base):
mail_login = Column(String, default='mail@example.com')
mail_password = Column(String, default='mypassword')
mail_from = Column(String, default='automailer <mail@example.com>')
mail_size = Column(Integer, default=25*1024*1024)
config_calibre_dir = Column(String)
config_port = Column(Integer, default=constants.DEFAULT_PORT)
......@@ -96,7 +97,7 @@ class _Settings(_Base):
config_use_goodreads = Column(Boolean, default=False)
config_goodreads_api_key = Column(String)
config_goodreads_api_secret = Column(String)
config_register_email = Column(Boolean, default=False)
config_login_type = Column(Integer, default=0)
config_kobo_proxy = Column(Boolean, default=False)
......@@ -116,10 +117,11 @@ class _Settings(_Base):
config_ldap_group_members_field = Column(String, default='memberUid')
config_ldap_group_name = Column(String, default='calibreweb')
config_ebookconverter = Column(Integer, default=0)
config_converterpath = Column(String)
config_kepubifypath = Column(String, default=None)
config_converterpath = Column(String, default=None)
config_calibre = Column(String)
config_rarfile_location = Column(String)
config_rarfile_location = Column(String, default=None)
config_upload_formats = Column(String, default=','.join(constants.EXTENSIONS_UPLOAD))
config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE)
......@@ -140,6 +142,22 @@ class _ConfigSQL(object):
self.config_calibre_dir = None
self.load()
change = False
if self.config_converterpath == None:
change = True
self.config_converterpath = autodetect_calibre_binary()
if self.config_kepubifypath == None:
change = True
self.config_kepubifypath = autodetect_kepubify_binary()
if self.config_rarfile_location == None:
change = True
self.config_rarfile_location = autodetect_unrar_binary()
if change:
self.save()
def _read_from_storage(self):
if self._settings is None:
log.debug("_ConfigSQL._read_from_storage")
......@@ -264,7 +282,8 @@ class _ConfigSQL(object):
setattr(self, k, v)
if self.config_google_drive_watch_changes_response:
self.config_google_drive_watch_changes_response = json.loads(self.config_google_drive_watch_changes_response)
self.config_google_drive_watch_changes_response = \
json.loads(self.config_google_drive_watch_changes_response)
have_metadata_db = bool(self.config_calibre_dir)
if have_metadata_db:
......@@ -272,8 +291,13 @@ class _ConfigSQL(object):
db_file = os.path.join(self.config_calibre_dir, 'metadata.db')
have_metadata_db = os.path.isfile(db_file)
self.db_configured = have_metadata_db
logger.setup(self.config_logfile, self.config_log_level)
constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip() for x in self.config_upload_formats.split(',')]
logfile = logger.setup(self.config_logfile, self.config_log_level)
if logfile != self.config_logfile:
log.warning("Log path %s not valid, falling back to default", self.config_logfile)
self.config_logfile = logfile
self._session.merge(s)
self._session.commit()
def save(self):
'''Apply all configuration values to the underlying storage.'''
......@@ -334,17 +358,41 @@ def _migrate_table(session, orm_class):
if changed:
session.commit()
def autodetect_calibre_binary():
if sys.platform == "win32":
calibre_path = ["C:\\program files\calibre\calibre-convert.exe",
"C:\\program files(x86)\calibre\calibre-convert.exe"]
calibre_path = ["C:\\program files\calibre\ebook-convert.exe",
"C:\\program files(x86)\calibre\ebook-convert.exe",
"C:\\program files(x86)\calibre2\ebook-convert.exe",
"C:\\program files\calibre2\ebook-convert.exe"]
else:
calibre_path = ["/opt/calibre/ebook-convert"]
for element in calibre_path:
if os.path.isfile(element) and os.access(element, os.X_OK):
return element
return None
return ""
def autodetect_unrar_binary():
if sys.platform == "win32":
calibre_path = ["C:\\program files\\WinRar\\unRAR.exe",
"C:\\program files(x86)\\WinRar\\unRAR.exe"]
else:
calibre_path = ["/usr/bin/unrar"]
for element in calibre_path:
if os.path.isfile(element) and os.access(element, os.X_OK):
return element
return ""
def autodetect_kepubify_binary():
if sys.platform == "win32":
calibre_path = ["C:\\program files\\kepubify\\kepubify-windows-64Bit.exe",
"C:\\program files(x86)\\kepubify\\kepubify-windows-64Bit.exe"]
else:
calibre_path = ["/opt/kepubify/kepubify-linux-64bit", "/opt/kepubify/kepubify-linux-32bit"]
for element in calibre_path:
if os.path.isfile(element) and os.access(element, os.X_OK):
return element
return ""
def _migrate_database(session):
# make sure the table is created, if it does not exist
......
......@@ -81,6 +81,7 @@ SIDEBAR_PUBLISHER = 1 << 12
SIDEBAR_RATING = 1 << 13
SIDEBAR_FORMAT = 1 << 14
SIDEBAR_ARCHIVED = 1 << 15
# SIDEBAR_LIST = 1 << 16
ADMIN_USER_ROLES = sum(r for r in ALL_ROLES.values()) & ~ROLE_ANONYMOUS
ADMIN_USER_SIDEBAR = (SIDEBAR_ARCHIVED << 1) - 1
......@@ -111,11 +112,9 @@ del env_CALIBRE_PORT
EXTENSIONS_AUDIO = {'mp3', 'mp4', 'ogg', 'opus', 'wav', 'flac'}
EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt'}
EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
EXTENSIONS_CONVERT = ['pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt']
EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'kepub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
'fb2', 'html', 'rtf', 'lit', 'odt', 'mp3', 'mp4', 'ogg', 'opus', 'wav', 'flac'}
# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] +
# (['rar','cbr'] if feature_support['rar'] else []))
def has_flag(value, bit_flag):
......
......@@ -19,6 +19,7 @@
from __future__ import division, print_function, unicode_literals
import os
import re
import sys
from flask_babel import gettext as _
from . import config, logger
......@@ -29,8 +30,8 @@ log = logger.create()
# _() necessary to make babel aware of string for translation
_NOT_CONFIGURED = _('not configured')
_NOT_INSTALLED = 'not installed'
_EXECUTION_ERROR = 'Execution permissions missing'
_NOT_INSTALLED = _('not installed')
_EXECUTION_ERROR = _('Execution permissions missing')
def _get_command_version(path, pattern, argument=None):
......@@ -48,10 +49,15 @@ def _get_command_version(path, pattern, argument=None):
return _NOT_INSTALLED
def get_version():
version = None
if config.config_ebookconverter == 1:
version = _get_command_version(config.config_converterpath, r'Amazon kindlegen\(')
elif config.config_ebookconverter == 2:
version = _get_command_version(config.config_converterpath, r'ebook-convert.*\(calibre', '--version')
return version or _NOT_CONFIGURED
def get_calibre_version():
return _get_command_version(config.config_converterpath, r'ebook-convert.*\(calibre', '--version') \
or _NOT_CONFIGURED
def get_unrar_version():
return _get_command_version(config.config_rarfile_location, r'UNRAR.*\d') or _NOT_CONFIGURED
def get_kepubify_version():
return _get_command_version(config.config_kepubifypath, r'kepubify\s','--version') or _NOT_CONFIGURED
This diff is collapsed.
This diff is collapsed.
......@@ -39,7 +39,7 @@ try:
except ImportError:
pass
from . import logger, gdriveutils, config, db
from . import logger, gdriveutils, config, ub, calibre_db
from .web import admin_required
......@@ -145,7 +145,8 @@ def on_received_watch_confirmation():
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
else:
dbpath = os.path.join(config.config_calibre_dir, "metadata.db").encode()
if not response['deleted'] and response['file']['title'] == 'metadata.db' and response['file']['md5Checksum'] != hashlib.md5(dbpath):
if not response['deleted'] and response['file']['title'] == 'metadata.db' \
and response['file']['md5Checksum'] != hashlib.md5(dbpath):
tmpDir = tempfile.gettempdir()
log.info('Database file updated')
copyfile(dbpath, os.path.join(tmpDir, "metadata.db_" + str(current_milli_time())))
......@@ -154,7 +155,7 @@ def on_received_watch_confirmation():
log.info('Setting up new DB')
# prevent error on windows, as os.rename does on exisiting files
move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
db.setup_db(config)
calibre_db.setup_db(config, ub.app_DB_path)
except Exception as e:
log.exception(e)
updateMetaData()
......
This diff is collapsed.
......@@ -57,12 +57,12 @@ def get_language_name(locale, lang_code):
def get_language_codes(locale, language_names, remainder=None):
language_names = set(x.strip().lower() for x in language_names if x)
languages = list()
for k, v in get_language_names(locale).items():
v = v.lower()
if v in language_names:
languages.append(k)
language_names.remove(v)
yield k
if remainder is not None:
remainder.extend(language_names)
return languages
......@@ -111,10 +111,3 @@ def timestamptodate(date, fmt=None):
@jinjia.app_template_filter('yesno')
def yesno(value, yes, no):
return yes if value else no
'''@jinjia.app_template_filter('canread')
def canread(ext):
if isinstance(ext, db.Data):
ext = ext.format
return ext.lower() in EXTENSIONS_READER'''
......@@ -48,7 +48,7 @@ from sqlalchemy.sql.expression import and_, or_
from sqlalchemy.exc import StatementError
import requests
from . import config, logger, kobo_auth, db, helper, shelf as shelf_lib, ub
from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub
from .services import SyncToken as SyncToken
from .web import download_required
from .kobo_auth import requires_kobo_auth
......@@ -143,7 +143,7 @@ def HandleSyncRequest():
# We reload the book database so that the user get's a fresh view of the library
# in case of external changes (e.g: adding a book through Calibre).
db.reconnect_db(config)
calibre_db.reconnect_db(config, ub.app_DB_path)
archived_books = (
ub.session.query(ub.ArchivedBook)
......@@ -170,7 +170,7 @@ def HandleSyncRequest():
# It looks like it's treating the db.Books.last_modified field as a string and may fail
# the comparison because of the +00:00 suffix.
changed_entries = (
db.session.query(db.Books)
calibre_db.session.query(db.Books)
.join(db.Data)
.filter(or_(func.datetime(db.Books.last_modified) > sync_token.books_last_modified,
db.Books.id.in_(recently_restored_or_archived_books)))
......@@ -207,7 +207,7 @@ def HandleSyncRequest():
ub.KoboReadingState.user_id == current_user.id,
ub.KoboReadingState.book_id.notin_(reading_states_in_new_entitlements))))
for kobo_reading_state in changed_reading_states.all():
book = db.session.query(db.Books).filter(db.Books.id == kobo_reading_state.book_id).one_or_none()
book = calibre_db.session.query(db.Books).filter(db.Books.id == kobo_reading_state.book_id).one_or_none()
if book:
sync_results.append({
"ChangedReadingState": {
......@@ -256,7 +256,7 @@ def HandleMetadataRequest(book_uuid):
if not current_app.wsgi_app.is_proxied:
log.debug('Kobo: Received unproxied request, changed request port to server port')
log.info("Kobo library metadata request received for book %s" % book_uuid)
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
book = calibre_db.get_book_by_uuid(book_uuid)
if not book or not book.data:
log.info(u"Book %s not found in database", book_uuid)
return redirect_or_proxy_request()
......@@ -474,7 +474,7 @@ def add_items_to_shelf(items, shelf):
items_unknown_to_calibre.append(item)
continue
book = db.session.query(db.Books).filter(db.Books.uuid == item["RevisionId"]).one_or_none()
book = calibre_db.get_book_by_uuid(item["RevisionId"])
if not book:
items_unknown_to_calibre.append(item)
continue
......@@ -545,7 +545,7 @@ def HandleTagRemoveItem(tag_id):
items_unknown_to_calibre.append(item)
continue
book = db.session.query(db.Books).filter(db.Books.uuid == item["RevisionId"]).one_or_none()
book = calibre_db.get_book_by_uuid(item["RevisionId"])
if not book:
items_unknown_to_calibre.append(item)
continue
......@@ -613,7 +613,7 @@ def create_kobo_tag(shelf):
"Type": "UserTag"
}
for book_shelf in shelf.books:
book = db.session.query(db.Books).filter(db.Books.id == book_shelf.book_id).one_or_none()
book = calibre_db.get_book(book_shelf.book_id)
if not book:
log.info(u"Book (id: %s) in BookShelf (id: %s) not found in book database", book_shelf.book_id, shelf.id)
continue
......@@ -629,7 +629,7 @@ def create_kobo_tag(shelf):
@kobo.route("/v1/library/<book_uuid>/state", methods=["GET", "PUT"])
@login_required
def HandleStateRequest(book_uuid):
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
book = calibre_db.get_book_by_uuid(book_uuid)
if not book or not book.data:
log.info(u"Book %s not found in database", book_uuid)
return redirect_or_proxy_request()
......@@ -804,7 +804,7 @@ def TopLevelEndpoint():
@login_required
def HandleBookDeletionRequest(book_uuid):
log.info("Kobo book deletion request received for book %s" % book_uuid)
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
book = calibre_db.get_book_by_uuid(book_uuid)
if not book:
log.info(u"Book %s not found in database", book_uuid)
return redirect_or_proxy_request()
......
......@@ -82,7 +82,6 @@ def _absolute_log_file(log_file, default_log_file):
if not os.path.dirname(log_file):
log_file = os.path.join(_CONFIG_DIR, log_file)
return os.path.abspath(log_file)
return default_log_file
......@@ -115,7 +114,7 @@ def setup(log_file, log_level=None):
if previous_handler:
# if the log_file has not changed, don't create a new handler
if getattr(previous_handler, 'baseFilename', None) == log_file:
return
return "" if log_file == DEFAULT_LOG_FILE else log_file
logging.debug("logging to %s level %s", log_file, r.level)
if log_file == LOG_TO_STDERR or log_file == LOG_TO_STDOUT:
......@@ -132,12 +131,14 @@ def setup(log_file, log_level=None):
if log_file == DEFAULT_LOG_FILE:
raise
file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=50000, backupCount=2)
log_file = ""
file_handler.setFormatter(FORMATTER)
for h in r.handlers:
r.removeHandler(h)
h.close()
r.addHandler(file_handler)
return "" if log_file == DEFAULT_LOG_FILE else log_file
def create_access_log(log_file, log_name, formatter):
......@@ -150,11 +151,18 @@ def create_access_log(log_file, log_name, formatter):
access_log = logging.getLogger(log_name)
access_log.propagate = False
access_log.setLevel(logging.INFO)
try:
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2)
except IOError:
if log_file == DEFAULT_ACCESS_LOG:
raise
file_handler = RotatingFileHandler(DEFAULT_ACCESS_LOG, maxBytes=50000, backupCount=2)
log_file = ""
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2)
file_handler.setFormatter(formatter)
access_log.addHandler(file_handler)
return access_log
return access_log, \
"" if _absolute_log_file(log_file, DEFAULT_ACCESS_LOG) == DEFAULT_ACCESS_LOG else log_file
# Enable logging of smtp lib debug output
......
This diff is collapsed.
......@@ -72,7 +72,11 @@ class WebServer(object):
if config.config_access_log:
log_name = "gevent.access" if _GEVENT else "tornado.access"
formatter = logger.ACCESS_FORMATTER_GEVENT if _GEVENT else logger.ACCESS_FORMATTER_TORNADO
self.access_logger = logger.create_access_log(config.config_access_logfile, log_name, formatter)
self.access_logger, logfile = logger.create_access_log(config.config_access_logfile, log_name, formatter)
if logfile != config.config_access_logfile:
log.warning("Accesslog path %s not valid, falling back to default", config.config_access_logfile)
config.config_access_logfile = logfile
config.save()
else:
if not _GEVENT:
logger.get('tornado.access').disabled = True
......@@ -196,6 +200,9 @@ class WebServer(object):
def stop(self, restart=False):
from . import updater_thread
updater_thread.stop()
from . import calibre_db
calibre_db.stop()
log.info("webserver stop (restart=%s)", restart)
self.restart = restart
......
......@@ -20,7 +20,10 @@ from __future__ import division, print_function, unicode_literals
import time
from functools import reduce
from goodreads.client import GoodreadsClient
try:
from goodreads.client import GoodreadsClient
except ImportError:
from betterreads.client import GoodreadsClient
try: import Levenshtein
except ImportError: Levenshtein = False
......@@ -95,8 +98,12 @@ def get_other_books(author_info, library_books=None):
for book in author_info.books:
if book.isbn in identifiers:
continue
if book.gid["#text"] in identifiers:
continue
if isinstance(book.gid, int):
if book.gid in identifiers:
continue
else:
if book.gid["#text"] in identifiers:
continue
if Levenshtein and library_titles:
goodreads_title = book._book_dict['title_without_series']
......
......@@ -28,9 +28,8 @@ from flask_babel import gettext as _
from flask_login import login_required, current_user
from sqlalchemy.sql.expression import func
from . import logger, ub, searched_ids, db
from . import logger, ub, searched_ids, db, calibre_db
from .web import render_title_template
from .helper import common_filters
shelf = Blueprint('shelf', __name__)
......@@ -320,11 +319,11 @@ def show_shelf(shelf_type, shelf_id):
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id)\
.order_by(ub.BookShelf.order.asc()).all()
for book in books_in_shelf:
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).filter(common_filters()).first()
cur_book = calibre_db.get_filtered_book(book.book_id)
if cur_book:
result.append(cur_book)
else:
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
cur_book = calibre_db.get_book(book.book_id)
if not cur_book:
log.info('Not existing book %s in %s deleted', book.book_id, shelf)
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book.book_id).delete()
......@@ -356,7 +355,7 @@ def order_shelf(shelf_id):
books_in_shelf2 = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
.order_by(ub.BookShelf.order.asc()).all()
for book in books_in_shelf2:
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).filter(common_filters()).first()
cur_book = calibre_db.get_filtered_book(book.book_id)
if cur_book:
result.append({'title': cur_book.title,
'id': cur_book.id,
......@@ -364,7 +363,7 @@ def order_shelf(shelf_id):
'series': cur_book.series,
'series_index': cur_book.series_index})
else:
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
cur_book = calibre_db.get_book(book.book_id)
result.append({'title': _('Hidden Book'),
'id': cur_book.id,
'author': [],
......
body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{
display: none;
}
.cover .badge{
position: absolute;
top: 0;
right: 0;
background-color: #cc7b19;
border-radius: 0;
padding: 0 8px;
box-shadow: 0 0 4px rgba(0,0,0,.6);
line-height: 24px;
}
.cover{
box-shadow: 0 0 4px rgba(0,0,0,.6);
}
......@@ -109,7 +109,7 @@ a { color: #45b29d; }
.container-fluid .book .cover img {
border: 1px solid #fff;
box-sizeing: border-box;
box-sizing: border-box;
height: 100%;
bottom: 0;
position: absolute;
......@@ -165,7 +165,7 @@ span.glyphicon.glyphicon-tags {
.container-fluid .single .cover img {
border: 1px solid #fff;
box-sizeing: border-box;
box-sizing: border-box;
-webkit-box-shadow: 0 5px 8px -6px #777;
-moz-box-shadow: 0 5px 8px -6px #777;
box-shadow: 0 5px 8px -6px #777;
......@@ -174,6 +174,12 @@ span.glyphicon.glyphicon-tags {
.navbar-default .navbar-toggle .icon-bar {background-color: #000; }
.navbar-default .navbar-toggle {border-color: #000; }
.cover { margin-bottom: 10px; }
.cover .badge{
position: absolute;
top: 2px;
left: 2px;
background-color: #777;
}
.cover-height { max-height: 100px;}
.col-sm-2 a .cover-small {
......@@ -207,7 +213,7 @@ span.glyphicon.glyphicon-tags {
.panel-body {background-color: #f5f5f5; }
.spinner {margin: 0 41%; }
.spinner2 {margin: 0 41%; }
.intend-form { margin-left:20px; }
table .bg-dark-danger {background-color: #d9534f; color: #fff; }
table .bg-dark-danger a {color: #fff; }
table .bg-dark-danger:hover {background-color: #c9302c; }
......@@ -296,3 +302,4 @@ div.log {
white-space: nowrap;
padding: 0.5em;
}
/* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
* Copyright (C) 2018 OzzieIsaacs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var $list = $("#list").isotope({
itemSelector: ".book",
layoutMode: "fitRows",
getSortData: {
title: ".title",
}
});
$("#desc").click(function() {
$list.isotope({
sortBy: "name",
sortAscending: true
});
return;
});
$("#asc").click(function() {
$list.isotope({
sortBy: "name",
sortAscending: false
});
return;
});
$("#all").click(function() {
// go through all elements and make them visible
$list.isotope({ filter: function() {
return true;
} })
});
$(".char").click(function() {
var character = this.innerText;
$list.isotope({ filter: function() {
return this.attributes["data-id"].value.charAt(0).toUpperCase() == character;
} })
});
......@@ -323,4 +323,17 @@ $(function() {
$(".discover .row").isotope("layout");
});
$(".update-view").click(function(e) {
var target = $(this).data("target");
var view = $(this).data("view");
e.preventDefault();
e.stopPropagation();
var data = {};
data[target] = view;
console.debug("Updating view data: ", data);
$.post( "/ajax/view", data).done(function( ) {
location.reload();
});
});
});
......@@ -22,7 +22,7 @@ import os
import subprocess
def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subprocess.PIPE):
def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subprocess.PIPE, newlines=True):
# Linux py2.7 encode as list without quotes no empty element for parameters
# linux py3.x no encode and as list without quotes no empty element for parameters
# windows py2.7 encode as string with quotes empty element for parameters is okay
......@@ -41,12 +41,13 @@ def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subpro
else:
exc_command = [x for x in command]
return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=True, env=env)
return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=newlines, env=env)
def process_wait(command, serr=subprocess.PIPE):
# Run command, wait for process to terminate, and return an iterator over lines of its output.
p = process_open(command, serr=serr)
newlines = os.name != 'nt'
p = process_open(command, serr=serr, newlines=newlines)
p.wait()
for line in p.stdout.readlines():
if isinstance(line, bytes):
......
......@@ -13,11 +13,14 @@
<th>{{_('E-mail Address')}}</th>
<th>{{_('Send to Kindle E-mail Address')}}</th>
<th>{{_('Downloads')}}</th>
<th class="hidden-xs">{{_('Admin')}}</th>
<th class="hidden-xs">{{_('Download')}}</th>
<th class="hidden-xs">{{_('View Books')}}</th>
<th class="hidden-xs">{{_('Upload')}}</th>
<th class="hidden-xs">{{_('Edit')}}</th>
<th class="hidden-xs ">{{_('Admin')}}</th>
<th class="hidden-xs hidden-sm">{{_('Password')}}</th>
<th class="hidden-xs hidden-sm">{{_('Upload')}}</th>
<th class="hidden-xs hidden-sm">{{_('Download')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('View Books')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('Edit')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('Delete')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('Public Shelf')}}</th>
</tr>
{% for user in allUser %}
{% if not user.role_anonymous() or config.config_anonbrowse %}
......@@ -27,10 +30,13 @@
<td>{{user.kindle_mail}}</td>
<td>{{user.downloads.count()}}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_admin()) }}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_download()) }}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_viewer()) }}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_upload()) }}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_edit()) }}</td>
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_passwd()) }}</td>
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_upload()) }}</td>
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_download()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_viewer()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_edit()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_delete_books()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_edit_shelfs()) }}</td>
</tr>
{% endif %}
{% endfor %}
......
......@@ -86,7 +86,7 @@
</div>
<div class="form-group">
<label for="series_index">{{_('Series ID')}}</label>
<input type="number" step="0.01" min="0" class="form-control" name="series_index" id="series_index" value="{{book.series_index}}">
<input type="number" step="0.01" min="0" placeholder="1" class="form-control" name="series_index" id="series_index" value="{{book.series_index}}">
</div>
<div class="form-group">
<label for="rating">{{_('Rating')}}</label>
......
{% extends "layout.html" %}
{% block body %}
<h1 class="{{page}}">{{_(title)}}</h1>
<div class="filterheader hidden-xs hidden-sm">
{% if entries.__len__() %}
{% if data == 'author' %}
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
{% endif %}
{% endif %}
<button id="desc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
<button id="asc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
{% if charlist|length %}
<button id="all" class="btn btn-primary">{{_('All')}}</button>
{% endif %}
<div class="btn-group character" role="group">
{% for char in charlist%}
<button class="btn btn-primary char">{{char.char}}</button>
{% endfor %}
</div>
{% if title == "Series" %}
<button class="update-view btn btn-primary" href="#" data-target="series_view" data-view="grid">Grid</button>
{% endif %}
</div>
<div class="container">
<div id="list" class="col-xs-12 col-sm-6">
{% for entry in entries %}
{% if loop.index0 == (loop.length/2+loop.length%2)|int and loop.length > 20 %}
</div>
<div id="second" class="col-xs-12 col-sm-6">
{% endif %}
<div class="row" {% if entry[0].sort %}data-name="{{entry[0].name}}"{% endif %} data-id="{% if entry[0].sort %}{{entry[0].sort}}{% else %}{% if entry.name %}{{entry.name}}{% else %}{{entry[0].name}}{% endif %}{% endif %}">
<div class="col-xs-2 col-sm-2 col-md-1" align="left"><span class="badge">{{entry.count}}</span></div>
<div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{loop.index0}}" href="{% if entry.format %}{{url_for('web.books_list', data=data, sort='new', book_id=entry.format )}}{% else %}{{url_for('web.books_list', data=data, sort='new', book_id=entry[0].id )}}{% endif %}">
{% if entry.name %}
<div class="rating">
{% for number in range(entry.name) %}
<span class="glyphicon glyphicon-star good"></span>
{% if loop.last and loop.index < 5 %}
{% for numer in range(5 - loop.index) %}
<span class="glyphicon glyphicon-star"></span>
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% else %}
{% if entry.format %}
{{entry.format}}
{% else %}
{{entry[0].name}}{% endif %}{% endif %}</a></div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block js %}
<script src="{{ url_for('static', filename='js/filter_list.js') }}"></script>
{% endblock %}
This diff is collapsed.
......@@ -6,7 +6,7 @@
{% block body %}
<div class="discover">
<h2>{{title}}</h2>
<form role="form" method="POST" autocomplete="off">
<form role="form" method="POST" autocomplete="off" class="col-md-10 col-lg-6">
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">
......
......@@ -202,6 +202,7 @@
</label>
</form>
</p>
{% if g.user.check_visibility(32768) %}
<p>
<form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST">
<label class="block-label">
......@@ -210,6 +211,7 @@
</label>
</form>
</p>
{% endif %}
</div>
{% endif %}
......
......@@ -6,14 +6,14 @@
{% block body %}
<div class="discover">
<h1>{{title}}</h1>
<form role="form" method="POST">
<form role="form" class="col-md-10 col-lg-6" method="POST">
<div class="form-group">
<label for="mail_server">{{_('SMTP Hostname')}}</label>
<input type="text" class="form-control" name="mail_server" id="mail_server" value="{{content.mail_server}}">
</div>
<div class="form-group">
<label for="mail_port">{{_('SMTP Port')}}</label>
<input type="text" class="form-control" name="mail_port" id="mail_port" value="{{content.mail_port}}">
<input type="number" min="1" max="65535" step="1" class="form-control" name="mail_port" id="mail_port" value="{% if content.mail_port != None %}{{ content.mail_port }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="mail_use_ssl">{{_('Encryption')}}</label>
......@@ -35,11 +35,19 @@
<label for="mail_from">{{_('From E-mail')}}</label>
<input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}">
</div>
<label for="mail_size">{{_('Attachment Size Limit')}}</label>
<div class="form-group input-group">
<input type="number" min="1" max="600" step="1" class="form-control" name="mail_size" id="mail_size" value="{% if content.mail_size != None %}{{ (content.mail_size / 1024 / 1024)|int }}{% endif %}">
<span class="input-group-btn">
<button type="button" id="attachement_size" class="btn btn-default" disabled>MB</button>
</span>
</div>
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test E-mail')}}</button>
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Cancel')}}</a>
</form>
{% if g.allow_registration %}
<div class="col-md-10 col-lg-6">
<h2>{{_('Allowed Domains (Whitelist)')}}</h2>
<form id="domain_add_allow" action="{{ url_for('admin.add_domain',allow=1)}}" method="POST">
<div class="form-group required">
......@@ -74,6 +82,7 @@
</div>
<button id="domain_deny_submit" class="btn btn-default">{{_('Add')}}</button>
</form>
</div>
{% endif %}
</div>
......
{% extends "layout.html" %}
{% block body %}
<h1 class="{{page}}">{{_(title)}}</h1>
<div class="filterheader hidden-xs hidden-sm">
{% if entries.__len__() %}
{% if entries[0][0].sort %}
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
{% endif %}
{% endif %}
<button id="desc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
<button id="asc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
{% if charlist|length %}
<button id="all" class="btn btn-primary">{{_('All')}}</button>
{% endif %}
<div class="btn-group character" role="group">
{% for char in charlist%}
<button class="btn btn-primary char">{{char.char}}</button>
{% endfor %}
</div>
<button class="update-view btn btn-primary" href="#" data-target="series_view" data-view="list">List</button>
</div>
{% if entries[0] %}
<div id="list" class="row">
{% for entry in entries %}
<div class="col-sm-3 col-lg-2 col-xs-6 book sortable" {% if entry[0].sort %}data-name="{{entry[0].series[0].name}}"{% endif %} data-id="{% if entry[0].series[0].name %}{{entry[0].series[0].name}}{% endif %}">
<div class="cover">
<a href="{{url_for('web.books_list', data=data, sort='new', book_id=entry[0].series[0].id )}}">
<img src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/>
<span class="badge">{{entry.count}}</span>
</a>
</div>
<div class="meta">
<a href="{{url_for('web.books_list', data=data, sort='new', book_id=entry[0].series[0].id )}}">
<p class="title">{{entry[0].series[0].name|shortentitle}}</p>
</a>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %}
{% block js %}
<script src="{{ url_for('static', filename='js/filter_grid.js') }}"></script>
{% endblock %}
......@@ -17,6 +17,7 @@
<link href="{{ url_for('static', filename='css/upload.css') }}" rel="stylesheet" media="screen">
{% if g.current_theme == 1 %}
<link href="{{ url_for('static', filename='css/caliBlur.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen">
{% endif %}
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
......@@ -25,7 +26,7 @@
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body class="{{ page }}" data-text="{{_('Home')}}" data-textback="{{_('Back')}}">
<body class="{{ page }} {{ bodyClass }}" data-text="{{_('Home')}}" data-textback="{{_('Back')}}">
<!-- Static navbar -->
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container-fluid">
......
......@@ -18,6 +18,10 @@
<button class="btn btn-primary char">{{char.char}}</button>
{% endfor %}
</div>
{% if data == "series" %}
<button class="update-view btn btn-primary" href="#" data-target="series_view" data-view="grid">Grid</button>
{% endif %}
</div>
<div class="container">
<div id="list" class="col-xs-12 col-sm-6">
......
......@@ -3,10 +3,12 @@
<div class="well col-sm-6 col-sm-offset-2">
<h2 style="margin-top: 0">{{_('Register New Account')}}</h2>
<form method="POST" role="form">
{% if not config.config_register_email %}
<div class="form-group required">
<label for="nickname">{{_('Username')}}</label>
<input type="text" class="form-control" id="nickname" name="nickname" placeholder="{{_('Choose a username')}}" required>
</div>
{% endif %}
<div class="form-group required">
<label for="email">{{_('E-mail Address')}}</label>
<input type="email" class="form-control" id="email" name="email" placeholder="{{_('Your email address')}}" required>
......
{% extends "layout.html" %}
{% block body %}
<div class="col-sm-8">
<div class="col-md-10 col-lg-6">
<form role="form" id="search" action="{{ url_for('web.advanced_search') }}" method="GET">
<div class="form-group">
<label for="book_title">{{_('Book Title')}}</label>
......
......@@ -3,6 +3,7 @@
<div class="discover">
<h1>{{title}}</h1>
<form role="form" method="POST" autocomplete="off">
<div class="col-md-10 col-lg-8">
{% if new_user or ( g.user and content.nickname != "Guest" and g.user.role_admin() ) %}
<div class="form-group required">
<label for="nickname">{{_('Username')}}</label>
......@@ -65,6 +66,7 @@
<div class="btn btn-danger" id="config_delete_kobo_token" data-toggle="modal" data-target="#modalDeleteToken" data-remote="false" {% if not content.remote_auth_token.first() %} style="display: none;" {% endif %}>{{_('Delete')}}</div>
</div>
{% endif %}
</div>
<div class="col-sm-6">
{% for element in sidebar %}
{% if element['config_show'] %}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -49,6 +49,7 @@ from . import constants
session = None
app_DB_path = None
Base = declarative_base()
......@@ -107,6 +108,11 @@ def get_sidebar_config(kwargs=None):
{"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived",
"visibility": constants.SIDEBAR_ARCHIVED, 'public': (not g.user.is_anonymous), "page": "archived",
"show_text": _('Show archived books'), "config_show": content})
'''sidebar.append(
{"glyph": "glyphicon-th-list", "text": _('Books List'), "link": 'web.books_list', "id": "list",
"visibility": constants.SIDEBAR_LIST, 'public': (not g.user.is_anonymous), "page": "list",
"show_text": _('Show Books List'), "config_show": content})'''
return sidebar
......@@ -211,6 +217,7 @@ class User(UserBase, Base):
denied_column_value = Column(String, default="")
allowed_column_value = Column(String, default="")
remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic')
series_view = Column(String(10), default="list")
if oauth_support:
......@@ -251,6 +258,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
self.allowed_tags = data.allowed_tags
self.denied_column_value = data.denied_column_value
self.allowed_column_value = data.allowed_column_value
self.series_view = data.series_view
def role_admin(self):
return False
......@@ -447,7 +455,7 @@ class RemoteAuthToken(Base):
# Migrate database to current version, has to be updated after every database change. Currently migration from
# everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding
# everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding
# rows with SQL commands
def migrate_Database(session):
engine = session.bind
......@@ -557,6 +565,12 @@ def migrate_Database(session):
conn.execute("ALTER TABLE user ADD column `denied_column_value` DEFAULT ''")
conn.execute("ALTER TABLE user ADD column `allowed_column_value` DEFAULT ''")
session.commit()
try:
session.query(exists().where(User.series_view)).scalar()
except exc.OperationalError:
conn = engine.connect()
conn.execute("ALTER TABLE user ADD column `series_view` VARCHAR(10) DEFAULT 'list'")
if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() \
is None:
create_anonymous_user(session)
......@@ -568,7 +582,7 @@ def migrate_Database(session):
# Create new table user_id and copy contents of table user into it
conn = engine.connect()
conn.execute("CREATE TABLE user_id (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
" nickname VARCHAR(64),"
"nickname VARCHAR(64),"
"email VARCHAR(120),"
"role SMALLINT,"
"password VARCHAR,"
......@@ -576,10 +590,11 @@ def migrate_Database(session):
"locale VARCHAR(2),"
"sidebar_view INTEGER,"
"default_language VARCHAR(3),"
"series_view VARCHAR(10),"
"UNIQUE (nickname),"
"UNIQUE (email))")
conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale,"
"sidebar_view, default_language) "
"sidebar_view, default_language, series_view) "
"SELECT id, nickname, email, role, password, kindle_mail, locale,"
"sidebar_view, default_language FROM user")
# delete old user table and rename new user_id table to user:
......@@ -616,8 +631,7 @@ def delete_download(book_id):
session.query(Downloads).filter(book_id == Downloads.book_id).delete()
session.commit()
# Generate user Guest (translated text), as anoymous user, no rights
# Generate user Guest (translated text), as anonymous user, no rights
def create_anonymous_user(session):
user = User()
user.nickname = "Guest"
......@@ -651,7 +665,9 @@ def create_admin_user(session):
def init_db(app_db_path):
# Open session for database connection
global session
global app_DB_path
app_DB_path = app_db_path
engine = create_engine(u'sqlite:///{0}'.format(app_db_path), echo=False)
Session = sessionmaker()
......
......@@ -40,7 +40,7 @@ try:
from wand.exceptions import PolicyError
use_generic_pdf_cover = False
except (ImportError, RuntimeError) as e:
log.debug('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
log.debug('Cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
use_generic_pdf_cover = True
try:
......@@ -48,21 +48,21 @@ try:
from PyPDF2 import __version__ as PyPdfVersion
use_pdf_meta = True
except ImportError as e:
log.debug('cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
log.debug('Cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
use_pdf_meta = False
try:
from . import epub
use_epub_meta = True
except ImportError as e:
log.debug('cannot import epub, extracting epub metadata will not work: %s', e)
log.debug('Cannot import epub, extracting epub metadata will not work: %s', e)
use_epub_meta = False
try:
from . import fb2
use_fb2_meta = True
except ImportError as e:
log.debug('cannot import fb2, extracting fb2 metadata will not work: %s', e)
log.debug('Cannot import fb2, extracting fb2 metadata will not work: %s', e)
use_fb2_meta = False
try:
......@@ -70,20 +70,17 @@ try:
from PIL import __version__ as PILversion
use_PIL = True
except ImportError as e:
log.debug('cannot import Pillow, using png and webp images as cover will not work: %s', e)
log.debug('Cannot import Pillow, using png and webp images as cover will not work: %s', e)
use_PIL = False
__author__ = 'lemmsh'
def process(tmp_file_path, original_file_name, original_file_extension, rarExecutable):
"""Get the metadata for tmp_file_path."""
meta = None
extension_upper = original_file_extension.upper()
try:
if ".PDF" == extension_upper:
meta = pdf_meta(tmp_file_path, original_file_name, original_file_extension)
elif ".EPUB" == extension_upper and use_epub_meta is True:
elif extension_upper in [".KEPUB", ".EPUB"] and use_epub_meta is True:
meta = epub.get_epub_info(tmp_file_path, original_file_name, original_file_extension)
elif ".FB2" == extension_upper and use_fb2_meta is True:
meta = fb2.get_fb2_info(tmp_file_path, original_file_extension)
......@@ -182,7 +179,7 @@ def get_versions():
else:
PILVersion = u'not installed'
if comic.use_comic_meta:
ComicVersion = u'installed'
ComicVersion = comic.comic_version or u'installed'
else:
ComicVersion = u'not installed'
return {'Image Magick': IVersion,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -31,7 +31,7 @@ rarfile>=2.7
# other
natsort>=2.2.0,<7.1.0
git+https://github.com/OzzieIsaacs/comicapi.git@15dff9ce4e1ffed29ba4a2feadfcdb6bed00bcad#egg=comicapi
git+https://github.com/OzzieIsaacs/comicapi.git@3e15b950b72724b1b8ca619c36580b5fbaba9784#egg=comicapi
#Kobo integration
jsonschema>=3.2.0,<3.3.0
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment