Fix for #1407 converting books should now be possible again

pull/1429/head
Ozzieisaacs 5 years ago
parent a48418364c
commit 1a458fe39f

@ -37,6 +37,9 @@ from . import config_sql, logger, cache_buster, cli, ub, db
from .reverseproxy import ReverseProxied
from .server import WebServer
# import queue
# queue = queue.Queue()
mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml')
mimetypes.add_type('application/epub+zip', '.epub')
@ -82,6 +85,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 +103,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, cli.settingspath)
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,10 @@ _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()
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())

@ -38,7 +38,7 @@ from sqlalchemy.exc import IntegrityError
from sqlalchemy.sql.expression import func
from . import constants, logger, helper, services
from . import db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
from .helper import speaking_language, check_valid_domain, send_test_mail, reset_password, generate_password_hash
from .gdriveutils import is_gdrive_ready, gdrive_support
from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano
@ -86,7 +86,7 @@ def shutdown():
showtext = {}
if task in (0, 1): # valid commandos received
# close all database connections
db.dispose()
calibre_db.dispose()
ub.dispose()
if task == 0:
@ -99,7 +99,7 @@ def shutdown():
if task == 2:
log.warning("reconnecting to calibre database")
db.setup_db(config, ub.app_DB_path)
calibre_db.setup_db(config, ub.app_DB_path)
showtext['text'] = _(u'Reconnect successful')
return json.dumps(showtext)
@ -148,9 +148,9 @@ def configuration():
@login_required
@admin_required
def view_configuration():
readColumn = db.session.query(db.Custom_Columns)\
readColumn = calibre_db.session.query(db.Custom_Columns)\
.filter(and_(db.Custom_Columns.datatype == 'bool',db.Custom_Columns.mark_for_delete == 0)).all()
restrictColumns= db.session.query(db.Custom_Columns)\
restrictColumns= calibre_db.session.query(db.Custom_Columns)\
.filter(and_(db.Custom_Columns.datatype == 'text',db.Custom_Columns.mark_for_delete == 0)).all()
return render_title_template("config_view_edit.html", conf=config, readColumns=readColumn,
restrictColumns=restrictColumns,
@ -944,7 +944,7 @@ def edit_user(user_id):
translations = babel.list_translations() + [LC('en')]
kobo_support = feature_support['kobo'] and config.config_kobo_sync
for book in content.downloads:
downloadbook = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
downloadbook = calibre_db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
if downloadbook:
downloads.append(downloadbook)
else:

@ -23,18 +23,22 @@ import os
import re
import ast
from datetime import datetime
import threading
import time
import queue
from sqlalchemy import create_engine, event
from sqlalchemy import create_engine
from sqlalchemy import Table, Column, ForeignKey, CheckConstraint
from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.exc import OperationalError
session = None
from . import logger
# session = None
cc_exceptions = ['datetime', 'comments', 'composite', 'series']
cc_classes = {}
engine = None
# engine = None
Base = declarative_base()
@ -226,6 +230,7 @@ class Publishers(Base):
class Data(Base):
__tablename__ = 'data'
__table_args__ = {'schema':'calibre'}
id = Column(Integer, primary_key=True)
book = Column(Integer, ForeignKey('books.id'), nullable=False)
@ -314,136 +319,170 @@ class Custom_Columns(Base):
return display_dict
def update_title_sort(config, conn=None):
# user defined sort function for calibre databases (Series, etc.)
def _title_sort(title):
# calibre sort stuff
title_pat = re.compile(config.config_title_regex, re.IGNORECASE)
match = title_pat.search(title)
if match:
prep = match.group(1)
title = title.replace(prep, '') + ', ' + prep
return title.strip()
conn = conn or session.connection().connection.connection
conn.create_function("title_sort", 1, _title_sort)
def setup_db(config, app_db_path):
dispose()
global engine
if not config.config_calibre_dir:
config.invalidate()
return False
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
if not os.path.exists(dbpath):
config.invalidate()
return False
try:
#engine = create_engine('sqlite:///{0}'.format(dbpath),
engine = create_engine('sqlite://',
echo=False,
isolation_level="SERIALIZABLE",
connect_args={'check_same_thread': False})
engine.execute("attach database '{}' as calibre;".format(dbpath))
engine.execute("attach database '{}' as app_settings;".format(app_db_path))
conn = engine.connect()
# conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302
except Exception as e:
config.invalidate(e)
return False
config.db_configured = True
update_title_sort(config, conn.connection)
if not cc_classes:
cc = conn.execute("SELECT id, datatype FROM custom_columns")
cc_ids = []
books_custom_column_links = {}
for row in cc:
if row.datatype not in cc_exceptions:
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'),
primary_key=True),
Column('value', Integer,
ForeignKey('custom_column_' + str(row.id) + '.id'),
primary_key=True)
)
cc_ids.append([row.id, row.datatype])
if row.datatype == 'bool':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Boolean)}
elif row.datatype == 'int':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Integer)}
elif row.datatype == 'float':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Float)}
class CalibreDB(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.engine = None
self.session = None
self.queue = None
self.log = None
def add_queue(self,queue):
self.queue = queue
self.log = logger.create()
def run(self):
while True:
i = self.queue.get()
if i == 'dummy':
self.queue.task_done()
break
if i['task'] == 'add_format':
cur_book = self.session.query(Books).filter(Books.id == i['id']).first()
cur_book.data.append(i['format'])
try:
# db.session.merge(cur_book)
self.session.commit()
except OperationalError as e:
self.session.rollback()
self.log.error("Database error: %s", e)
# self._handleError(_(u"Database error: %(error)s.", error=e))
# return
self.queue.task_done()
def stop(self):
self.queue.put('dummy')
def setup_db(self, config, app_db_path):
self.dispose()
# global engine
if not config.config_calibre_dir:
config.invalidate()
return False
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
if not os.path.exists(dbpath):
config.invalidate()
return False
try:
#engine = create_engine('sqlite:///{0}'.format(dbpath),
self.engine = create_engine('sqlite://',
echo=False,
isolation_level="SERIALIZABLE",
connect_args={'check_same_thread': False})
self.engine.execute("attach database '{}' as calibre;".format(dbpath))
self.engine.execute("attach database '{}' as app_settings;".format(app_db_path))
conn = self.engine.connect()
# conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302
except Exception as e:
config.invalidate(e)
return False
config.db_configured = True
self.update_title_sort(config, conn.connection)
if not cc_classes:
cc = conn.execute("SELECT id, datatype FROM custom_columns")
cc_ids = []
books_custom_column_links = {}
for row in cc:
if row.datatype not in cc_exceptions:
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'),
primary_key=True),
Column('value', Integer,
ForeignKey('custom_column_' + str(row.id) + '.id'),
primary_key=True)
)
cc_ids.append([row.id, row.datatype])
if row.datatype == 'bool':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Boolean)}
elif row.datatype == 'int':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Integer)}
elif row.datatype == 'float':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Float)}
else:
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'value': Column(String)}
cc_classes[row.id] = type(str('Custom_Column_' + str(row.id)), (Base,), ccdict)
for cc_id in cc_ids:
if (cc_id[1] == 'bool') or (cc_id[1] == 'int') or (cc_id[1] == 'float'):
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
primaryjoin=(
Books.id == cc_classes[cc_id[0]].book),
backref='books'))
else:
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'value': Column(String)}
cc_classes[row.id] = type(str('Custom_Column_' + str(row.id)), (Base,), ccdict)
for cc_id in cc_ids:
if (cc_id[1] == 'bool') or (cc_id[1] == 'int') or (cc_id[1] == 'float'):
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
primaryjoin=(
Books.id == cc_classes[cc_id[0]].book),
backref='books'))
else:
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
secondary=books_custom_column_links[cc_id[0]],
backref='books'))
global session
Session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
session = Session()
return True
def dispose():
global session
old_session = session
session = None
if old_session:
try: old_session.close()
except: pass
if old_session.bind:
try: old_session.bind.dispose()
except Exception: pass
for attr in list(Books.__dict__.keys()):
if attr.startswith("custom_column_"):
setattr(Books, attr, None)
for db_class in cc_classes.values():
Base.metadata.remove(db_class.__table__)
cc_classes.clear()
for table in reversed(Base.metadata.sorted_tables):
name = table.key
if name.startswith("custom_column_") or name.startswith("books_custom_column_"):
if table is not None:
Base.metadata.remove(table)
def reconnect_db(config, app_db_path):
session.close()
engine.dispose()
setup_db(config, app_db_path)
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
secondary=books_custom_column_links[cc_id[0]],
backref='books'))
# global session
Session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=self.engine))
self.session = Session()
return True
def update_title_sort(self, config, conn=None):
# user defined sort function for calibre databases (Series, etc.)
def _title_sort(title):
# calibre sort stuff
title_pat = re.compile(config.config_title_regex, re.IGNORECASE)
match = title_pat.search(title)
if match:
prep = match.group(1)
title = title.replace(prep, '') + ', ' + prep
return title.strip()
conn = conn or self.session.connection().connection.connection
conn.create_function("title_sort", 1, _title_sort)
def dispose(self):
# global session
old_session = self.session
self.session = None
if old_session:
try: old_session.close()
except: pass
if old_session.bind:
try: old_session.bind.dispose()
except Exception: pass
for attr in list(Books.__dict__.keys()):
if attr.startswith("custom_column_"):
setattr(Books, attr, None)
for db_class in cc_classes.values():
Base.metadata.remove(db_class.__table__)
cc_classes.clear()
for table in reversed(Base.metadata.sorted_tables):
name = table.key
if name.startswith("custom_column_") or name.startswith("books_custom_column_"):
if table is not None:
Base.metadata.remove(table)
def reconnect_db(self, config, app_db_path):
self.session.close()
self.engine.dispose()
self.setup_db(config, app_db_path)

@ -33,7 +33,8 @@ from flask_login import current_user, login_required
from sqlalchemy.exc import OperationalError
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
from . import config, get_locale, db, ub, worker
from . import config, get_locale, ub, worker, db
from . import calibre_db
from .helper import order_authors, common_filters
from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required
@ -175,7 +176,7 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session):
@login_required
def delete_book(book_id, book_format):
if current_user.role_delete_books():
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).first()
if book:
try:
result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper())
@ -193,13 +194,13 @@ def delete_book(book_id, book_format):
# check if only this book links to:
# author, language, series, tags, custom columns
modify_database_object([u''], book.authors, db.Authors, db.session, 'author')
modify_database_object([u''], book.tags, db.Tags, db.session, 'tags')
modify_database_object([u''], book.series, db.Series, db.session, 'series')
modify_database_object([u''], book.languages, db.Languages, db.session, 'languages')
modify_database_object([u''], book.publishers, db.Publishers, db.session, 'publishers')
modify_database_object([u''], book.authors, db.Authors, calibre_db.session, 'author')
modify_database_object([u''], book.tags, db.Tags, calibre_db.session, 'tags')
modify_database_object([u''], book.series, db.Series, calibre_db.session, 'series')
modify_database_object([u''], book.languages, db.Languages, calibre_db.session, 'languages')
modify_database_object([u''], book.publishers, db.Publishers, calibre_db.session, 'publishers')
cc = db.session.query(db.Custom_Columns).\
cc = calibre_db.session.query(db.Custom_Columns).\
filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
for c in cc:
cc_string = "custom_column_" + str(c.id)
@ -209,32 +210,32 @@ def delete_book(book_id, book_format):
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
log.debug('remove ' + str(c.id))
db.session.delete(del_cc)
db.session.commit()
calibre_db.session.delete(del_cc)
calibre_db.session.commit()
elif c.datatype == 'rating':
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
if len(del_cc.books) == 0:
log.debug('remove ' + str(c.id))
db.session.delete(del_cc)
db.session.commit()
calibre_db.session.delete(del_cc)
calibre_db.session.commit()
else:
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
log.debug('remove ' + str(c.id))
db.session.delete(del_cc)
db.session.commit()
calibre_db.session.delete(del_cc)
calibre_db.session.commit()
else:
modify_database_object([u''], getattr(book, cc_string), db.cc_classes[c.id],
db.session, 'custom')
db.session.query(db.Books).filter(db.Books.id == book_id).delete()
calibre_db.session, 'custom')
calibre_db.session.query(db.Books).filter(db.Books.id == book_id).delete()
else:
db.session.query(db.Data).filter(db.Data.book == book.id).\
calibre_db.session.query(db.Data).filter(db.Data.book == book.id).\
filter(db.Data.format == book_format).delete()
db.session.commit()
calibre_db.session.commit()
except Exception as e:
log.debug(e)
db.session.rollback()
calibre_db.session.rollback()
else:
# book not found
log.error('Book with id "%s" could not be deleted: not found', book_id)
@ -247,9 +248,9 @@ def delete_book(book_id, book_format):
def render_edit_book(book_id):
db.update_title_sort(config)
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
book = db.session.query(db.Books)\
calibre_db.update_title_sort(config)
cc = calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
book = calibre_db.session.query(db.Books)\
.filter(db.Books.id == book_id).filter(common_filters()).first()
if not book:
@ -304,7 +305,7 @@ def edit_book_ratings(to_save, book):
ratingx2 = int(float(to_save["rating"]) * 2)
if ratingx2 != old_rating:
changed = True
is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first()
is_rating = calibre_db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first()
if is_rating:
book.ratings.append(is_rating)
else:
@ -326,7 +327,7 @@ def edit_book_languages(to_save, book):
for l in unknown_languages:
log.error('%s is not a valid language', l)
flash(_(u"%(langname)s is not a valid language", langname=l), category="error")
return modify_database_object(list(input_l), book.languages, db.Languages, db.session, 'languages')
return modify_database_object(list(input_l), book.languages, db.Languages, calibre_db.session, 'languages')
def edit_book_publisher(to_save, book):
@ -334,15 +335,15 @@ def edit_book_publisher(to_save, book):
if to_save["publisher"]:
publisher = to_save["publisher"].rstrip().strip()
if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name):
changed |= modify_database_object([publisher], book.publishers, db.Publishers, db.session, 'publisher')
changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session, 'publisher')
elif len(book.publishers):
changed |= modify_database_object([], book.publishers, db.Publishers, db.session, 'publisher')
changed |= modify_database_object([], book.publishers, db.Publishers, calibre_db.session, 'publisher')
return changed
def edit_cc_data(book_id, book, to_save):
changed = False
cc = 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()
for c in cc:
cc_string = "custom_column_" + str(c.id)
if not c.is_multiple:
@ -365,12 +366,12 @@ def edit_cc_data(book_id, book, to_save):
else:
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
db.session.delete(del_cc)
calibre_db.session.delete(del_cc)
changed = True
else:
cc_class = db.cc_classes[c.id]
new_cc = cc_class(value=to_save[cc_string], book=book_id)
db.session.add(new_cc)
calibre_db.session.add(new_cc)
changed = True
else:
@ -382,18 +383,18 @@ def edit_cc_data(book_id, book, to_save):
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
if len(del_cc.books) == 0:
db.session.delete(del_cc)
calibre_db.session.delete(del_cc)
changed = True
cc_class = db.cc_classes[c.id]
new_cc = db.session.query(cc_class).filter(
new_cc = calibre_db.session.query(cc_class).filter(
cc_class.value == to_save[cc_string].strip()).first()
# if no cc val is found add it
if new_cc is None:
new_cc = cc_class(value=to_save[cc_string].strip())
db.session.add(new_cc)
calibre_db.session.add(new_cc)
changed = True
db.session.flush()
new_cc = db.session.query(cc_class).filter(
calibre_db.session.flush()
new_cc = calibre_db.session.query(cc_class).filter(
cc_class.value == to_save[cc_string].strip()).first()
# add cc value to book
getattr(book, cc_string).append(new_cc)
@ -403,12 +404,12 @@ def edit_cc_data(book_id, book, to_save):
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
if not del_cc.books or len(del_cc.books) == 0:
db.session.delete(del_cc)
calibre_db.session.delete(del_cc)
changed = True
else:
input_tags = to_save[cc_string].split(',')
input_tags = list(map(lambda it: it.strip(), input_tags))
changed |= modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session,
changed |= modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], calibre_db.session,
'custom')
return changed
@ -446,7 +447,7 @@ def upload_single_file(request, book, book_id):
return redirect(url_for('web.show_book', book_id=book.id))
file_size = os.path.getsize(saved_filename)
is_format = db.session.query(db.Data).filter(db.Data.book == book_id).\
is_format = calibre_db.session.query(db.Data).filter(db.Data.book == book_id).\
filter(db.Data.format == file_ext.upper()).first()
# Format entry already exists, no need to update the database
@ -455,11 +456,11 @@ def upload_single_file(request, book, book_id):
else:
try:
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
db.session.add(db_format)
db.session.commit()
db.update_title_sort(config)
calibre_db.session.add(db_format)
calibre_db.session.commit()
calibre_db.update_title_sort(config)
except OperationalError as e:
db.session.rollback()
calibre_db.session.rollback()
log.error('Database error: %s', e)
flash(_(u"Database error: %(error)s.", error=e), category="error")
return redirect(url_for('web.show_book', book_id=book.id))
@ -498,8 +499,8 @@ def edit_book(book_id):
return render_edit_book(book_id)
# create the function for sorting...
db.update_title_sort(config)
book = db.session.query(db.Books)\
calibre_db.update_title_sort(config)
book = calibre_db.session.query(db.Books)\
.filter(db.Books.id == book_id).filter(common_filters()).first()
# Book not found
@ -531,13 +532,13 @@ def edit_book(book_id):
if input_authors == ['']:
input_authors = [_(u'Unknown')] # prevent empty Author
modif_date |= modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author')
modif_date |= modify_database_object(input_authors, book.authors, db.Authors, calibre_db.session, 'author')
# Search for each author if author is in database, if not, authorname and sorted authorname is generated new
# everything then is assembled for sorted author field in database
sort_authors_list = list()
for inp in input_authors:
stored_author = db.session.query(db.Authors).filter(db.Authors.name == inp).first()
stored_author = calibre_db.session.query(db.Authors).filter(db.Authors.name == inp).first()
if not stored_author:
stored_author = helper.get_sorted_author(inp)
else:
@ -581,17 +582,17 @@ def edit_book(book_id):
# Handle identifiers
input_identifiers = identifier_list(to_save, book)
modif_date |= modify_identifiers(input_identifiers, book.identifiers, db.session)
modif_date |= modify_identifiers(input_identifiers, book.identifiers, calibre_db.session)
# Handle book tags
input_tags = to_save["tags"].split(',')
input_tags = list(map(lambda it: it.strip(), input_tags))
modif_date |= modify_database_object(input_tags, book.tags, db.Tags, db.session, 'tags')
modif_date |= modify_database_object(input_tags, book.tags, db.Tags, calibre_db.session, 'tags')
# Handle book series
input_series = [to_save["series"].strip()]
input_series = [x for x in input_series if x != '']
modif_date |= modify_database_object(input_series, book.series, db.Series, db.session, 'series')
modif_date |= modify_database_object(input_series, book.series, db.Series, calibre_db.session, 'series')
if to_save["pubdate"]:
try:
@ -615,7 +616,7 @@ def edit_book(book_id):
if modif_date:
book.last_modified = datetime.utcnow()
db.session.commit()
calibre_db.session.commit()
if config.config_use_google_drive:
gdriveutils.updateGdriveCalibreFromLocal()
if "detail_view" in to_save:
@ -624,12 +625,12 @@ def edit_book(book_id):
flash(_("Metadata successfully updated"), category="success")
return render_edit_book(book_id)
else:
db.session.rollback()
calibre_db.session.rollback()
flash(error, category="error")
return render_edit_book(book_id)
except Exception as e:
log.exception(e)
db.session.rollback()
calibre_db.session.rollback()
flash(_("Error editing book, please check logfile for details"), category="error")
return redirect(url_for('web.show_book', book_id=book.id))
@ -671,8 +672,8 @@ def upload():
for requested_file in request.files.getlist("btn-upload"):
try:
# create the function for sorting...
db.update_title_sort(config)
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
calibre_db.update_title_sort(config)
calibre_db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
# check if file extension is correct
if '.' in requested_file.filename:
@ -708,13 +709,13 @@ def upload():
+ Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning")
# handle authors
is_author = db.session.query(db.Authors).filter(db.Authors.name == authr).first()
is_author = calibre_db.session.query(db.Authors).filter(db.Authors.name == authr).first()
if is_author:
db_author = is_author
authr= is_author.name
else:
db_author = db.Authors(authr, helper.get_sorted_author(authr), "")
db.session.add(db_author)
calibre_db.session.add(db_author)
title_dir = helper.get_valid_filename(title)
author_dir = helper.get_valid_filename(authr)
@ -746,29 +747,29 @@ def upload():
# handle series
db_series = None
is_series = db.session.query(db.Series).filter(db.Series.name == series).first()
is_series = calibre_db.session.query(db.Series).filter(db.Series.name == series).first()
if is_series:
db_series = is_series
elif series != '':
db_series = db.Series(series, "")
db.session.add(db_series)
calibre_db.session.add(db_series)
# add language actually one value in list
input_language = meta.languages
db_language = None
if input_language != "":
input_language = isoLanguages.get(name=input_language).part3
hasLanguage = db.session.query(db.Languages).filter(db.Languages.lang_code == input_language).first()
hasLanguage = calibre_db.session.query(db.Languages).filter(db.Languages.lang_code == input_language).first()
if hasLanguage:
db_language = hasLanguage
else:
db_language = db.Languages(input_language)
db.session.add(db_language)
calibre_db.session.add(db_language)
# If the language of the file is excluded from the users view, it's not imported, to allow the user to view
# the book it's language is set to the filter language
if db_language != current_user.filter_language() and current_user.filter_language() != "all":
db_language = db.session.query(db.Languages).\
db_language = calibre_db.session.query(db.Languages).\
filter(db.Languages.lang_code == current_user.filter_language()).first()
# combine path and normalize path from windows systems
@ -788,25 +789,25 @@ def upload():
input_tags = tags.split(',')
input_tags = list(map(lambda it: it.strip(), input_tags))
if input_tags[0] !="":
modify_database_object(input_tags, db_book.tags, db.Tags, db.session, 'tags')
modify_database_object(input_tags, db_book.tags, db.Tags, calibre_db.session, 'tags')
# flush content, get db_book.id available
db_book.data.append(db_data)
db.session.add(db_book)
db.session.flush()
calibre_db.session.add(db_book)
calibre_db.session.flush()
# add comment
book_id = db_book.id
upload_comment = Markup(meta.description).unescape()
if upload_comment != "":
db.session.add(db.Comments(upload_comment, book_id))
calibre_db.session.add(db.Comments(upload_comment, book_id))
# save data to database, reread data
db.session.commit()
db.update_title_sort(config)
calibre_db.session.commit()
calibre_db.update_title_sort(config)
# Reread book. It's important not to filter the result, as it could have language which hide it from
# current users view (tags are not stored/extracted from metadata and could also be limited)
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).first()
# upload book to gdrive if nesseccary and add "(bookid)" to folder name
if config.config_use_google_drive:
gdriveutils.updateGdriveCalibreFromLocal()
@ -823,7 +824,7 @@ def upload():
flash(_(u"Failed to Move Cover File %(file)s: %(error)s", file=new_coverpath,
error=e),
category="error")
db.session.commit()
calibre_db.session.commit()
if config.config_use_google_drive:
gdriveutils.updateGdriveCalibreFromLocal()
if error:
@ -846,7 +847,7 @@ def upload():
resp = {"location": url_for('web.show_book', book_id=db_book.id)}
return Response(json.dumps(resp), mimetype='application/json')
except OperationalError as e:
db.session.rollback()
calibre_db.session.rollback()
log.error("Database error: %s", e)
flash(_(u"Database error: %(error)s.", error=e), category="error")
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')

@ -23,7 +23,6 @@ import os
import io
import json
import mimetypes
import random
import re
import shutil
import time
@ -42,6 +41,7 @@ from flask_login import current_user
from sqlalchemy.sql.expression import true, false, and_, or_, text, func
from werkzeug.datastructures import Headers
from werkzeug.security import generate_password_hash
from . import calibre_db
try:
from urllib.parse import quote
@ -74,8 +74,8 @@ log = logger.create()
# Convert existing book entry to new format
def convert_book_format(book_id, calibrepath, old_book_format, new_book_format, user_id, kindle_mail=None):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == old_book_format).first()
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = calibre_db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == old_book_format).first()
if not data:
error_message = _(u"%(format)s format not found for book id: %(book)d", format=old_book_format, book=book_id)
log.error("convert_book_format: %s", error_message)
@ -212,7 +212,7 @@ def check_read_formats(entry):
# 3: If Pdf file is existing, it's directly send to kindle email
def send_mail(book_id, book_format, convert, kindle_mail, calibrepath, user_id):
"""Send email with attachments"""
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).first()
if convert == 1:
# returns None if success, otherwise errormessage
@ -324,7 +324,7 @@ def delete_book_file(book, calibrepath, book_format=None):
def update_dir_structure_file(book_id, calibrepath, first_author):
localbook = db.session.query(db.Books).filter(db.Books.id == book_id).first()
localbook = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).first()
path = os.path.join(calibrepath, localbook.path)
authordir = localbook.path.split('/')[0]
@ -383,7 +383,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
def update_dir_structure_gdrive(book_id, first_author):
error = False
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).first()
path = book.path
authordir = book.path.split('/')[0]
@ -494,13 +494,13 @@ def get_cover_on_failure(use_generic_cover):
def get_book_cover(book_id):
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters(allow_show_archived=True)).first()
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters(allow_show_archived=True)).first()
return get_book_cover_internal(book, use_generic_cover_on_failure=True)
def get_book_cover_with_uuid(book_uuid,
use_generic_cover_on_failure=True):
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
book = calibre_db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
return get_book_cover_internal(book, use_generic_cover_on_failure)
@ -775,7 +775,7 @@ def tags_filters():
# Creates for all stored languages a translated speaking name in the array for the UI
def speaking_language(languages=None):
if not languages:
languages = db.session.query(db.Languages).join(db.books_languages_link).join(db.Books)\
languages = calibre_db.session.query(db.Languages).join(db.books_languages_link).join(db.Books)\
.filter(common_filters())\
.group_by(text('books_languages_link.lang_code')).all()
for lang in languages:
@ -808,7 +808,7 @@ def order_authors(entry):
error = False
for auth in sort_authors:
# ToDo: How to handle not found authorname
result = db.session.query(db.Authors).filter(db.Authors.sort == auth.lstrip().strip()).first()
result = calibre_db.session.query(db.Authors).filter(db.Authors.sort == auth.lstrip().strip()).first()
if not result:
error = True
break
@ -825,12 +825,12 @@ def fill_indexpage(page, database, db_filter, order, *join):
def fill_indexpage_with_archived_books(page, database, db_filter, order, allow_show_archived, *join):
if current_user.show_detail_random():
randm = db.session.query(db.Books).filter(common_filters(allow_show_archived))\
randm = calibre_db.session.query(db.Books).filter(common_filters(allow_show_archived))\
.order_by(func.random()).limit(config.config_random_books)
else:
randm = false()
off = int(int(config.config_books_per_page) * (page - 1))
query = db.session.query(database).join(*join, isouter=True).\
query = calibre_db.session.query(database).join(*join, isouter=True).\
filter(db_filter).\
filter(common_filters(allow_show_archived))
pagination = Pagination(page, config.config_books_per_page,
@ -843,8 +843,8 @@ def fill_indexpage_with_archived_books(page, database, db_filter, order, allow_s
def get_typeahead(database, query, replace=('', ''), tag_filter=true()):
query = query or ''
db.session.connection().connection.connection.create_function("lower", 1, lcase)
entries = db.session.query(database).filter(tag_filter).\
calibre_db.session.connection().connection.connection.create_function("lower", 1, lcase)
entries = calibre_db.session.query(database).filter(tag_filter).\
filter(func.lower(database.name).ilike("%" + query + "%")).all()
json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries])
return json_dumps
@ -852,7 +852,7 @@ def get_typeahead(database, query, replace=('', ''), tag_filter=true()):
# read search results from calibre-database and return it (function is used for feed and simple search
def get_search_results(term):
db.session.connection().connection.connection.create_function("lower", 1, lcase)
calibre_db.session.connection().connection.connection.create_function("lower", 1, lcase)
q = list()
authorterms = re.split("[, ]+", term)
for authorterm in authorterms:
@ -860,7 +860,7 @@ def get_search_results(term):
db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + term + "%"))
return db.session.query(db.Books).filter(common_filters()).filter(
return calibre_db.session.query(db.Books).filter(common_filters()).filter(
or_(db.Books.tags.any(func.lower(db.Tags.name).ilike("%" + term + "%")),
db.Books.series.any(func.lower(db.Series.name).ilike("%" + term + "%")),
db.Books.authors.any(and_(*q)),
@ -870,7 +870,7 @@ def get_search_results(term):
def get_cc_columns(filter_config_custom_read=False):
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
tmpcc = calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
cc = []
r = None
if config.config_columns_to_ignore:
@ -887,9 +887,9 @@ def get_cc_columns(filter_config_custom_read=False):
def get_download_link(book_id, book_format, client):
book_format = book_format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
if book:
data1 = db.session.query(db.Data).filter(db.Data.book == book.id)\
data1 = calibre_db.session.query(db.Data).filter(db.Data.book == book.id)\
.filter(db.Data.format == book_format.upper()).first()
else:
abort(404)
@ -911,13 +911,13 @@ def get_download_link(book_id, book_format, client):
def check_exists_book(authr, title):
db.session.connection().connection.connection.create_function("lower", 1, lcase)
calibre_db.session.connection().connection.connection.create_function("lower", 1, lcase)
q = list()
authorterms = re.split(r'\s*&\s*', authr)
for authorterm in authorterms:
q.append(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + authorterm + "%")))
return db.session.query(db.Books).filter(
return calibre_db.session.query(db.Books).filter(
and_(db.Books.authors.any(and_(*q)),
func.lower(db.Books.title).ilike("%" + title + "%")
)).first()

@ -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
@ -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.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
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.session.query(db.Books).filter(db.Books.uuid == item["RevisionId"]).one_or_none()
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.session.query(db.Books).filter(db.Books.uuid == item["RevisionId"]).one_or_none()
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.session.query(db.Books).filter(db.Books.id == book_shelf.book_id).one_or_none()
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.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
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.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
if not book:
log.info(u"Book %s not found in database", book_uuid)
return redirect_or_proxy_request()

@ -30,7 +30,7 @@ from flask_login import current_user
from sqlalchemy.sql.expression import func, text, or_, and_
from werkzeug.security import check_password_hash
from . import constants, logger, config, db, ub, services, get_locale, isoLanguages
from . import constants, logger, config, db, calibre_db, ub, services, get_locale, isoLanguages
from .helper import fill_indexpage, get_download_link, get_book_cover, speaking_language
from .pagination import Pagination
from .web import common_filters, get_search_results, render_read_books, download_required
@ -108,7 +108,7 @@ def feed_new():
@opds.route("/opds/discover")
@requires_basic_auth_if_no_ano
def feed_discover():
entries = db.session.query(db.Books).filter(common_filters()).order_by(func.random())\
entries = calibre_db.session.query(db.Books).filter(common_filters()).order_by(func.random())\
.limit(config.config_books_per_page)
pagination = Pagination(1, config.config_books_per_page, int(config.config_books_per_page))
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
@ -133,10 +133,10 @@ def feed_hot():
hot_books = all_books.offset(off).limit(config.config_books_per_page)
entries = list()
for book in hot_books:
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.Downloads.book_id).first()
downloadBook = calibre_db.session.query(db.Books).filter(db.Books.id == book.Downloads.book_id).first()
if downloadBook:
entries.append(
db.session.query(db.Books).filter(common_filters())
calibre_db.session.query(db.Books).filter(common_filters())
.filter(db.Books.id == book.Downloads.book_id).first()
)
else:
@ -153,11 +153,11 @@ def feed_hot():
@requires_basic_auth_if_no_ano
def feed_authorindex():
off = request.args.get("offset") or 0
entries = db.session.query(db.Authors).join(db.books_authors_link).join(db.Books).filter(common_filters())\
entries = calibre_db.session.query(db.Authors).join(db.books_authors_link).join(db.Books).filter(common_filters())\
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).limit(config.config_books_per_page)\
.offset(off)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(db.session.query(db.Authors).all()))
len(calibre_db.session.query(db.Authors).all()))
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_author', pagination=pagination)
@ -176,11 +176,11 @@ def feed_author(book_id):
@requires_basic_auth_if_no_ano
def feed_publisherindex():
off = request.args.get("offset") or 0
entries = db.session.query(db.Publishers).join(db.books_publishers_link).join(db.Books).filter(common_filters())\
entries = calibre_db.session.query(db.Publishers).join(db.books_publishers_link).join(db.Books).filter(common_filters())\
.group_by(text('books_publishers_link.publisher')).order_by(db.Publishers.sort)\
.limit(config.config_books_per_page).offset(off)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(db.session.query(db.Publishers).all()))
len(calibre_db.session.query(db.Publishers).all()))
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_publisher', pagination=pagination)
@ -199,10 +199,10 @@ def feed_publisher(book_id):
@requires_basic_auth_if_no_ano
def feed_categoryindex():
off = request.args.get("offset") or 0
entries = db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(common_filters())\
entries = calibre_db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(common_filters())\
.group_by(text('books_tags_link.tag')).order_by(db.Tags.name).offset(off).limit(config.config_books_per_page)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(db.session.query(db.Tags).all()))
len(calibre_db.session.query(db.Tags).all()))
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_category', pagination=pagination)
@ -221,10 +221,10 @@ def feed_category(book_id):
@requires_basic_auth_if_no_ano
def feed_seriesindex():
off = request.args.get("offset") or 0
entries = db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(common_filters())\
entries = calibre_db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(common_filters())\
.group_by(text('books_series_link.series')).order_by(db.Series.sort).offset(off).all()
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(db.session.query(db.Series).all()))
len(calibre_db.session.query(db.Series).all()))
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_series', pagination=pagination)
@ -243,7 +243,7 @@ def feed_series(book_id):
@requires_basic_auth_if_no_ano
def feed_ratingindex():
off = request.args.get("offset") or 0
entries = db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
entries = calibre_db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
(db.Ratings.rating / 2).label('name')) \
.join(db.books_ratings_link).join(db.Books).filter(common_filters()) \
.group_by(text('books_ratings_link.rating')).order_by(db.Ratings.rating).all()
@ -271,7 +271,7 @@ def feed_ratings(book_id):
@requires_basic_auth_if_no_ano
def feed_formatindex():
off = request.args.get("offset") or 0
entries = db.session.query(db.Data).join(db.Books).filter(common_filters()) \
entries = calibre_db.session.query(db.Data).join(db.Books).filter(common_filters()) \
.group_by(db.Data.format).order_by(db.Data.format).all()
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(entries))
@ -305,7 +305,7 @@ def feed_languagesindex():
cur_l = LC.parse(current_user.filter_language())
except UnknownLocaleError:
cur_l = None
languages = db.session.query(db.Languages).filter(
languages = calibre_db.session.query(db.Languages).filter(
db.Languages.lang_code == current_user.filter_language()).all()
if cur_l:
languages[0].name = cur_l.get_language_name(get_locale())
@ -356,7 +356,7 @@ def feed_shelf(book_id):
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == book_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).first()
cur_book = calibre_db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
result.append(cur_book)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(result))
@ -378,7 +378,7 @@ def opds_download_link(book_id, book_format):
@opds.route("/ajax/book/<string:uuid>", defaults={'library': ""})
@requires_basic_auth_if_no_ano
def get_metadata_calibre_companion(uuid, library):
entry = db.session.query(db.Books).filter(db.Books.uuid.like("%" + uuid + "%")).first()
entry = calibre_db.session.query(db.Books).filter(db.Books.uuid.like("%" + uuid + "%")).first()
if entry is not None:
js = render_template('json.txt', entry=entry)
response = make_response(js)

@ -196,6 +196,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

@ -28,7 +28,7 @@ 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
@ -320,11 +320,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.session.query(db.Books).filter(db.Books.id == book.book_id).filter(common_filters()).first()
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.session.query(db.Books).filter(db.Books.id == book.book_id).first()
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 +356,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.session.query(db.Books).filter(db.Books.id == book.book_id).filter(common_filters()).first()
if cur_book:
result.append({'title': cur_book.title,
'id': cur_book.id,
@ -364,7 +364,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.session.query(db.Books).filter(db.Books.id == book.book_id).first()
result.append({'title': _('Hidden Book'),
'id': cur_book.id,
'author': [],

@ -50,6 +50,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
from . import constants, logger, isoLanguages, services, worker
from . import searched_ids, lm, babel, db, ub, config, get_locale, app
from . import calibre_db
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
from .helper import common_filters, get_search_results, fill_indexpage, fill_indexpage_with_archived_books, \
speaking_language, check_valid_domain, order_authors, get_typeahead, render_task_status, json_serial, \
@ -438,21 +439,21 @@ def toggle_read(book_id):
ub.session.commit()
else:
try:
db.update_title_sort(config)
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
calibre_db.update_title_sort(config)
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
if len(read_status):
read_status[0].value = not read_status[0].value
db.session.commit()
calibre_db.session.commit()
else:
cc_class = db.cc_classes[config.config_read_column]
new_cc = cc_class(value=1, book=book_id)
db.session.add(new_cc)
db.session.commit()
calibre_db.session.add(new_cc)
calibre_db.session.commit()
except KeyError:
log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column)
except OperationalError as e:
db.session.rollback()
calibre_db.session.rollback()
log.error(u"Read status could not set: %e", e)
return ""
@ -496,7 +497,7 @@ def update_view():
@web.route("/ajax/getcomic/<int:book_id>/<book_format>/<int:page>")
@login_required
def get_comic_book(book_id, book_format, page):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).first()
if not book:
return "", 204
else:
@ -589,8 +590,8 @@ def get_languages_json():
@login_required_if_no_ano
def get_matching_tags():
tag_dict = {'tags': []}
q = db.session.query(db.Books)
db.session.connection().connection.connection.create_function("lower", 1, lcase)
q = calibre_db.session.query(db.Books)
calibre_db.session.connection().connection.connection.create_function("lower", 1, lcase)
author_input = request.args.get('author_name') or ''
title_input = request.args.get('book_title') or ''
include_tag_inputs = request.args.getlist('include_tag') or ''
@ -692,7 +693,7 @@ def books_list(data, sort, book_id, page):
def render_hot_books(page):
if current_user.check_visibility(constants.SIDEBAR_HOT):
if current_user.show_detail_random():
random = db.session.query(db.Books).filter(common_filters()) \
random = calibre_db.session.query(db.Books).filter(common_filters()) \
.order_by(func.random()).limit(config.config_random_books)
else:
random = false()
@ -702,7 +703,7 @@ def render_hot_books(page):
hot_books = all_books.offset(off).limit(config.config_books_per_page)
entries = list()
for book in hot_books:
downloadBook = db.session.query(db.Books).filter(common_filters()).filter(
downloadBook = calibre_db.session.query(db.Books).filter(common_filters()).filter(
db.Books.id == book.Downloads.book_id).first()
if downloadBook:
entries.append(downloadBook)
@ -727,7 +728,7 @@ def render_author_books(page, author_id, order):
category="error")
return redirect(url_for("web.index"))
author = db.session.query(db.Authors).get(author_id)
author = calibre_db.session.query(db.Authors).get(author_id)
author_name = author.name.replace('|', ',')
author_info = None
@ -742,7 +743,7 @@ def render_author_books(page, author_id, order):
def render_publisher_books(page, book_id, order):
publisher = db.session.query(db.Publishers).filter(db.Publishers.id == book_id).first()
publisher = calibre_db.session.query(db.Publishers).filter(db.Publishers.id == book_id).first()
if publisher:
entries, random, pagination = fill_indexpage(page, db.Books,
db.Books.publishers.any(db.Publishers.id == book_id),
@ -755,7 +756,7 @@ def render_publisher_books(page, book_id, order):
def render_series_books(page, book_id, order):
name = db.session.query(db.Series).filter(db.Series.id == book_id).first()
name = calibre_db.session.query(db.Series).filter(db.Series.id == book_id).first()
if name:
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.id == book_id),
[db.Books.series_index, order[0]])
@ -766,7 +767,7 @@ def render_series_books(page, book_id, order):
def render_ratings_books(page, book_id, order):
name = db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
name = calibre_db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.id == book_id),
[db.Books.timestamp.desc(), order[0]])
if name and name.rating <= 10:
@ -777,7 +778,7 @@ def render_ratings_books(page, book_id, order):
def render_formats_books(page, book_id, order):
name = db.session.query(db.Data).filter(db.Data.format == book_id.upper()).first()
name = calibre_db.session.query(db.Data).filter(db.Data.format == book_id.upper()).first()
if name:
entries, random, pagination = fill_indexpage(page, db.Books,
db.Books.data.any(db.Data.format == book_id.upper()),
@ -789,7 +790,7 @@ def render_formats_books(page, book_id, order):
def render_category_books(page, book_id, order):
name = db.session.query(db.Tags).filter(db.Tags.id == book_id).first()
name = calibre_db.session.query(db.Tags).filter(db.Tags.id == book_id).first()
if name:
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.id == book_id),
[order[0], db.Series.name, db.Books.series_index],
@ -825,10 +826,10 @@ def books_table():
@login_required_if_no_ano
def author_list():
if current_user.check_visibility(constants.SIDEBAR_AUTHOR):
entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count')) \
entries = calibre_db.session.query(db.Authors, func.count('books_authors_link.book').label('count')) \
.join(db.books_authors_link).join(db.Books).filter(common_filters()) \
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).all()
charlist = db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \
charlist = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \
.join(db.books_authors_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all()
for entry in entries:
@ -843,10 +844,10 @@ def author_list():
@login_required_if_no_ano
def publisher_list():
if current_user.check_visibility(constants.SIDEBAR_PUBLISHER):
entries = db.session.query(db.Publishers, func.count('books_publishers_link.book').label('count')) \
entries = calibre_db.session.query(db.Publishers, func.count('books_publishers_link.book').label('count')) \
.join(db.books_publishers_link).join(db.Books).filter(common_filters()) \
.group_by(text('books_publishers_link.publisher')).order_by(db.Publishers.name).all()
charlist = db.session.query(func.upper(func.substr(db.Publishers.name, 1, 1)).label('char')) \
charlist = calibre_db.session.query(func.upper(func.substr(db.Publishers.name, 1, 1)).label('char')) \
.join(db.books_publishers_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Publishers.name, 1, 1))).all()
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
@ -860,19 +861,19 @@ def publisher_list():
def series_list():
if current_user.check_visibility(constants.SIDEBAR_SERIES):
if current_user.series_view == 'list':
entries = db.session.query(db.Series, func.count('books_series_link.book').label('count')) \
entries = calibre_db.session.query(db.Series, func.count('books_series_link.book').label('count')) \
.join(db.books_series_link).join(db.Books).filter(common_filters()) \
.group_by(text('books_series_link.series')).order_by(db.Series.sort).all()
charlist = db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
charlist = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
.join(db.books_series_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
title=_(u"Series"), page="serieslist", data="series")
else:
entries = db.session.query(db.Books, func.count('books_series_link').label('count')) \
entries = calibre_db.session.query(db.Books, func.count('books_series_link').label('count')) \
.join(db.books_series_link).join(db.Series).filter(common_filters()) \
.group_by(text('books_series_link.series')).order_by(db.Series.sort).all()
charlist = db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
charlist = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
.join(db.books_series_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
@ -886,9 +887,9 @@ def series_list():
@login_required_if_no_ano
def ratings_list():
if current_user.check_visibility(constants.SIDEBAR_RATING):
entries = db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
entries = calibre_db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
(db.Ratings.rating / 2).label('name')) \
.join(db.books_ratings_link).join(db.Books).filter(common_filters()) \
.join(calibre_db.books_ratings_link).join(db.Books).filter(common_filters()) \
.group_by(text('books_ratings_link.rating')).order_by(db.Ratings.rating).all()
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
title=_(u"Ratings list"), page="ratingslist", data="ratings")
@ -900,7 +901,7 @@ def ratings_list():
@login_required_if_no_ano
def formats_list():
if current_user.check_visibility(constants.SIDEBAR_FORMAT):
entries = db.session.query(db.Data, func.count('data.book').label('count'), db.Data.format.label('format')) \
entries = calibre_db.session.query(db.Data, func.count('data.book').label('count'), db.Data.format.label('format')) \
.join(db.Books).filter(common_filters()) \
.group_by(db.Data.format).order_by(db.Data.format).all()
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
@ -922,13 +923,13 @@ def language_overview():
cur_l = LC.parse(current_user.filter_language())
except UnknownLocaleError:
cur_l = None
languages = db.session.query(db.Languages).filter(
languages = calibre_db.session.query(db.Languages).filter(
db.Languages.lang_code == current_user.filter_language()).all()
if cur_l:
languages[0].name = cur_l.get_language_name(get_locale())
else:
languages[0].name = _(isoLanguages.get(part3=languages[0].lang_code).name)
lang_counter = db.session.query(db.books_languages_link,
lang_counter = calibre_db.session.query(db.books_languages_link,
func.count('books_languages_link.book').label('bookcount')).group_by(
text('books_languages_link.lang_code')).all()
return render_title_template('languages.html', languages=languages, lang_counter=lang_counter,
@ -942,10 +943,10 @@ def language_overview():
@login_required_if_no_ano
def category_list():
if current_user.check_visibility(constants.SIDEBAR_CATEGORY):
entries = db.session.query(db.Tags, func.count('books_tags_link.book').label('count')) \
entries = calibre_db.session.query(db.Tags, func.count('books_tags_link.book').label('count')) \
.join(db.books_tags_link).join(db.Books).order_by(db.Tags.name).filter(common_filters()) \
.group_by(text('books_tags_link.tag')).all()
charlist = db.session.query(func.upper(func.substr(db.Tags.name, 1, 1)).label('char')) \
charlist = calibre_db.session.query(func.upper(func.substr(db.Tags.name, 1, 1)).label('char')) \
.join(db.books_tags_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Tags.name, 1, 1))).all()
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
@ -1003,8 +1004,8 @@ def search():
def advanced_search():
# Build custom columns names
cc = get_cc_columns()
db.session.connection().connection.connection.create_function("lower", 1, lcase)
q = db.session.query(db.Books).filter(common_filters()).order_by(db.Books.sort)
calibre_db.session.connection().connection.connection.create_function("lower", 1, lcase)
q = calibre_db.session.query(db.Books).filter(common_filters()).order_by(db.Books.sort)
include_tag_inputs = request.args.getlist('include_tag')
exclude_tag_inputs = request.args.getlist('exclude_tag')
@ -1057,11 +1058,11 @@ def advanced_search():
format='medium', locale=get_locale())])
except ValueError:
pub_start = u""
tag_names = db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all()
tag_names = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all()
searchterm.extend(tag.name for tag in tag_names)
serie_names = db.session.query(db.Series).filter(db.Series.id.in_(include_series_inputs)).all()
serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(include_series_inputs)).all()
searchterm.extend(serie.name for serie in serie_names)
language_names = db.session.query(db.Languages).filter(db.Languages.id.in_(include_languages_inputs)).all()
language_names = calibre_db.session.query(db.Languages).filter(db.Languages.id.in_(include_languages_inputs)).all()
if language_names:
language_names = speaking_language(language_names)
searchterm.extend(language.name for language in language_names)
@ -1136,11 +1137,11 @@ def advanced_search():
return render_title_template('search.html', adv_searchterm=searchterm,
entries=q, title=_(u"search"), page="search")
# prepare data for search-form
tags = db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(common_filters()) \
tags = calibre_db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(common_filters()) \
.group_by(text('books_tags_link.tag')).order_by(db.Tags.name).all()
series = db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(common_filters()) \
series = calibre_db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(common_filters()) \
.group_by(text('books_series_link.series')).order_by(db.Series.name).filter(common_filters()).all()
extensions = db.session.query(db.Data).join(db.Books).filter(common_filters()) \
extensions = calibre_db.session.query(db.Data).join(db.Books).filter(common_filters()) \
.group_by(db.Data.format).order_by(db.Data.format).all()
if current_user.filter_language() == u"all":
@ -1229,8 +1230,8 @@ def get_cover(book_id):
@viewer_required
def serve_book(book_id, book_format, anyname):
book_format = book_format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper()) \
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = calibre_db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper()) \
.first()
log.info('Serving book: %s', data.name)
if config.config_use_google_drive:
@ -1522,9 +1523,9 @@ def profile():
oauth_status = None
for book in current_user.downloads:
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
downloadBook = calibre_db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
if downloadBook:
downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
downloads.append(calibre_db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
else:
ub.delete_download(book.book_id)
if request.method == "POST":
@ -1603,7 +1604,7 @@ def profile():
@login_required_if_no_ano
@viewer_required
def read_book(book_id, book_format):
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
book = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
if not book:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
log.debug(u"Error opening eBook. File does not exist or file is not accessible:")
@ -1627,7 +1628,7 @@ def read_book(book_id, book_format):
else:
for fileExt in constants.EXTENSIONS_AUDIO:
if book_format.lower() == fileExt:
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
entries = calibre_db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
log.debug(u"Start mp3 listening for %d", book_id)
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
@ -1653,7 +1654,7 @@ def read_book(book_id, book_format):
@web.route("/book/<int:book_id>")
@login_required_if_no_ano
def show_book(book_id):
entries = db.session.query(db.Books).filter(and_(db.Books.id == book_id,
entries = calibre_db.session.query(db.Books).filter(and_(db.Books.id == book_id,
common_filters(allow_show_archived=True))).first()
if entries:
for index in range(0, len(entries.languages)):

@ -24,10 +24,10 @@ import smtplib
import socket
import time
import threading
import queue
from glob import glob
from shutil import copyfile
from datetime import datetime
from sqlalchemy.exc import OperationalError
try:
from StringIO import StringIO
@ -46,7 +46,8 @@ from email.utils import make_msgid
from email.generator import Generator
from flask_babel import gettext as _
from . import db, logger, config
from . import calibre_db, db
from . import logger, config
from .subproc_wrapper import process_open
from . import gdriveutils
@ -190,6 +191,8 @@ class WorkerThread(threading.Thread):
self.UIqueue = list()
self.asyncSMTP = None
self.id = 0
self.db_queue = queue.Queue()
calibre_db.add_queue(self.db_queue)
self.doLock = threading.Lock()
# Main thread loop starting the different tasks
@ -275,6 +278,18 @@ class WorkerThread(threading.Thread):
self.doLock.acquire()
index = self.current
self.doLock.release()
'''dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
engine = create_engine('sqlite://',
echo=False,
isolation_level="SERIALIZABLE",
connect_args={'check_same_thread': True})
engine.execute("attach database '{}' as calibre;".format(dbpath))
conn = engine.connect()
Session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
w_session = Session()
engine.execute("attach database '{}' as calibre;".format(dbpath))'''
file_path = self.queue[index]['file_path']
bookid = self.queue[index]['bookid']
format_old_ext = u'.' + self.queue[index]['settings']['old_book_format'].lower()
@ -285,7 +300,7 @@ class WorkerThread(threading.Thread):
# this will allow send to kindle workflow to continue to work
if os.path.isfile(file_path + format_new_ext):
log.info("Book id %d already converted to %s", bookid, format_new_ext)
cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first()
cur_book = calibre_db.session.query(db.Books).filter(db.Books.id == bookid).first()
self.queue[index]['path'] = file_path
self.queue[index]['title'] = cur_book.title
self._handleSuccess()
@ -309,19 +324,26 @@ class WorkerThread(threading.Thread):
check, error_message = self._convert_calibre(file_path, format_old_ext, format_new_ext, index)
if check == 0:
cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first()
cur_book = calibre_db.session.query(db.Books).filter(db.Books.id == bookid).first()
if os.path.isfile(file_path + format_new_ext):
# self.db_queue.join()
new_format = db.Data(name=cur_book.data[0].name,
book_format=self.queue[index]['settings']['new_book_format'].upper(),
book=bookid, uncompressed_size=os.path.getsize(file_path + format_new_ext))
cur_book.data.append(new_format)
task = {'task':'add_format','id': bookid, 'format': new_format}
self.db_queue.put(task)
# To Do how to handle error?
print('finished')
'''cur_book.data.append(new_format)
try:
db.session.commit()
# db.session.merge(cur_book)
calibre_db.session.commit()
except OperationalError as e:
db.session.rollback()
calibre_db.session.rollback()
log.error("Database error: %s", e)
self._handleError(_(u"Database error: %(error)s.", error=e))
return
return'''
self.queue[index]['path'] = cur_book.path
self.queue[index]['title'] = cur_book.title
@ -375,6 +397,7 @@ class WorkerThread(threading.Thread):
# process returncode
check = p.returncode
calibre_traceback = p.stderr.readlines()
error_message = ""
for ele in calibre_traceback:
if sys.version_info < (3, 0):
ele = ele.decode('utf-8')

Loading…
Cancel
Save