From 64e4a1155cc0e7aa8bd7e178a83086aaee7df1d4 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Tue, 18 Jun 2024 20:13:26 +0200 Subject: [PATCH] Code cosmetics Bugfix missing bleach tags (#3080) Bugfix pdfreader --- cps/MyLoginManager.py | 3 +- cps/__init__.py | 1 + cps/admin.py | 25 ++-- cps/babel.py | 1 + cps/clean_html.py | 10 +- cps/cli.py | 35 ++++-- cps/config_sql.py | 37 ++++-- cps/constants.py | 3 +- cps/db.py | 40 ++++--- cps/debug_info.py | 2 + cps/dep_check.py | 2 + cps/editbooks.py | 25 ---- cps/epub.py | 1 + cps/epub_helper.py | 13 ++- cps/error_handler.py | 3 +- cps/file_helper.py | 1 + cps/gdrive.py | 10 +- cps/gdriveutils.py | 31 +++-- cps/gevent_wsgi.py | 1 + cps/helper.py | 24 ++-- cps/isoLanguages.py | 1 - cps/iso_language_names.py | 2 +- cps/kobo.py | 18 +-- cps/kobo_auth.py | 2 +- cps/kobo_sync_status.py | 4 +- cps/main.py | 5 +- cps/metadata_provider/amazon.py | 4 +- cps/metadata_provider/douban.py | 3 +- cps/metadata_provider/lubimyczytac.py | 2 +- cps/oauth_bb.py | 4 +- cps/opds.py | 1 + cps/server.py | 6 +- cps/services/SyncToken.py | 10 +- cps/services/gmail.py | 1 + cps/services/goodreads_support.py | 9 +- cps/services/simpleldap.py | 7 +- cps/tasks/convert.py | 10 +- cps/tasks/database.py | 1 - cps/tasks/mail.py | 2 +- cps/tasks/metadata_backup.py | 1 + cps/tasks/thumbnail.py | 5 +- cps/tasks/upload.py | 1 + cps/templates/readpdf.html | 19 +++- cps/ub.py | 14 +++ cps/updater.py | 2 + setup.cfg | 3 +- test/Calibre-Web TestSummary_Linux.html | 145 +++++++++++------------- 47 files changed, 322 insertions(+), 228 deletions(-) diff --git a/cps/MyLoginManager.py b/cps/MyLoginManager.py index 39e7e4a5..c4025819 100644 --- a/cps/MyLoginManager.py +++ b/cps/MyLoginManager.py @@ -26,12 +26,13 @@ from flask import session, current_app from flask_login.utils import decode_cookie from flask_login.signals import user_loaded_from_cookie + class MyLoginManager(LoginManager): def _session_protection_failed(self): sess = session._get_current_object() ident = self._session_identifier_generator() if(sess and not (len(sess) == 1 - and sess.get('csrf_token', None))) and ident != sess.get('_id', None): + and sess.get('csrf_token', None))) and ident != sess.get('_id', None): return super(). _session_protection_failed() return False diff --git a/cps/__init__.py b/cps/__init__.py index 9601c63b..1200c2e1 100755 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -107,6 +107,7 @@ if limiter_present: else: limiter = None + def create_app(): if csrf: csrf.init_app(app) diff --git a/cps/admin.py b/cps/admin.py index 3382e566..a39b4342 100755 --- a/cps/admin.py +++ b/cps/admin.py @@ -479,7 +479,7 @@ def edit_list_user(param): elif param.endswith('role'): value = int(vals['field_index']) if user.name == "Guest" and value in \ - [constants.ROLE_ADMIN, constants.ROLE_PASSWD, constants.ROLE_EDIT_SHELFS]: + [constants.ROLE_ADMIN, constants.ROLE_PASSWD, constants.ROLE_EDIT_SHELFS]: raise Exception(_("Guest can't have this role")) # check for valid value, last on checks for power of 2 value if value > 0 and value <= constants.ROLE_VIEWER and (value & value - 1 == 0 or value == 1): @@ -945,7 +945,7 @@ def do_full_kobo_sync(userid): def check_valid_read_column(column): if column != "0": if not calibre_db.session.query(db.CustomColumns).filter(db.CustomColumns.id == column) \ - .filter(and_(db.CustomColumns.datatype == 'bool', db.CustomColumns.mark_for_delete == 0)).all(): + .filter(and_(db.CustomColumns.datatype == 'bool', db.CustomColumns.mark_for_delete == 0)).all(): return False return True @@ -953,7 +953,7 @@ def check_valid_read_column(column): def check_valid_restricted_column(column): if column != "0": if not calibre_db.session.query(db.CustomColumns).filter(db.CustomColumns.id == column) \ - .filter(and_(db.CustomColumns.datatype == 'text', db.CustomColumns.mark_for_delete == 0)).all(): + .filter(and_(db.CustomColumns.datatype == 'text', db.CustomColumns.mark_for_delete == 0)).all(): return False return True @@ -999,10 +999,7 @@ def get_drives(current): for d in string.ascii_uppercase: if os.path.exists('{}:'.format(d)) and current[0].lower() != d.lower(): drive = "{}:\\".format(d) - data = {"name": drive, "fullpath": drive} - data["sort"] = "_" + data["fullpath"].lower() - data["type"] = "dir" - data["size"] = "" + data = {"name": drive, "fullpath": drive, "type": "dir", "size": "", "sort": "_" + drive.lower()} drive_letters.append(data) return drive_letters @@ -1142,12 +1139,12 @@ def _configuration_oauth_helper(to_save): reboot_required = False for element in oauthblueprints: if to_save["config_" + str(element['id']) + "_oauth_client_id"] != element['oauth_client_id'] \ - or to_save["config_" + str(element['id']) + "_oauth_client_secret"] != element['oauth_client_secret']: + or to_save["config_" + str(element['id']) + "_oauth_client_secret"] != element['oauth_client_secret']: reboot_required = True element['oauth_client_id'] = to_save["config_" + str(element['id']) + "_oauth_client_id"] element['oauth_client_secret'] = to_save["config_" + str(element['id']) + "_oauth_client_secret"] if to_save["config_" + str(element['id']) + "_oauth_client_id"] \ - and to_save["config_" + str(element['id']) + "_oauth_client_secret"]: + and to_save["config_" + str(element['id']) + "_oauth_client_secret"]: active_oauths += 1 element["active"] = 1 else: @@ -1202,9 +1199,9 @@ def _configuration_ldap_helper(to_save): config.save() if not config.config_ldap_provider_url \ - or not config.config_ldap_port \ - or not config.config_ldap_dn \ - or not config.config_ldap_user_object: + or not config.config_ldap_port \ + or not config.config_ldap_dn \ + or not config.config_ldap_user_object: return reboot_required, _configuration_result(_('Please Enter a LDAP Provider, ' 'Port, DN and User Object Identifier')) @@ -1372,7 +1369,7 @@ def update_scheduledtasks(): error = False to_save = request.form.to_dict() if 0 <= int(to_save.get("schedule_start_time")) <= 23: - _config_int( to_save, "schedule_start_time") + _config_int(to_save, "schedule_start_time") else: flash(_("Invalid start time for task specified"), category="error") error = True @@ -1720,7 +1717,7 @@ def _db_configuration_update_helper(): return _db_configuration_result('{}'.format(ex), gdrive_error) if db_change or not db_valid or not config.db_configured \ - or config.config_calibre_dir != to_save["config_calibre_dir"]: + or config.config_calibre_dir != to_save["config_calibre_dir"]: if not os.path.exists(metadata_db) or not to_save['config_calibre_dir']: return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdrive_error) else: diff --git a/cps/babel.py b/cps/babel.py index e6c32806..21ed4fc7 100644 --- a/cps/babel.py +++ b/cps/babel.py @@ -10,6 +10,7 @@ log = logger.create() babel = Babel() + def get_locale(): # if a user is logged in, use the locale from the user settings if current_user is not None and hasattr(current_user, "locale"): diff --git a/cps/clean_html.py b/cps/clean_html.py index 93a5d292..907bc59e 100644 --- a/cps/clean_html.py +++ b/cps/clean_html.py @@ -24,13 +24,21 @@ log = logger.create() try: # at least bleach 6.0 is needed -> incomplatible change from list arguments to set arguments from bleach import clean as clean_html + from bleach.sanitizer import ALLOWED_TAGS + bleach = True except ImportError: from nh3 import clean as clean_html + bleach = False def clean_string(unsafe_text, book_id=0): try: - safe_text = clean_html(unsafe_text) + if bleach: + allowed_tags = list(ALLOWED_TAGS) + allowed_tags.extend(['p', 'span', 'div', 'pre']) + safe_text = clean_html(unsafe_text, tags=set(allowed_tags)) + else: + safe_text = clean_html(unsafe_text) except ParserError as e: log.error("Comments of book {} are corrupted: {}".format(book_id, e)) safe_text = "" diff --git a/cps/cli.py b/cps/cli.py index 64842259..5bf289b8 100644 --- a/cps/cli.py +++ b/cps/cli.py @@ -35,6 +35,19 @@ def version_info(): class CliParameter(object): + def __init__(self): + self.user_credentials = None + self.ip_address = None + self.allow_localhost = None + self.reconnect_enable = None + self.memory_backend = None + self.dry_run = None + self.certfilepath = None + self.keyfilepath = None + self.gd_path = None + self.settings_path = None + self.logpath = None + def init(self): self.arg_parser() @@ -44,22 +57,25 @@ class CliParameter(object): prog='cps.py') parser.add_argument('-p', metavar='path', help='path and name to settings db, e.g. /opt/cw.db') parser.add_argument('-g', metavar='path', help='path and name to gdrive db, e.g. /opt/gd.db') - parser.add_argument('-c', metavar='path', help='path and name to SSL certfile, e.g. /opt/test.cert, ' - 'works only in combination with keyfile') + parser.add_argument('-c', metavar='path', help='path and name to SSL certfile, ' + 'e.g. /opt/test.cert, works only in combination with keyfile') parser.add_argument('-k', metavar='path', help='path and name to SSL keyfile, e.g. /opt/test.key, ' 'works only in combination with certfile') parser.add_argument('-o', metavar='path', help='path and name Calibre-Web logfile') - parser.add_argument('-v', '--version', action='version', help='Shows version number and exits Calibre-Web', + parser.add_argument('-v', '--version', action='version', help='Shows version number ' + 'and exits Calibre-Web', version=version_info()) parser.add_argument('-i', metavar='ip-address', help='Server IP-Address to listen') - parser.add_argument('-m', action='store_true', help='Use Memory-backend as limiter backend, use this parameter in case of miss configured backend') + parser.add_argument('-m', action='store_true', + help='Use Memory-backend as limiter backend, use this parameter ' + 'in case of miss configured backend') parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password and exits Calibre-Web') - parser.add_argument('-f', action='store_true', help='Flag is depreciated and will be removed in next version') parser.add_argument('-l', action='store_true', help='Allow loading covers from localhost') - parser.add_argument('-d', action='store_true', help='Dry run of updater to check file permissions in advance ' - 'and exits Calibre-Web') - parser.add_argument('-r', action='store_true', help='Enable public database reconnect route under /reconnect') + parser.add_argument('-d', action='store_true', help='Dry run of updater to check file permissions ' + 'in advance and exits Calibre-Web') + parser.add_argument('-r', action='store_true', help='Enable public database reconnect ' + 'route under /reconnect') args = parser.parse_args() self.logpath = args.o or "" @@ -130,6 +146,3 @@ class CliParameter(object): if self.user_credentials and ":" not in self.user_credentials: print("No valid 'username:password' format") sys.exit(3) - - if args.f: - print("Warning: -f flag is depreciated and will be removed in next version") diff --git a/cps/config_sql.py b/cps/config_sql.py index 6d5b1177..33dba623 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -48,6 +48,7 @@ class _Flask_Settings(_Base): flask_session_key = Column(BLOB, default=b"") def __init__(self, key): + super().__init__() self.flask_session_key = key @@ -82,7 +83,9 @@ class _Settings(_Base): config_random_books = Column(Integer, default=4) config_authors_max = Column(Integer, default=0) config_read_column = Column(Integer, default=0) - config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines|Le|La|Les|L\'|Un|Une)\s+') + config_title_regex = Column(String, + default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine' + r'|Einen|Dem|Des|Einem|Eines|Le|La|Les|L\'|Un|Une)\s+') config_theme = Column(Integer, default=0) config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL) @@ -178,6 +181,26 @@ class _Settings(_Base): class ConfigSQL(object): # pylint: disable=no-member def __init__(self): + '''self.config_calibre_uuid = None + self.config_calibre_split_dir = None + self.dirty = None + self.config_logfile = None + self.config_upload_formats = None + self.mail_gmail_token = None + self.mail_server_type = None + self.mail_server = None + self.config_log_level = None + self.config_allowed_column_value = None + self.config_denied_column_value = None + self.config_allowed_tags = None + self.config_denied_tags = None + self.config_default_show = None + self.config_default_role = None + self.config_keyfile = None + self.config_certfile = None + self.config_rarfile_location = None + self.config_kepubifypath = None + self.config_binariesdir = None''' self.__dict__["dirty"] = list() def init_config(self, session, secret_key, cli): @@ -191,16 +214,16 @@ class ConfigSQL(object): change = False - if self.config_binariesdir == None: # pylint: disable=access-member-before-definition + if self.config_binariesdir is None: change = True self.config_binariesdir = autodetect_calibre_binaries() self.config_converterpath = autodetect_converter_binary(self.config_binariesdir) - if self.config_kepubifypath == None: # pylint: disable=access-member-before-definition + if self.config_kepubifypath is None: change = True self.config_kepubifypath = autodetect_kepubify_binary() - if self.config_rarfile_location == None: # pylint: disable=access-member-before-definition + if self.config_rarfile_location is None: change = True self.config_rarfile_location = autodetect_unrar_binary() if change: @@ -429,8 +452,7 @@ def _encrypt_fields(session, secret_key): {_Settings.mail_password_e: crypter.encrypt(settings.mail_password.encode())}) if settings.config_ldap_serv_password: session.query(_Settings).update( - {_Settings.config_ldap_serv_password_e: - crypter.encrypt(settings.config_ldap_serv_password.encode())}) + {_Settings.config_ldap_serv_password_e: crypter.encrypt(settings.config_ldap_serv_password.encode())}) session.commit() @@ -546,7 +568,7 @@ def load_configuration(session, secret_key): def get_flask_session_key(_session): flask_settings = _session.query(_Flask_Settings).one_or_none() - if flask_settings == None: + if flask_settings is None: flask_settings = _Flask_Settings(os.urandom(32)) _session.add(flask_settings) _session.commit() @@ -557,6 +579,7 @@ def get_encryption_key(key_path): key_file = os.path.join(key_path, ".key") generate = True error = "" + key = None if os.path.exists(key_file) and os.path.getsize(key_file) > 32: with open(key_file, "rb") as f: key = f.read() diff --git a/cps/constants.py b/cps/constants.py index ef207e02..e5836433 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -159,12 +159,13 @@ EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'kepub', 'mobi', 'azw', 'azw3', 'cbr' _extension = "" if sys.platform == "win32": _extension = ".exe" -SUPPORTED_CALIBRE_BINARIES = {binary:binary + _extension for binary in ["ebook-convert", "calibredb"]} +SUPPORTED_CALIBRE_BINARIES = {binary: binary + _extension for binary in ["ebook-convert", "calibredb"]} def has_flag(value, bit_flag): return bit_flag == (bit_flag & (value or 0)) + def selected_roles(dictionary): return sum(v for k, v in ALL_ROLES.items() if k in dictionary) diff --git a/cps/db.py b/cps/db.py index ed1ccde0..1770457b 100644 --- a/cps/db.py +++ b/cps/db.py @@ -104,6 +104,7 @@ class Identifiers(Base): book = Column(Integer, ForeignKey('books.id'), nullable=False) def __init__(self, val, id_type, book): + super().__init__() self.val = val self.type = id_type self.book = book @@ -178,7 +179,7 @@ class Identifiers(Base): elif self.val.lower().startswith("javascript:"): return quote(self.val) elif self.val.lower().startswith("data:"): - link , __, __ = str.partition(self.val, ",") + link, __, __ = str.partition(self.val, ",") return link else: return "{0}".format(self.val) @@ -192,6 +193,7 @@ class Comments(Base): text = Column(String(collation='NOCASE'), nullable=False) def __init__(self, comment, book): + super().__init__() self.text = comment self.book = book @@ -209,6 +211,7 @@ class Tags(Base): name = Column(String(collation='NOCASE'), unique=True, nullable=False) def __init__(self, name): + super().__init__() self.name = name def get(self): @@ -230,6 +233,7 @@ class Authors(Base): link = Column(String, nullable=False, default="") def __init__(self, name, sort, link=""): + super().__init__() self.name = name self.sort = sort self.link = link @@ -252,6 +256,7 @@ class Series(Base): sort = Column(String(collation='NOCASE')) def __init__(self, name, sort): + super().__init__() self.name = name self.sort = sort @@ -272,6 +277,7 @@ class Ratings(Base): rating = Column(Integer, CheckConstraint('rating>-1 AND rating<11'), unique=True) def __init__(self, rating): + super().__init__() self.rating = rating def get(self): @@ -291,6 +297,7 @@ class Languages(Base): lang_code = Column(String(collation='NOCASE'), nullable=False, unique=True) def __init__(self, lang_code): + super().__init__() self.lang_code = lang_code def get(self): @@ -314,6 +321,7 @@ class Publishers(Base): sort = Column(String(collation='NOCASE')) def __init__(self, name, sort): + super().__init__() self.name = name self.sort = sort @@ -338,6 +346,7 @@ class Data(Base): name = Column(String, nullable=False) def __init__(self, book, book_format, uncompressed_size, name): + super().__init__() self.book = book self.format = book_format self.uncompressed_size = uncompressed_size @@ -357,6 +366,7 @@ class Metadata_Dirtied(Base): book = Column(Integer, ForeignKey('books.id'), nullable=False, unique=True) def __init__(self, book): + super().__init__() self.book = book @@ -391,6 +401,7 @@ class Books(Base): def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover, authors, tags, languages=None): + super().__init__() self.title = title self.sort = sort self.author_sort = author_sort @@ -399,12 +410,12 @@ class Books(Base): self.series_index = series_index self.last_modified = last_modified self.path = path - self.has_cover = (has_cover != None) + self.has_cover = (has_cover is not None) def __repr__(self): return "".format(self.title, self.sort, self.author_sort, - self.timestamp, self.pubdate, self.series_index, - self.last_modified, self.path, self.has_cover) + self.timestamp, self.pubdate, self.series_index, + self.last_modified, self.path, self.has_cover) @property def atom_timestamp(self): @@ -448,11 +459,13 @@ class CustomColumns(Base): content['is_editable'] = self.editable content['rec_index'] = sequence + 22 # toDo why ?? if isinstance(value, datetime): - content['#value#'] = {"__class__": "datetime.datetime", "__value__": value.strftime("%Y-%m-%dT%H:%M:%S+00:00")} + content['#value#'] = {"__class__": "datetime.datetime", + "__value__": value.strftime("%Y-%m-%dT%H:%M:%S+00:00")} else: content['#value#'] = value content['#extra#'] = extra - content['is_multiple2'] = {} if not self.is_multiple else {"cache_to_list": "|", "ui_to_list": ",", "list_to_ui": ", "} + content['is_multiple2'] = {} if not self.is_multiple else {"cache_to_list": "|", "ui_to_list": ",", + "list_to_ui": ", "} return json.dumps(content, ensure_ascii=False) @@ -512,7 +525,6 @@ class CalibreDB: if init: self.init_db(expire_on_commit) - def init_db(self, expire_on_commit=True): if self._init: self.init_session(expire_on_commit) @@ -722,8 +734,8 @@ class CalibreDB: def common_filters(self, allow_show_archived=False, return_all_languages=False): if not allow_show_archived: archived_books = (ub.session.query(ub.ArchivedBook) - .filter(ub.ArchivedBook.user_id == int(current_user.id)) - .filter(ub.ArchivedBook.is_archived == True) + .filter(ub.ArchivedBook.user_id==int(current_user.id)) + .filter(ub.ArchivedBook.is_archived==True) .all()) archived_book_ids = [archived_book.book_id for archived_book in archived_books] archived_filter = Books.id.notin_(archived_book_ids) @@ -959,7 +971,7 @@ class CalibreDB: pagination = None result = self.search_query(term, config, *join).order_by(*order).all() result_count = len(result) - if offset != None and limit != None: + if offset is not None and limit is not None: offset = int(offset) limit_all = offset + int(limit) pagination = Pagination((offset / (int(limit)) + 1), limit, result_count) @@ -989,7 +1001,7 @@ class CalibreDB: if not return_all_languages: no_lang_count = (self.session.query(Books) .outerjoin(books_languages_link).outerjoin(Languages) - .filter(Languages.lang_code == None) + .filter(Languages.lang_code==None) .filter(self.common_filters()) .count()) if no_lang_count: @@ -1087,9 +1099,3 @@ class Category: self.id = cat_id self.rating = rating self.count = 1 - -'''class Count: - count = None - - def __init__(self, count): - self.count = count''' diff --git a/cps/debug_info.py b/cps/debug_info.py index 82ca8ca6..879846ab 100644 --- a/cps/debug_info.py +++ b/cps/debug_info.py @@ -33,6 +33,7 @@ from .about import collect_stats log = logger.create() + class lazyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, LazyString): @@ -40,6 +41,7 @@ class lazyEncoder(json.JSONEncoder): # Let the base class default method raise the TypeError return json.JSONEncoder.default(self, obj) + def assemble_logfiles(file_name): log_list = sorted(glob.glob(file_name + '*'), reverse=True) wfd = BytesIO() diff --git a/cps/dep_check.py b/cps/dep_check.py index 34d0e24b..b1917f7a 100644 --- a/cps/dep_check.py +++ b/cps/dep_check.py @@ -58,6 +58,8 @@ def load_dependencies(optional=False): def dependency_check(optional=False): d = list() + dep_version_int = None + low_check = None deps = load_dependencies(optional) for dep in deps: try: diff --git a/cps/editbooks.py b/cps/editbooks.py index 23d812a5..802c3b09 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -990,17 +990,6 @@ def edit_book_comments(comments, book): modify_date = False if comments: comments = clean_string(comments, book.id) - #try: - # if BLEACH: - # comments = clean_html(comments, tags=set(), attributes=set()) - # else: - # comments = clean_html(comments) - #except ParserError as e: - # log.error("Comments of book {} are corrupted: {}".format(book.id, e)) - # comments = "" - #except TypeError as e: - # log.error("Comments can't be parsed, maybe 'lxml' is too new, try installing 'bleach': {}".format(e)) - # comments = "" if len(book.comments): if book.comments[0].text != comments: book.comments[0].text = comments @@ -1059,18 +1048,6 @@ def edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string): to_save[cc_string] = Markup(to_save[cc_string]).unescape() if to_save[cc_string]: to_save[cc_string] = clean_string(to_save[cc_string], book_id) - #try: - # if BLEACH: - # to_save[cc_string] = clean_html(to_save[cc_string], tags=set(), attributes=set()) - # else: - # to_save[cc_string] = clean_html(to_save[cc_string]) - #except ParserError as e: - # log.error("Customs Comments of book {} are corrupted: {}".format(book_id, e)) - # to_save[cc_string] = "" - #except TypeError as e: - # to_save[cc_string] = "" - # log.error("Customs Comments can't be parsed, maybe 'lxml' is too new, " - # "try installing 'bleach': {}".format(e)) elif c.datatype == 'datetime': try: to_save[cc_string] = datetime.strptime(to_save[cc_string], "%Y-%m-%d") @@ -1297,8 +1274,6 @@ def search_objects_remove(db_book_object, db_type, input_elements): del_elements = [] for c_elements in db_book_object: found = False - #if db_type == 'languages': - # type_elements = c_elements.lang_code if db_type == 'custom': type_elements = c_elements.value else: diff --git a/cps/epub.py b/cps/epub.py index 4a621f10..c802f61d 100644 --- a/cps/epub.py +++ b/cps/epub.py @@ -45,6 +45,7 @@ def _extract_cover(zip_file, cover_file, cover_path, tmp_file_name): cf = zip_file.read(zip_cover_path) return cover.cover_processing(tmp_file_name, cf, extension) + def get_epub_layout(book, book_data): file_path = os.path.normpath(os.path.join(config.get_book_path(), book.path, book_data.name + "." + book_data.format.lower())) diff --git a/cps/epub_helper.py b/cps/epub_helper.py index d221dff3..4517f2c6 100644 --- a/cps/epub_helper.py +++ b/cps/epub_helper.py @@ -43,7 +43,7 @@ def updateEpub(src, dest, filename, data, ): # create a temp copy of the archive without filename with zipfile.ZipFile(src, 'r') as zin: with zipfile.ZipFile(dest, 'w') as zout: - zout.comment = zin.comment # preserve the comment + zout.comment = zin.comment # preserve the comment for item in zin.infolist(): if item.filename != filename: zout.writestr(item, zin.read(item.filename)) @@ -53,7 +53,9 @@ def updateEpub(src, dest, filename, data, ): zf.writestr(filename, data) -def get_content_opf(file_path, ns=default_ns): +def get_content_opf(file_path, ns=None): + if ns is None: + ns = default_ns epubZip = zipfile.ZipFile(file_path) txt = epubZip.read('META-INF/container.xml') tree = etree.fromstring(txt) @@ -154,13 +156,14 @@ def create_new_metadata_backup(book, custom_columns, export_language, translate return package + def replace_metadata(tree, package): rep_element = tree.xpath('/pkg:package/pkg:metadata', namespaces=default_ns)[0] new_element = package.xpath('//metadata', namespaces=default_ns)[0] tree.replace(rep_element, new_element) return etree.tostring(tree, - xml_declaration=True, - encoding='utf-8', - pretty_print=True).decode('utf-8') + xml_declaration=True, + encoding='utf-8', + pretty_print=True).decode('utf-8') diff --git a/cps/error_handler.py b/cps/error_handler.py index 7c003bdb..92d3e876 100644 --- a/cps/error_handler.py +++ b/cps/error_handler.py @@ -31,6 +31,7 @@ from . import config, app, logger, services log = logger.create() # custom error page + def error_http(error): return render_template('http_error.html', error_code="Error {0}".format(error.code), @@ -52,6 +53,7 @@ def internal_error(error): instance=config.config_calibre_web_title ), 500 + def init_errorhandler(): # http error handling for ex in default_exceptions: @@ -60,7 +62,6 @@ def init_errorhandler(): elif ex == 500: app.register_error_handler(ex, internal_error) - if services.ldap: # Only way of catching the LDAPException upon logging in with LDAP server down @app.errorhandler(services.ldap.LDAPException) diff --git a/cps/file_helper.py b/cps/file_helper.py index 7c3e5291..c714f5c2 100644 --- a/cps/file_helper.py +++ b/cps/file_helper.py @@ -20,6 +20,7 @@ from tempfile import gettempdir import os import shutil + def get_temp_dir(): tmp_dir = os.path.join(gettempdir(), 'calibre_web') if not os.path.isdir(tmp_dir): diff --git a/cps/gdrive.py b/cps/gdrive.py index 4d110f83..284fb21e 100644 --- a/cps/gdrive.py +++ b/cps/gdrive.py @@ -45,7 +45,7 @@ except ImportError as err: current_milli_time = lambda: int(round(time() * 1000)) -gdrive_watch_callback_token = 'target=calibreweb-watch_files' #nosec +gdrive_watch_callback_token = 'target=calibreweb-watch_files' # nosec @gdrive.route("/authenticate") @@ -86,11 +86,12 @@ def watch_gdrive(): notification_id = str(uuid4()) try: result = gdriveutils.watchChange(gdriveutils.Gdrive.Instance().drive, notification_id, - 'web_hook', address, gdrive_watch_callback_token, current_milli_time() + 604800*1000) + 'web_hook', address, gdrive_watch_callback_token, current_milli_time() + 604800*1000) + config.config_google_drive_watch_changes_response = result config.save() except HttpError as e: - reason=json.loads(e.content)['error']['errors'][0] + reason = json.loads(e.content)['error']['errors'][0] if reason['reason'] == 'push.webhookUrlUnauthorized': flash(_('Callback domain is not verified, ' 'please follow steps to verify domain in google developer console'), category="error") @@ -115,6 +116,7 @@ def revoke_watch_gdrive(): config.save() return redirect(url_for('admin.db_configuration')) + try: @csrf.exempt @gdrive.route("/watch/callback", methods=['GET', 'POST']) @@ -138,7 +140,7 @@ try: if response: 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): # nosec + and response['file']['md5Checksum'] != hashlib.md5(dbpath): # nosec tmp_dir = get_temp_dir() log.info('Database file updated') diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py index b1d30596..93cfe1e1 100644 --- a/cps/gdriveutils.py +++ b/cps/gdriveutils.py @@ -207,6 +207,7 @@ def getDrive(drive=None, gauth=None): log.error("Google Drive error: {}".format(e)) return drive + def listRootFolders(): try: drive = getDrive(Gdrive.Instance().drive) @@ -224,7 +225,7 @@ def getEbooksFolder(drive): def getFolderInFolder(parentId, folderName, drive): # drive = getDrive(drive) - query="" + query = "" if folderName: query = "title = '%s' and " % folderName.replace("'", r"\'") folder = query + "'%s' in parents and mimeType = 'application/vnd.google-apps.folder'" \ @@ -235,6 +236,7 @@ def getFolderInFolder(parentId, folderName, drive): else: return fileList[0] + # Search for id of root folder in gdrive database, if not found request from gdrive and store in internal database def getEbooksFolderId(drive=None): storedPathName = session.query(GdriveId).filter(GdriveId.path == '/').first() @@ -369,20 +371,20 @@ def moveGdriveFolderRemote(origin_file, target_folder): def copyToDrive(drive, uploadFile, createRoot, replaceFiles, - ignoreFiles=None, - parent=None, prevDir=''): + ignoreFiles=None, + parent=None, prevDir=''): ignoreFiles = ignoreFiles or [] drive = getDrive(drive) isInitial = not bool(parent) if not parent: parent = getEbooksFolder(drive) - if os.path.isdir(os.path.join(prevDir,uploadFile)): + if os.path.isdir(os.path.join(prevDir, uploadFile)): existingFolder = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" % (os.path.basename(uploadFile).replace("'", r"\'"), parent['id'])}).GetList() if len(existingFolder) == 0 and (not isInitial or createRoot): parent = drive.CreateFile({'title': os.path.basename(uploadFile), 'parents': [{"kind": "drive#fileLink", 'id': parent['id']}], - "mimeType": "application/vnd.google-apps.folder"}) + "mimeType": "application/vnd.google-apps.folder"}) parent.Upload() else: if (not isInitial or createRoot) and len(existingFolder) > 0: @@ -398,7 +400,7 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles, driveFile = existingFiles[0] else: driveFile = drive.CreateFile({'title': os.path.basename(uploadFile).replace("'", r"\'"), - 'parents': [{"kind":"drive#fileLink", 'id': parent['id']}], }) + 'parents': [{"kind": "drive#fileLink", 'id': parent['id']}], }) driveFile.SetContentFile(os.path.join(prevDir, uploadFile)) driveFile.Upload() @@ -410,7 +412,7 @@ def uploadFileToEbooksFolder(destFile, f, string=False): for i, x in enumerate(splitDir): if i == len(splitDir)-1: existing_Files = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" % - (x.replace("'", r"\'"), parent['id'])}).GetList() + (x.replace("'", r"\'"), parent['id'])}).GetList() if len(existing_Files) > 0: driveFile = existing_Files[0] else: @@ -423,17 +425,17 @@ def uploadFileToEbooksFolder(destFile, f, string=False): driveFile.Upload() else: existing_Folder = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" % - (x.replace("'", r"\'"), parent['id'])}).GetList() + (x.replace("'", r"\'"), parent['id'])}).GetList() if len(existing_Folder) == 0: parent = drive.CreateFile({'title': x, 'parents': [{"kind": "drive#fileLink", 'id': parent['id']}], - "mimeType": "application/vnd.google-apps.folder"}) + "mimeType": "application/vnd.google-apps.folder"}) parent.Upload() else: parent = existing_Folder[0] def watchChange(drive, channel_id, channel_type, channel_address, - channel_token=None, expiration=None): + channel_token=None, expiration=None): # Watch for all changes to a user's Drive. # Args: # service: Drive API service instance. @@ -504,7 +506,7 @@ def stopChannel(drive, channel_id, resource_id): return drive.auth.service.channels().stop(body=body).execute() -def getChangeById (drive, change_id): +def getChangeById(drive, change_id): # Print a single Change resource information. # # Args: @@ -538,8 +540,9 @@ def updateGdriveCalibreFromLocal(): if os.path.isdir(os.path.join(config.config_calibre_dir, x)): shutil.rmtree(os.path.join(config.config_calibre_dir, x)) + # update gdrive.db on edit of books title -def updateDatabaseOnEdit(ID,newPath): +def updateDatabaseOnEdit(ID, newPath): sqlCheckPath = newPath if newPath[-1] == '/' else newPath + '/' storedPathName = session.query(GdriveId).filter(GdriveId.gdrive_id == ID).first() if storedPathName: @@ -585,6 +588,7 @@ def get_cover_via_gdrive(cover_path): else: return None + # Gets cover file from gdrive def get_metadata_backup_via_gdrive(metadata_path): df = getFileFromEbooksFolder(metadata_path, 'metadata.opf') @@ -608,6 +612,7 @@ def get_metadata_backup_via_gdrive(metadata_path): else: return None + # Creates chunks for downloading big files def partial(total_byte_len, part_size_limit): s = [] @@ -616,6 +621,7 @@ def partial(total_byte_len, part_size_limit): s.append([p, last]) return s + # downloads files in chunks from gdrive def do_gdrive_download(df, headers, convert_encoding=False): total_size = int(df.metadata.get('fileSize')) @@ -655,6 +661,7 @@ oauth_scope: - https://www.googleapis.com/auth/drive """ + def update_settings(client_id, client_secret, redirect_uri): if redirect_uri.endswith('/'): redirect_uri = redirect_uri[:-1] diff --git a/cps/gevent_wsgi.py b/cps/gevent_wsgi.py index b044f31b..cd9614c9 100644 --- a/cps/gevent_wsgi.py +++ b/cps/gevent_wsgi.py @@ -19,6 +19,7 @@ from gevent.pywsgi import WSGIHandler + class MyWSGIHandler(WSGIHandler): def get_environ(self): env = super().get_environ() diff --git a/cps/helper.py b/cps/helper.py index 52161ca0..df8fece7 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -227,7 +227,7 @@ def send_mail(book_id, book_format, convert, ereader_mail, calibrepath, user_id) email_text = N_("%(book)s send to eReader", book=link) WorkerThread.add(user_id, TaskEmail(_("Send to eReader"), book.path, converted_file_name, config.get_mail_settings(), ereader_mail, - email_text, _('This Email has been sent via Calibre-Web.'),book.id)) + email_text, _('This Email has been sent via Calibre-Web.'), book.id)) return return _("The requested file could not be read. Maybe wrong permissions?") @@ -441,9 +441,9 @@ def rename_all_authors(first_author, renamed_author, calibre_path="", localbook= gd.moveGdriveFolderRemote(g_file, new_author_rename_dir) else: if os.path.isdir(os.path.join(calibre_path, old_author_dir)): + old_author_path = os.path.join(calibre_path, old_author_dir) + new_author_path = os.path.join(calibre_path, new_author_rename_dir) try: - old_author_path = os.path.join(calibre_path, old_author_dir) - new_author_path = os.path.join(calibre_path, new_author_rename_dir) shutil.move(os.path.normcase(old_author_path), os.path.normcase(new_author_path)) except OSError as ex: log.error("Rename author from: %s to %s: %s", old_author_path, new_author_path, ex) @@ -505,7 +505,6 @@ def upload_new_file_gdrive(book_id, first_author, renamed_author, title, title_d return rename_files_on_change(first_author, renamed_author, local_book=book, gdrive=True) - def update_dir_structure_gdrive(book_id, first_author, renamed_author): book = calibre_db.get_book(book_id) @@ -623,6 +622,7 @@ def reset_password(user_id): ub.session.rollback() return 0, None + def generate_random_password(min_length): min_length = max(8, min_length) - 4 random_source = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?" @@ -690,6 +690,7 @@ def valid_email(email): raise Exception(_("Invalid Email address format")) return email + def valid_password(check_password): if config.config_password_policy: verify = "" @@ -731,7 +732,7 @@ def update_dir_structure(book_id, def delete_book(book, calibrepath, book_format): if not book_format: - clear_cover_thumbnail_cache(book.id) ## here it breaks + clear_cover_thumbnail_cache(book.id) # here it breaks calibre_db.delete_dirty_metadata(book.id) if config.config_use_google_drive: return delete_book_gdrive(book, book_format) @@ -943,13 +944,14 @@ def save_cover(img, book_path): def do_download_file(book, book_format, client, data, headers): book_name = data.name + download_name = filename = None if config.config_use_google_drive: # startTime = time.time() df = gd.getFileFromEbooksFolder(book.path, book_name + "." + book_format) # log.debug('%s', time.time() - startTime) if df: if config.config_embed_metadata and ( - (book_format == "kepub" and config.config_kepubifypath ) or + (book_format == "kepub" and config.config_kepubifypath) or (book_format != "kepub" and config.config_binariesdir)): output_path = os.path.join(config.config_calibre_dir, book.path) if not os.path.exists(output_path): @@ -977,7 +979,7 @@ def do_download_file(book, book_format, client, data, headers): filename, download_name = do_kepubify_metadata_replace(book, os.path.join(filename, book_name + "." + book_format)) elif book_format != "kepub" and config.config_binariesdir and config.config_embed_metadata: - filename, download_name = do_calibre_export(book.id, book_format) + filename, download_name = do_calibre_export(book.id, book_format) else: download_name = book_name @@ -1052,11 +1054,11 @@ def check_calibre(calibre_location): return _('Calibre binaries not viable') else: ret_val = [] - missing_binaries=[path for path, available in - zip(SUPPORTED_CALIBRE_BINARIES.values(), binaries_available) if not available] + missing_binaries = [path for path, available in + zip(SUPPORTED_CALIBRE_BINARIES.values(), binaries_available) if not available] - missing_perms=[path for path, available in - zip(SUPPORTED_CALIBRE_BINARIES.values(), binaries_executable) if not available] + missing_perms = [path for path, available in + zip(SUPPORTED_CALIBRE_BINARIES.values(), binaries_executable) if not available] if missing_binaries: ret_val.append(_('Missing calibre binaries: %(missing)s', missing=", ".join(missing_binaries))) if missing_perms: diff --git a/cps/isoLanguages.py b/cps/isoLanguages.py index a66235ce..57473658 100644 --- a/cps/isoLanguages.py +++ b/cps/isoLanguages.py @@ -82,7 +82,6 @@ def get_language_codes(locale, language_names, remainder=None): return lang - def get_valid_language_codes(locale, language_names, remainder=None): lang = list() if "" in language_names: diff --git a/cps/iso_language_names.py b/cps/iso_language_names.py index 4b9a8ef9..930d2b5d 100644 --- a/cps/iso_language_names.py +++ b/cps/iso_language_names.py @@ -10973,4 +10973,4 @@ LANGUAGE_NAMES = { "zxx": "No linguistic content", "zza": "Zaza" } -} \ No newline at end of file +} diff --git a/cps/kobo.py b/cps/kobo.py index 00e40b49..3c747519 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -48,7 +48,7 @@ import requests from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub, csrf, kobo_sync_status from . import isoLanguages from .epub import get_epub_layout -from .constants import COVER_THUMBNAIL_SMALL #, sqlalchemy_version2 +from .constants import COVER_THUMBNAIL_SMALL from .helper import get_download_link from .services import SyncToken as SyncToken from .web import download_required @@ -145,7 +145,7 @@ def HandleSyncRequest(): sync_token = SyncToken.SyncToken.from_headers(request.headers) log.info("Kobo library sync request received") log.debug("SyncToken: {}".format(sync_token)) - log.debug("Download link format {}".format(get_download_url_for_book('[bookid]','[bookformat]'))) + log.debug("Download link format {}".format(get_download_url_for_book('[bookid]', '[bookformat]'))) if not current_app.wsgi_app.is_proxied: log.debug('Kobo: Received unproxied request, changed request port to external server port') @@ -212,7 +212,7 @@ def HandleSyncRequest(): kobo_reading_state = get_or_create_reading_state(book.Books.id) entitlement = { - "BookEntitlement": create_book_entitlement(book.Books, archived=(book.is_archived == True)), + "BookEntitlement": create_book_entitlement(book.Books, archived=(book.is_archived==True)), "BookMetadata": get_metadata(book.Books), } @@ -921,8 +921,8 @@ def HandleCoverImageRequest(book_uuid, width, height, Quality, isGreyscale): log.debug("Redirecting request for cover image of unknown book %s to Kobo" % book_uuid) return redirect(KOBO_IMAGEHOST_URL + "/{book_uuid}/{width}/{height}/false/image.jpg".format(book_uuid=book_uuid, - width=width, - height=height), 307) + width=width, + height=height), 307) @kobo.route("") @@ -951,7 +951,8 @@ def HandleBookDeletionRequest(book_uuid): @csrf.exempt @kobo.route("/v1/library/", methods=["DELETE", "GET"]) def HandleUnimplementedRequest(dummy=None): - log.debug("Unimplemented Library Request received: %s (request is forwarded to kobo if configured)", request.base_url) + log.debug("Unimplemented Library Request received: %s (request is forwarded to kobo if configured)", + request.base_url) return redirect_or_proxy_request() @@ -1004,7 +1005,8 @@ def handle_getests(): @kobo.route("/v1/affiliate", methods=["GET", "POST"]) @kobo.route("/v1/deals", methods=["GET", "POST"]) def HandleProductsRequest(dummy=None): - log.debug("Unimplemented Products Request received: %s (request is forwarded to kobo if configured)", request.base_url) + log.debug("Unimplemented Products Request received: %s (request is forwarded to kobo if configured)", + request.base_url) return redirect_or_proxy_request() @@ -1021,7 +1023,7 @@ def make_calibre_web_auth_response(): "RefreshToken": RefreshToken, "TokenType": "Bearer", "TrackingId": str(uuid.uuid4()), - "UserKey": content.get('UserKey',""), + "UserKey": content.get('UserKey', ""), } ) ) diff --git a/cps/kobo_auth.py b/cps/kobo_auth.py index 49b7e475..f49b3fb0 100644 --- a/cps/kobo_auth.py +++ b/cps/kobo_auth.py @@ -115,7 +115,7 @@ def generate_auth_token(user_id): "generate_kobo_auth_url.html", title=_("Kobo Setup"), auth_token=auth_token.auth_token, - warning = warning + warning=warning ) diff --git a/cps/kobo_sync_status.py b/cps/kobo_sync_status.py index 7a201861..bff4a705 100644 --- a/cps/kobo_sync_status.py +++ b/cps/kobo_sync_status.py @@ -23,6 +23,7 @@ import datetime from sqlalchemy.sql.expression import or_, and_, true from sqlalchemy import exc + # Add the current book id to kobo_synced_books table for current user, if entry is already present, # do nothing (safety precaution) def add_synced_books(book_id): @@ -50,7 +51,6 @@ def remove_synced_book(book_id, all=False, session=None): ub.session_commit(_session=session) - def change_archived_books(book_id, state=None, message=None): archived_book = ub.session.query(ub.ArchivedBook).filter(and_(ub.ArchivedBook.user_id == int(current_user.id), ub.ArchivedBook.book_id == book_id)).first() @@ -71,7 +71,7 @@ def update_on_sync_shelfs(user_id): books_to_archive = (ub.session.query(ub.KoboSyncedBooks) .join(ub.BookShelf, ub.KoboSyncedBooks.book_id == ub.BookShelf.book_id, isouter=True) .join(ub.Shelf, ub.Shelf.user_id == user_id, isouter=True) - .filter(or_(ub.Shelf.kobo_sync == 0, ub.Shelf.kobo_sync == None)) + .filter(or_(ub.Shelf.kobo_sync == 0, ub.Shelf.kobo_sync==None)) .filter(ub.KoboSyncedBooks.user_id == user_id).all()) for b in books_to_archive: change_archived_books(b.book_id, True) diff --git a/cps/main.py b/cps/main.py index 286b2b27..b868f4e5 100644 --- a/cps/main.py +++ b/cps/main.py @@ -27,6 +27,7 @@ from flask import request def request_username(): return request.authorization.username + def main(): app = create_app() @@ -48,12 +49,14 @@ def main(): kobo_available = get_kobo_activated() except (ImportError, AttributeError): # Catch also error for not installed flask-WTF (missing csrf decorator) kobo_available = False + kobo, kobo_auth,get_remote_address = None try: from .oauth_bb import oauth oauth_available = True except ImportError: oauth_available = False + oauth = None from . import web_server init_errorhandler() @@ -62,7 +65,7 @@ def main(): app.register_blueprint(tasks) app.register_blueprint(web) app.register_blueprint(opds) - limiter.limit("3/minute",key_func=request_username)(opds) + limiter.limit("3/minute", key_func=request_username)(opds) app.register_blueprint(jinjia) app.register_blueprint(about) app.register_blueprint(shelf) diff --git a/cps/metadata_provider/amazon.py b/cps/metadata_provider/amazon.py index 30291a3f..843a9d76 100644 --- a/cps/metadata_provider/amazon.py +++ b/cps/metadata_provider/amazon.py @@ -25,7 +25,7 @@ try: import cchardet #optional for better speed except ImportError: pass -from cps import logger + from cps.services.Metadata import MetaRecord, MetaSourceInfo, Metadata import cps.logger as logger @@ -33,8 +33,6 @@ import cps.logger as logger from operator import itemgetter log = logger.create() -log = logger.create() - class Amazon(Metadata): __name__ = "Amazon" diff --git a/cps/metadata_provider/douban.py b/cps/metadata_provider/douban.py index 39c71cc7..1dd12d8a 100644 --- a/cps/metadata_provider/douban.py +++ b/cps/metadata_provider/douban.py @@ -217,7 +217,8 @@ class Douban(Metadata): return match - def _clean_date(self, date: str) -> str: + @staticmethod + def _clean_date(date: str) -> str: """ Clean up the date string to be in the format YYYY-MM-DD diff --git a/cps/metadata_provider/lubimyczytac.py b/cps/metadata_provider/lubimyczytac.py index 1dde42e7..0f3081ec 100644 --- a/cps/metadata_provider/lubimyczytac.py +++ b/cps/metadata_provider/lubimyczytac.py @@ -103,7 +103,7 @@ class LubimyCzytac(Metadata): PUBLISH_DATE = "//dt[contains(@title,'Data pierwszego wydania" FIRST_PUBLISH_DATE = f"{DETAILS}{PUBLISH_DATE} oryginalnego')]{SIBLINGS}[1]/text()" FIRST_PUBLISH_DATE_PL = f"{DETAILS}{PUBLISH_DATE} polskiego')]{SIBLINGS}[1]/text()" - TAGS = "//a[contains(@href,'/ksiazki/t/')]/text()" # "//nav[@aria-label='breadcrumbs']//a[contains(@href,'/ksiazki/k/')]/span/text()" + TAGS = "//a[contains(@href,'/ksiazki/t/')]/text()" # "//nav[@aria-label='breadcrumbs']//a[contains(@href,'/ksiazki/k/')]/span/text()" RATING = "//meta[@property='books:rating:value']/@content" diff --git a/cps/oauth_bb.py b/cps/oauth_bb.py index ec400f71..76b8d2ba 100644 --- a/cps/oauth_bb.py +++ b/cps/oauth_bb.py @@ -135,7 +135,7 @@ def bind_oauth_or_register(provider_id, provider_user_id, redirect_url, provider if oauth_entry.user: login_user(oauth_entry.user) log.debug("You are now logged in as: '%s'", oauth_entry.user.name) - flash(_("Success! You are now logged in as: %(nickname)s", nickname= oauth_entry.user.name), + flash(_("Success! You are now logged in as: %(nickname)s", nickname=oauth_entry.user.name), category="success") return redirect(url_for('web.index')) else: @@ -205,6 +205,7 @@ def unlink_oauth(provider): flash(_("Not Linked to %(oauth)s", oauth=provider), category="error") return redirect(url_for('web.profile')) + def generate_oauth_blueprints(): if not ub.session.query(ub.OAuthProvider).count(): for provider in ("github", "google"): @@ -291,6 +292,7 @@ if ub.oauth_support: return oauth_update_token(str(oauthblueprints[1]['id']), token, google_user_id) + # notify on OAuth provider error @oauth_error.connect_via(oauthblueprints[0]['blueprint']) def github_error(blueprint, error, error_description=None, error_uri=None): diff --git a/cps/opds.py b/cps/opds.py index 2226895c..b61de5a9 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -394,6 +394,7 @@ def feed_shelf(book_id): and_(ub.Shelf.is_public == 1, ub.Shelf.id == book_id))).first() result = list() + pagination = list() # user is allowed to access shelf if shelf: result, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), diff --git a/cps/server.py b/cps/server.py index c0f83403..10d1722a 100644 --- a/cps/server.py +++ b/cps/server.py @@ -97,7 +97,8 @@ class WebServer(object): log.warning('Cert path: %s', certfile_path) log.warning('Key path: %s', keyfile_path) - def _make_gevent_socket_activated(self): + @staticmethod + def _make_gevent_socket_activated(): # Reuse an already open socket on fd=SD_LISTEN_FDS_START SD_LISTEN_FDS_START = 3 return GeventSocket(fileno=SD_LISTEN_FDS_START) @@ -139,8 +140,8 @@ class WebServer(object): return ((self.listen_address, self.listen_port), _readable_listen_address(self.listen_address, self.listen_port)) - try: address = ('::', self.listen_port) + try: sock = WSGIServer.get_listener(address, family=socket.AF_INET6) except socket.error as ex: log.error('%s', ex) @@ -301,7 +302,6 @@ class WebServer(object): log.info("Performing restart of Calibre-Web") args = self._get_args_for_reloading() os.execv(args[0].lstrip('"').rstrip('"'), args) - return True @staticmethod def shutdown_scheduler(): diff --git a/cps/services/SyncToken.py b/cps/services/SyncToken.py index 49f27ef4..871d2b28 100644 --- a/cps/services/SyncToken.py +++ b/cps/services/SyncToken.py @@ -173,8 +173,8 @@ class SyncToken: def __str__(self): return "{},{},{},{},{},{}".format(self.books_last_created, - self.books_last_modified, - self.archive_last_modified, - self.reading_state_last_modified, - self.tags_last_modified, - self.raw_kobo_store_token) + self.books_last_modified, + self.archive_last_modified, + self.reading_state_last_modified, + self.tags_last_modified, + self.raw_kobo_store_token) diff --git a/cps/services/gmail.py b/cps/services/gmail.py index 3a4eab7f..5b0cdbe5 100644 --- a/cps/services/gmail.py +++ b/cps/services/gmail.py @@ -36,6 +36,7 @@ SCOPES = ['openid', 'https://www.googleapis.com/auth/gmail.send', 'https://www.g def setup_gmail(token): # If there are no (valid) credentials available, let the user log in. creds = None + user_info = None if "token" in token: creds = Credentials( token=token['token'], diff --git a/cps/services/goodreads_support.py b/cps/services/goodreads_support.py index e3baafac..b3b7bdef 100644 --- a/cps/services/goodreads_support.py +++ b/cps/services/goodreads_support.py @@ -32,6 +32,7 @@ except ImportError: from .. import logger from ..clean_html import clean_string + class my_GoodreadsClient(GoodreadsClient): def request(self, *args, **kwargs): @@ -39,6 +40,7 @@ class my_GoodreadsClient(GoodreadsClient): req = my_GoodreadsRequest(self, *args, **kwargs) return req.request() + class GoodreadsRequestException(Exception): def __init__(self, error_msg, url): self.error_msg = error_msg @@ -52,8 +54,8 @@ class my_GoodreadsRequest(GoodreadsRequest): def request(self): resp = requests.get(self.host+self.path, params=self.params, - headers={"User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:125.0) " - "Gecko/20100101 Firefox/125.0"}) + headers={"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:125.0) " + "Gecko/20100101 Firefox/125.0"}) if resp.status_code != 200: raise GoodreadsRequestException(resp.reason, self.path) if self.req_format == 'xml': @@ -125,7 +127,8 @@ def get_other_books(author_info, library_books=None): identifiers = [] library_titles = [] if library_books: - identifiers = list(reduce(lambda acc, book: acc + [i.val for i in book.identifiers if i.val], library_books, [])) + identifiers = list( + reduce(lambda acc, book: acc + [i.val for i in book.identifiers if i.val], library_books, [])) library_titles = [book.title for book in library_books] for book in author_info.books: diff --git a/cps/services/simpleldap.py b/cps/services/simpleldap.py index dc915ceb..3ae048b5 100644 --- a/cps/services/simpleldap.py +++ b/cps/services/simpleldap.py @@ -30,9 +30,11 @@ except ImportError: log = logger.create() + class LDAPLogger(object): - def write(self, message): + @staticmethod + def write(message): try: log.debug(message.strip("\n").replace("\n", "")) except Exception: @@ -71,6 +73,7 @@ class mySimpleLDap(LDAP): _ldap = mySimpleLDap() + def init_app(app, config): if config.config_login_type != constants.LOGIN_LDAP: return @@ -124,7 +127,7 @@ def init_app(app, config): log.error(e) -def get_object_details(user=None,query_filter=None): +def get_object_details(user=None, query_filter=None): return _ldap.get_object_details(user, query_filter=query_filter) diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index 3a121a2e..868839ab 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -44,9 +44,11 @@ log = logger.create() current_milli_time = lambda: int(round(time() * 1000)) + class TaskConvert(CalibreTask): def __init__(self, file_path, book_id, task_message, settings, ereader_mail, user=None): super(TaskConvert, self).__init__(task_message) + self.worker_thread = None self.file_path = file_path self.book_id = book_id self.title = "" @@ -67,12 +69,13 @@ class TaskConvert(CalibreTask): data.name + "." + self.settings['old_book_format'].lower()) df_cover = gdriveutils.getFileFromEbooksFolder(cur_book.path, "cover.jpg") if df: + datafile_cover = None datafile = os.path.join(config.get_book_path(), cur_book.path, data.name + "." + self.settings['old_book_format'].lower()) if df_cover: datafile_cover = os.path.join(config.get_book_path(), - cur_book.path, "cover.jpg") + cur_book.path, "cover.jpg") if not os.path.exists(os.path.join(config.get_book_path(), cur_book.path)): os.makedirs(os.path.join(config.get_book_path(), cur_book.path)) df.GetContentFile(datafile) @@ -85,7 +88,7 @@ class TaskConvert(CalibreTask): format=self.settings['old_book_format'], fn=data.name + "." + self.settings['old_book_format'].lower()) worker_db.session.close() - return self._handleError(self, error_message) + return self._handleError(error_message) filename = self._convert_ebook_format() if config.config_use_google_drive: @@ -242,10 +245,11 @@ class TaskConvert(CalibreTask): os.unlink(converted_file[0]) else: return 1, N_("Converted file not found or more than one file in folder %(folder)s", - folder=os.path.dirname(file_path)) + folder=os.path.dirname(file_path)) return check, None def _convert_calibre(self, file_path, format_old_ext, format_new_ext, has_cover): + path_tmp_opf = None try: # path_tmp_opf = self._embed_metadata() if config.config_embed_metadata: diff --git a/cps/tasks/database.py b/cps/tasks/database.py index afc4db2c..e5aa26da 100644 --- a/cps/tasks/database.py +++ b/cps/tasks/database.py @@ -31,7 +31,6 @@ class TaskReconnectDatabase(CalibreTask): self.listen_address = config.get_config_ipaddress() self.listen_port = config.config_port - def run(self, worker_thread): address = self.listen_address if self.listen_address else 'localhost' port = self.listen_port if self.listen_port else 8083 diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 39ad919f..4f85eefa 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -59,7 +59,7 @@ class EmailBase: if hasattr(self, 'sock') and self.sock: try: if self.transferSize: - lock=threading.Lock() + lock = threading.Lock() lock.acquire() self.transferSize = len(strg) lock.release() diff --git a/cps/tasks/metadata_backup.py b/cps/tasks/metadata_backup.py index 6451d9a3..5e0bb96a 100644 --- a/cps/tasks/metadata_backup.py +++ b/cps/tasks/metadata_backup.py @@ -25,6 +25,7 @@ from flask_babel import lazy_gettext as N_ from ..epub_helper import create_new_metadata_backup + class TaskBackupMetadata(CalibreTask): def __init__(self, export_language="en", diff --git a/cps/tasks/thumbnail.py b/cps/tasks/thumbnail.py index dd9ee1e0..54b68f40 100644 --- a/cps/tasks/thumbnail.py +++ b/cps/tasks/thumbnail.py @@ -110,7 +110,8 @@ class TaskGenerateCoverThumbnails(CalibreTask): self._handleSuccess() self.app_db_session.remove() - def get_books_with_covers(self, book_id=-1): + @staticmethod + def get_books_with_covers(book_id=-1): filter_exp = (db.Books.id == book_id) if book_id != -1 else True calibre_db = db.CalibreDB(expire_on_commit=False, init=True) books_cover = calibre_db.session.query(db.Books).filter(db.Books.has_cover == 1).filter(filter_exp).all() @@ -464,7 +465,7 @@ class TaskClearCoverThumbnailCache(CalibreTask): calibre_db = db.CalibreDB(expire_on_commit=False, init=True) thumbnails = (calibre_db.session.query(ub.Thumbnail) .join(db.Books, ub.Thumbnail.entity_id == db.Books.id, isouter=True) - .filter(db.Books.id == None) + .filter(db.Books.id==None) .all()) calibre_db.session.close() elif self.book_id > 0: # make sure single book is selected diff --git a/cps/tasks/upload.py b/cps/tasks/upload.py index bc8ba1e0..a74fc2f4 100644 --- a/cps/tasks/upload.py +++ b/cps/tasks/upload.py @@ -22,6 +22,7 @@ from flask_babel import lazy_gettext as N_ from cps.services.worker import CalibreTask, STAT_FINISH_SUCCESS + class TaskUpload(CalibreTask): def __init__(self, task_message, book_title): super(TaskUpload, self).__init__(task_message) diff --git a/cps/templates/readpdf.html b/cps/templates/readpdf.html index 027871cd..c747d02e 100644 --- a/cps/templates/readpdf.html +++ b/cps/templates/readpdf.html @@ -198,6 +198,15 @@ See https://github.com/adobe-type-tools/cmap-resources @@ -1945,13 +1945,13 @@ AssertionError: 'Test 执 to' != 'book' - + TestLoadMetadata 1 0 - 0 1 0 + 0 Detail @@ -1959,26 +1959,26 @@ AssertionError: 'Test 执 to' != 'book' - +
TestLoadMetadata - test_load_metadata
- ERROR + FAIL
-