mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-23 16:40:31 +00:00
Merge branch 'Develop'
Extract metadata of audiofiles during upload Updated pdf.js
This commit is contained in:
commit
7feff3b55c
@ -72,6 +72,9 @@ mimetypes.add_type('application/mpeg', '.mpeg')
|
|||||||
mimetypes.add_type('audio/mpeg', '.mp3')
|
mimetypes.add_type('audio/mpeg', '.mp3')
|
||||||
mimetypes.add_type('audio/x-m4a', '.m4a')
|
mimetypes.add_type('audio/x-m4a', '.m4a')
|
||||||
mimetypes.add_type('audio/x-m4a', '.m4b')
|
mimetypes.add_type('audio/x-m4a', '.m4b')
|
||||||
|
mimetypes.add_type('audio/x-hx-aac-adts', '.aac')
|
||||||
|
mimetypes.add_type('audio/vnd.dolby.dd-raw', '.ac3')
|
||||||
|
mimetypes.add_type('video/x-ms-asf', '.asf')
|
||||||
mimetypes.add_type('audio/ogg', '.ogg')
|
mimetypes.add_type('audio/ogg', '.ogg')
|
||||||
mimetypes.add_type('application/ogg', '.oga')
|
mimetypes.add_type('application/ogg', '.oga')
|
||||||
mimetypes.add_type('text/css', '.css')
|
mimetypes.add_type('text/css', '.css')
|
||||||
@ -84,7 +87,7 @@ app = Flask(__name__)
|
|||||||
app.config.update(
|
app.config.update(
|
||||||
SESSION_COOKIE_HTTPONLY=True,
|
SESSION_COOKIE_HTTPONLY=True,
|
||||||
SESSION_COOKIE_SAMESITE='Strict',
|
SESSION_COOKIE_SAMESITE='Strict',
|
||||||
REMEMBER_COOKIE_SAMESITE='Strict', # will be available in flask-login 0.5.1 earliest
|
REMEMBER_COOKIE_SAMESITE='Strict',
|
||||||
WTF_CSRF_SSL_STRICT=False,
|
WTF_CSRF_SSL_STRICT=False,
|
||||||
SESSION_COOKIE_NAME=os.environ.get('COOKIE_PREFIX', "") + "session",
|
SESSION_COOKIE_NAME=os.environ.get('COOKIE_PREFIX', "") + "session",
|
||||||
REMEMBER_COOKIE_NAME=os.environ.get('COOKIE_PREFIX', "") + "remember_token"
|
REMEMBER_COOKIE_NAME=os.environ.get('COOKIE_PREFIX', "") + "remember_token"
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import importlib
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
@ -41,8 +42,11 @@ req = dep_check.load_dependencies(False)
|
|||||||
opt = dep_check.load_dependencies(True)
|
opt = dep_check.load_dependencies(True)
|
||||||
for i in (req + opt):
|
for i in (req + opt):
|
||||||
modules[i[1]] = i[0]
|
modules[i[1]] = i[0]
|
||||||
modules['Jinja2'] = jinja2.__version__
|
modules['Jinja2'] = importlib.metadata.version("jinja2")
|
||||||
modules['pySqlite'] = sqlite3.version
|
try:
|
||||||
|
modules['pySqlite'] = sqlite3.version
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
modules['SQLite'] = sqlite3.sqlite_version
|
modules['SQLite'] = sqlite3.sqlite_version
|
||||||
sorted_modules = OrderedDict((sorted(modules.items(), key=lambda x: x[0].casefold())))
|
sorted_modules = OrderedDict((sorted(modules.items(), key=lambda x: x[0].casefold())))
|
||||||
|
|
||||||
|
123
cps/audio.py
Normal file
123
cps/audio.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
# Copyright (C) 2024 Ozzieisaacs
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import mutagen
|
||||||
|
import base64
|
||||||
|
from . import cover
|
||||||
|
|
||||||
|
from cps.constants import BookMeta
|
||||||
|
|
||||||
|
|
||||||
|
def get_audio_file_info(tmp_file_path, original_file_extension, original_file_name):
|
||||||
|
tmp_cover_name = None
|
||||||
|
audio_file = mutagen.File(tmp_file_path)
|
||||||
|
comments = None
|
||||||
|
if original_file_extension in [".mp3", ".wav", ".aiff"]:
|
||||||
|
cover_data = list()
|
||||||
|
for key, val in audio_file.tags.items():
|
||||||
|
if key.startswith("APIC:"):
|
||||||
|
cover_data.append(val)
|
||||||
|
if key.startswith("COMM:"):
|
||||||
|
comments = val.text[0]
|
||||||
|
title = audio_file.tags.get('TIT2').text[0] if "TIT2" in audio_file.tags else None
|
||||||
|
author = audio_file.tags.get('TPE1').text[0] if "TPE1" in audio_file.tags else None
|
||||||
|
if author is None:
|
||||||
|
author = audio_file.tags.get('TPE2').text[0] if "TPE2" in audio_file.tags else None
|
||||||
|
tags = audio_file.tags.get('TCON').text[0] if "TCON" in audio_file.tags else None # Genre
|
||||||
|
series = audio_file.tags.get('TALB').text[0] if "TALB" in audio_file.tags else None# Album
|
||||||
|
series_id = audio_file.tags.get('TRCK').text[0] if "TRCK" in audio_file.tags else None # track no.
|
||||||
|
publisher = audio_file.tags.get('TPUB').text[0] if "TPUB" in audio_file.tags else None
|
||||||
|
pubdate = str(audio_file.tags.get('TDRL').text[0]) if "TDRL" in audio_file.tags else None
|
||||||
|
if not pubdate:
|
||||||
|
pubdate = str(audio_file.tags.get('TDRC').text[0]) if "TDRC" in audio_file.tags else None
|
||||||
|
if not pubdate:
|
||||||
|
pubdate = str(audio_file.tags.get('TDOR').text[0]) if "TDOR" in audio_file.tags else None
|
||||||
|
if cover_data:
|
||||||
|
tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
|
||||||
|
cover_info = cover_data[0]
|
||||||
|
for dat in cover_data:
|
||||||
|
if dat.type == mutagen.id3.PictureType.COVER_FRONT:
|
||||||
|
cover_info = dat
|
||||||
|
break
|
||||||
|
cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:])
|
||||||
|
elif original_file_extension in [".ogg", ".flac"]:
|
||||||
|
title = audio_file.tags.get('TITLE')[0] if "TITLE" in audio_file else None
|
||||||
|
author = audio_file.tags.get('ARTIST')[0] if "ARTIST" in audio_file else None
|
||||||
|
comments = None # audio_file.tags.get('COMM', None)
|
||||||
|
tags = ""
|
||||||
|
series = audio_file.tags.get('ALBUM')[0] if "ALBUM" in audio_file else None
|
||||||
|
series_id = audio_file.tags.get('TRACKNUMBER')[0] if "TRACKNUMBER" in audio_file else None
|
||||||
|
publisher = audio_file.tags.get('LABEL')[0] if "LABEL" in audio_file else None
|
||||||
|
pubdate = audio_file.tags.get('DATE')[0] if "DATE" in audio_file else None
|
||||||
|
cover_data = audio_file.tags.get('METADATA_BLOCK_PICTURE')
|
||||||
|
if cover_data:
|
||||||
|
tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
|
||||||
|
with open(tmp_cover_name, "wb") as cover_file:
|
||||||
|
cover_file.write(mutagen.flac.Picture(base64.b64decode(cover_data[0])).data)
|
||||||
|
if hasattr(audio_file, "pictures"):
|
||||||
|
tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
|
||||||
|
with open(tmp_cover_name, "wb") as cover_file:
|
||||||
|
cover_file.write(audio_file.pictures[0].data)
|
||||||
|
elif original_file_extension in [".aac"]:
|
||||||
|
title = audio_file.tags.get('Title').value if "title" in audio_file else None
|
||||||
|
author = audio_file.tags.get('Artist').value if "artist" in audio_file else None
|
||||||
|
comments = None # audio_file.tags.get('COMM', None)
|
||||||
|
tags = ""
|
||||||
|
series = audio_file.tags.get('Album').value if "Album" in audio_file else None
|
||||||
|
series_id = audio_file.tags.get('Track').value if "Track" in audio_file else None
|
||||||
|
publisher = audio_file.tags.get('Label').value if "Label" in audio_file else None
|
||||||
|
pubdate = audio_file.tags.get('Year').value if "Year" in audio_file else None
|
||||||
|
cover_data = audio_file.tags['Cover Art (Front)']
|
||||||
|
if cover_data:
|
||||||
|
tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
|
||||||
|
with open(tmp_cover_name, "wb") as cover_file:
|
||||||
|
cover_file.write(cover_data.value.split(b"\x00",1)[1])
|
||||||
|
elif original_file_extension in [".asf"]:
|
||||||
|
title = audio_file.tags.get('Title')[0].value if "title" in audio_file else None
|
||||||
|
author = audio_file.tags.get('Artist')[0].value if "artist" in audio_file else None
|
||||||
|
comments = None # audio_file.tags.get('COMM', None)
|
||||||
|
tags = ""
|
||||||
|
series = audio_file.tags.get('Album')[0].value if "Album" in audio_file else None
|
||||||
|
series_id = audio_file.tags.get('Track')[0].value if "Track" in audio_file else None
|
||||||
|
publisher = audio_file.tags.get('Label')[0].value if "Label" in audio_file else None
|
||||||
|
pubdate = audio_file.tags.get('Year')[0].value if "Year" in audio_file else None
|
||||||
|
cover_data = audio_file.tags['WM/Picture']
|
||||||
|
if cover_data:
|
||||||
|
tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
|
||||||
|
with open(tmp_cover_name, "wb") as cover_file:
|
||||||
|
cover_file.write(cover_data[0].value)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return BookMeta(
|
||||||
|
file_path=tmp_file_path,
|
||||||
|
extension=original_file_extension,
|
||||||
|
title=title or original_file_name ,
|
||||||
|
author="Unknown" if author is None else author,
|
||||||
|
cover=tmp_cover_name,
|
||||||
|
description="" if comments is None else comments,
|
||||||
|
tags="" if tags is None else tags,
|
||||||
|
series="" if series is None else series,
|
||||||
|
series_id="1" if series_id is None else series_id.split("/")[0],
|
||||||
|
languages="",
|
||||||
|
publisher= "" if publisher is None else publisher,
|
||||||
|
pubdate="" if pubdate is None else pubdate,
|
||||||
|
identifiers=[],
|
||||||
|
)
|
@ -1,4 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from datetime import timezone
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
@ -496,7 +497,7 @@ class LoginManager:
|
|||||||
duration = timedelta(seconds=duration)
|
duration = timedelta(seconds=duration)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
expires = datetime.utcnow() + duration
|
expires = datetime.now(timezone.utc) + duration
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"REMEMBER_COOKIE_DURATION must be a datetime.timedelta,"
|
"REMEMBER_COOKIE_DURATION must be a datetime.timedelta,"
|
||||||
|
10
cps/db.py
10
cps/db.py
@ -20,7 +20,7 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
import unidecode
|
import unidecode
|
||||||
from weakref import WeakSet
|
from weakref import WeakSet
|
||||||
@ -378,10 +378,10 @@ class Books(Base):
|
|||||||
title = Column(String(collation='NOCASE'), nullable=False, default='Unknown')
|
title = Column(String(collation='NOCASE'), nullable=False, default='Unknown')
|
||||||
sort = Column(String(collation='NOCASE'))
|
sort = Column(String(collation='NOCASE'))
|
||||||
author_sort = Column(String(collation='NOCASE'))
|
author_sort = Column(String(collation='NOCASE'))
|
||||||
timestamp = Column(TIMESTAMP, default=datetime.utcnow)
|
timestamp = Column(TIMESTAMP, default=lambda: datetime.now(timezone.utc))
|
||||||
pubdate = Column(TIMESTAMP, default=DEFAULT_PUBDATE)
|
pubdate = Column(TIMESTAMP, default=DEFAULT_PUBDATE)
|
||||||
series_index = Column(String, nullable=False, default="1.0")
|
series_index = Column(String, nullable=False, default="1.0")
|
||||||
last_modified = Column(TIMESTAMP, default=datetime.utcnow)
|
last_modified = Column(TIMESTAMP, default=lambda: datetime.now(timezone.utc))
|
||||||
path = Column(String, default="", nullable=False)
|
path = Column(String, default="", nullable=False)
|
||||||
has_cover = Column(Integer, default=0)
|
has_cover = Column(Integer, default=0)
|
||||||
uuid = Column(String)
|
uuid = Column(String)
|
||||||
@ -1029,10 +1029,10 @@ class CalibreDB:
|
|||||||
return title.strip()
|
return title.strip()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# sqlalchemy <1.4.24
|
# sqlalchemy <1.4.24 and sqlalchemy 2.0
|
||||||
conn = conn or self.session.connection().connection.driver_connection
|
conn = conn or self.session.connection().connection.driver_connection
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# sqlalchemy >1.4.24 and sqlalchemy 2.0
|
# sqlalchemy >1.4.24
|
||||||
conn = conn or self.session.connection().connection.connection
|
conn = conn or self.session.connection().connection.connection
|
||||||
try:
|
try:
|
||||||
conn.create_function("title_sort", 1, _title_sort)
|
conn.create_function("title_sort", 1, _title_sort)
|
||||||
|
@ -26,7 +26,8 @@ from flask_babel.speaklater import LazyString
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import send_file, __version__
|
from flask import send_file
|
||||||
|
import importlib
|
||||||
|
|
||||||
from . import logger, config
|
from . import logger, config
|
||||||
from .about import collect_stats
|
from .about import collect_stats
|
||||||
@ -49,7 +50,8 @@ def assemble_logfiles(file_name):
|
|||||||
with open(f, 'rb') as fd:
|
with open(f, 'rb') as fd:
|
||||||
shutil.copyfileobj(fd, wfd)
|
shutil.copyfileobj(fd, wfd)
|
||||||
wfd.seek(0)
|
wfd.seek(0)
|
||||||
if int(__version__.split('.')[0]) < 2:
|
version = importlib.metadata.version("flask")
|
||||||
|
if int(version.split('.')[0]) < 2:
|
||||||
return send_file(wfd,
|
return send_file(wfd,
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
attachment_filename=os.path.basename(file_name))
|
attachment_filename=os.path.basename(file_name))
|
||||||
@ -72,7 +74,8 @@ def send_debug():
|
|||||||
for fp in file_list:
|
for fp in file_list:
|
||||||
zf.write(fp, os.path.basename(fp))
|
zf.write(fp, os.path.basename(fp))
|
||||||
memory_zip.seek(0)
|
memory_zip.seek(0)
|
||||||
if int(__version__.split('.')[0]) < 2:
|
version = importlib.metadata.version("flask")
|
||||||
|
if int(version.split('.')[0]) < 2:
|
||||||
return send_file(memory_zip,
|
return send_file(memory_zip,
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
attachment_filename="Calibre-Web-debug-pack.zip")
|
attachment_filename="Calibre-Web-debug-pack.zip")
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
import json
|
import json
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@ -200,7 +200,7 @@ def edit_book(book_id):
|
|||||||
book.pubdate = db.Books.DEFAULT_PUBDATE
|
book.pubdate = db.Books.DEFAULT_PUBDATE
|
||||||
|
|
||||||
if modify_date:
|
if modify_date:
|
||||||
book.last_modified = datetime.utcnow()
|
book.last_modified = datetime.now(timezone.utc)
|
||||||
kobo_sync_status.remove_synced_book(edited_books_id, all=True)
|
kobo_sync_status.remove_synced_book(edited_books_id, all=True)
|
||||||
calibre_db.set_metadata_dirty(book.id)
|
calibre_db.set_metadata_dirty(book.id)
|
||||||
|
|
||||||
@ -246,8 +246,12 @@ def upload():
|
|||||||
modify_date = False
|
modify_date = False
|
||||||
# create the function for sorting...
|
# create the function for sorting...
|
||||||
calibre_db.update_title_sort(config)
|
calibre_db.update_title_sort(config)
|
||||||
calibre_db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
|
try:
|
||||||
|
# sqlalchemy 2.0
|
||||||
|
uuid_func = calibre_db.session.connection().connection.driver_connection
|
||||||
|
except AttributeError:
|
||||||
|
uuid_func = calibre_db.session.connection().connection.connection
|
||||||
|
uuid_func.create_function('uuid4', 0,lambda: str(uuid4()))
|
||||||
meta, error = file_handling_on_upload(requested_file)
|
meta, error = file_handling_on_upload(requested_file)
|
||||||
if error:
|
if error:
|
||||||
return error
|
return error
|
||||||
@ -440,7 +444,7 @@ def edit_list_book(param):
|
|||||||
mimetype='application/json')
|
mimetype='application/json')
|
||||||
else:
|
else:
|
||||||
return _("Parameter not found"), 400
|
return _("Parameter not found"), 400
|
||||||
book.last_modified = datetime.utcnow()
|
book.last_modified = datetime.now(timezone.utc)
|
||||||
|
|
||||||
calibre_db.session.commit()
|
calibre_db.session.commit()
|
||||||
# revert change for sort if automatic fields link is deactivated
|
# revert change for sort if automatic fields link is deactivated
|
||||||
@ -556,7 +560,7 @@ def table_xchange_author_title():
|
|||||||
# toDo: Handle error
|
# toDo: Handle error
|
||||||
edit_error = helper.update_dir_structure(edited_books_id, config.get_book_path(), input_authors[0])
|
edit_error = helper.update_dir_structure(edited_books_id, config.get_book_path(), input_authors[0])
|
||||||
if modify_date:
|
if modify_date:
|
||||||
book.last_modified = datetime.utcnow()
|
book.last_modified = datetime.now(timezone.utc)
|
||||||
calibre_db.set_metadata_dirty(book.id)
|
calibre_db.set_metadata_dirty(book.id)
|
||||||
try:
|
try:
|
||||||
calibre_db.session.commit()
|
calibre_db.session.commit()
|
||||||
@ -707,8 +711,8 @@ def create_book_on_upload(modify_date, meta):
|
|||||||
pubdate = datetime(101, 1, 1)
|
pubdate = datetime(101, 1, 1)
|
||||||
|
|
||||||
# Calibre adds books with utc as timezone
|
# Calibre adds books with utc as timezone
|
||||||
db_book = db.Books(title, "", sort_authors, datetime.utcnow(), pubdate,
|
db_book = db.Books(title, "", sort_authors, datetime.now(timezone.utc), pubdate,
|
||||||
'1', datetime.utcnow(), path, meta.cover, db_author, [], "")
|
'1', datetime.now(timezone.utc), path, meta.cover, db_author, [], "")
|
||||||
|
|
||||||
modify_date |= modify_database_object(input_authors, db_book.authors, db.Authors, calibre_db.session,
|
modify_date |= modify_database_object(input_authors, db_book.authors, db.Authors, calibre_db.session,
|
||||||
'author')
|
'author')
|
||||||
|
@ -25,7 +25,7 @@ import re
|
|||||||
import regex
|
import regex
|
||||||
import shutil
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
import requests
|
import requests
|
||||||
import unidecode
|
import unidecode
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@ -788,24 +788,23 @@ def get_book_cover_internal(book, resolution=None):
|
|||||||
|
|
||||||
def get_book_cover_thumbnail(book, resolution):
|
def get_book_cover_thumbnail(book, resolution):
|
||||||
if book and book.has_cover:
|
if book and book.has_cover:
|
||||||
return ub.session \
|
return (ub.session
|
||||||
.query(ub.Thumbnail) \
|
.query(ub.Thumbnail)
|
||||||
.filter(ub.Thumbnail.type == THUMBNAIL_TYPE_COVER) \
|
.filter(ub.Thumbnail.type == THUMBNAIL_TYPE_COVER)
|
||||||
.filter(ub.Thumbnail.entity_id == book.id) \
|
.filter(ub.Thumbnail.entity_id == book.id)
|
||||||
.filter(ub.Thumbnail.resolution == resolution) \
|
.filter(ub.Thumbnail.resolution == resolution)
|
||||||
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow())) \
|
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.now(timezone.utc)))
|
||||||
.first()
|
.first())
|
||||||
|
|
||||||
|
|
||||||
def get_series_thumbnail_on_failure(series_id, resolution):
|
def get_series_thumbnail_on_failure(series_id, resolution):
|
||||||
book = calibre_db.session \
|
book = (calibre_db.session
|
||||||
.query(db.Books) \
|
.query(db.Books)
|
||||||
.join(db.books_series_link) \
|
.join(db.books_series_link)
|
||||||
.join(db.Series) \
|
.join(db.Series)
|
||||||
.filter(db.Series.id == series_id) \
|
.filter(db.Series.id == series_id)
|
||||||
.filter(db.Books.has_cover == 1) \
|
.filter(db.Books.has_cover == 1)
|
||||||
.first()
|
.first())
|
||||||
|
|
||||||
return get_book_cover_internal(book, resolution=resolution)
|
return get_book_cover_internal(book, resolution=resolution)
|
||||||
|
|
||||||
|
|
||||||
@ -827,13 +826,13 @@ def get_series_cover_internal(series_id, resolution=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_series_thumbnail(series_id, resolution):
|
def get_series_thumbnail(series_id, resolution):
|
||||||
return ub.session \
|
return (ub.session
|
||||||
.query(ub.Thumbnail) \
|
.query(ub.Thumbnail)
|
||||||
.filter(ub.Thumbnail.type == THUMBNAIL_TYPE_SERIES) \
|
.filter(ub.Thumbnail.type == THUMBNAIL_TYPE_SERIES)
|
||||||
.filter(ub.Thumbnail.entity_id == series_id) \
|
.filter(ub.Thumbnail.entity_id == series_id)
|
||||||
.filter(ub.Thumbnail.resolution == resolution) \
|
.filter(ub.Thumbnail.resolution == resolution)
|
||||||
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow())) \
|
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.now(timezone.utc)))
|
||||||
.first()
|
.first())
|
||||||
|
|
||||||
|
|
||||||
# saves book cover from url
|
# saves book cover from url
|
||||||
|
@ -26,7 +26,6 @@ from markupsafe import escape
|
|||||||
import datetime
|
import datetime
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import re
|
|
||||||
|
|
||||||
from flask import Blueprint, request, url_for
|
from flask import Blueprint, request, url_for
|
||||||
from flask_babel import format_date
|
from flask_babel import format_date
|
||||||
|
16
cps/kobo.py
16
cps/kobo.py
@ -18,7 +18,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
from datetime import datetime, timezone
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import zipfile
|
import zipfile
|
||||||
@ -131,7 +131,7 @@ def convert_to_kobo_timestamp_string(timestamp):
|
|||||||
return timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
|
return timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
log.debug("Timestamp not valid: {}".format(exc))
|
log.debug("Timestamp not valid: {}".format(exc))
|
||||||
return datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
|
||||||
@kobo.route("/v1/library/sync")
|
@kobo.route("/v1/library/sync")
|
||||||
@ -150,15 +150,15 @@ def HandleSyncRequest():
|
|||||||
|
|
||||||
# if no books synced don't respect sync_token
|
# if no books synced don't respect sync_token
|
||||||
if not ub.session.query(ub.KoboSyncedBooks).filter(ub.KoboSyncedBooks.user_id == current_user.id).count():
|
if not ub.session.query(ub.KoboSyncedBooks).filter(ub.KoboSyncedBooks.user_id == current_user.id).count():
|
||||||
sync_token.books_last_modified = datetime.datetime.min
|
sync_token.books_last_modified = datetime.min
|
||||||
sync_token.books_last_created = datetime.datetime.min
|
sync_token.books_last_created = datetime.min
|
||||||
sync_token.reading_state_last_modified = datetime.datetime.min
|
sync_token.reading_state_last_modified = datetime.min
|
||||||
|
|
||||||
new_books_last_modified = sync_token.books_last_modified # needed for sync selected shelfs only
|
new_books_last_modified = sync_token.books_last_modified # needed for sync selected shelfs only
|
||||||
new_books_last_created = sync_token.books_last_created # needed to distinguish between new and changed entitlement
|
new_books_last_created = sync_token.books_last_created # needed to distinguish between new and changed entitlement
|
||||||
new_reading_state_last_modified = sync_token.reading_state_last_modified
|
new_reading_state_last_modified = sync_token.reading_state_last_modified
|
||||||
|
|
||||||
new_archived_last_modified = datetime.datetime.min
|
new_archived_last_modified = datetime.min
|
||||||
sync_results = []
|
sync_results = []
|
||||||
|
|
||||||
# We reload the book database so that the user gets a fresh view of the library
|
# We reload the book database so that the user gets a fresh view of the library
|
||||||
@ -375,7 +375,7 @@ def create_book_entitlement(book, archived):
|
|||||||
book_uuid = str(book.uuid)
|
book_uuid = str(book.uuid)
|
||||||
return {
|
return {
|
||||||
"Accessibility": "Full",
|
"Accessibility": "Full",
|
||||||
"ActivePeriod": {"From": convert_to_kobo_timestamp_string(datetime.datetime.utcnow())},
|
"ActivePeriod": {"From": convert_to_kobo_timestamp_string(datetime.now(timezone.utc))},
|
||||||
"Created": convert_to_kobo_timestamp_string(book.timestamp),
|
"Created": convert_to_kobo_timestamp_string(book.timestamp),
|
||||||
"CrossRevisionId": book_uuid,
|
"CrossRevisionId": book_uuid,
|
||||||
"Id": book_uuid,
|
"Id": book_uuid,
|
||||||
@ -795,7 +795,7 @@ def HandleStateRequest(book_uuid):
|
|||||||
if new_book_read_status == ub.ReadBook.STATUS_IN_PROGRESS \
|
if new_book_read_status == ub.ReadBook.STATUS_IN_PROGRESS \
|
||||||
and new_book_read_status != book_read.read_status:
|
and new_book_read_status != book_read.read_status:
|
||||||
book_read.times_started_reading += 1
|
book_read.times_started_reading += 1
|
||||||
book_read.last_time_started_reading = datetime.datetime.utcnow()
|
book_read.last_time_started_reading = datetime.now(timezone.utc)
|
||||||
book_read.read_status = new_book_read_status
|
book_read.read_status = new_book_read_status
|
||||||
update_results_response["StatusInfoResult"] = {"Result": "Success"}
|
update_results_response["StatusInfoResult"] = {"Result": "Success"}
|
||||||
except (KeyError, TypeError, ValueError, StatementError):
|
except (KeyError, TypeError, ValueError, StatementError):
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
from .cw_login import current_user
|
from .cw_login import current_user
|
||||||
from . import ub
|
from . import ub
|
||||||
import datetime
|
from datetime import datetime, timezone
|
||||||
from sqlalchemy.sql.expression import or_, and_, true
|
from sqlalchemy.sql.expression import or_, and_, true
|
||||||
# from sqlalchemy import exc
|
# from sqlalchemy import exc
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ def change_archived_books(book_id, state=None, message=None):
|
|||||||
archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id)
|
archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id)
|
||||||
|
|
||||||
archived_book.is_archived = state if state else not archived_book.is_archived
|
archived_book.is_archived = state if state else not archived_book.is_archived
|
||||||
archived_book.last_modified = datetime.datetime.utcnow() # toDo. Check utc timestamp
|
archived_book.last_modified = datetime.now(timezone.utc) # toDo. Check utc timestamp
|
||||||
|
|
||||||
ub.session.merge(archived_book)
|
ub.session.merge(archived_book)
|
||||||
ub.session_commit(message)
|
ub.session_commit(message)
|
||||||
|
10
cps/shelf.py
10
cps/shelf.py
@ -21,7 +21,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from flask import Blueprint, flash, redirect, request, url_for, abort
|
from flask import Blueprint, flash, redirect, request, url_for, abort
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
@ -80,7 +80,7 @@ def add_to_shelf(shelf_id, book_id):
|
|||||||
return "%s is a invalid Book Id. Could not be added to Shelf" % book_id, 400
|
return "%s is a invalid Book Id. Could not be added to Shelf" % book_id, 400
|
||||||
|
|
||||||
shelf.books.append(ub.BookShelf(shelf=shelf.id, book_id=book_id, order=maxOrder + 1))
|
shelf.books.append(ub.BookShelf(shelf=shelf.id, book_id=book_id, order=maxOrder + 1))
|
||||||
shelf.last_modified = datetime.utcnow()
|
shelf.last_modified = datetime.now(timezone.utc)
|
||||||
try:
|
try:
|
||||||
ub.session.merge(shelf)
|
ub.session.merge(shelf)
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
@ -139,7 +139,7 @@ def search_to_shelf(shelf_id):
|
|||||||
for book in books_for_shelf:
|
for book in books_for_shelf:
|
||||||
maxOrder += 1
|
maxOrder += 1
|
||||||
shelf.books.append(ub.BookShelf(shelf=shelf.id, book_id=book, order=maxOrder))
|
shelf.books.append(ub.BookShelf(shelf=shelf.id, book_id=book, order=maxOrder))
|
||||||
shelf.last_modified = datetime.utcnow()
|
shelf.last_modified = datetime.now(timezone.utc)
|
||||||
try:
|
try:
|
||||||
ub.session.merge(shelf)
|
ub.session.merge(shelf)
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
@ -185,7 +185,7 @@ def remove_from_shelf(shelf_id, book_id):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
ub.session.delete(book_shelf)
|
ub.session.delete(book_shelf)
|
||||||
shelf.last_modified = datetime.utcnow()
|
shelf.last_modified = datetime.now(timezone.utc)
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
except (OperationalError, InvalidRequestError) as e:
|
except (OperationalError, InvalidRequestError) as e:
|
||||||
ub.session.rollback()
|
ub.session.rollback()
|
||||||
@ -271,7 +271,7 @@ def order_shelf(shelf_id):
|
|||||||
for book in books_in_shelf:
|
for book in books_in_shelf:
|
||||||
setattr(book, 'order', to_save[str(book.book_id)])
|
setattr(book, 'order', to_save[str(book.book_id)])
|
||||||
counter += 1
|
counter += 1
|
||||||
# if order different from before -> shelf.last_modified = datetime.utcnow()
|
# if order different from before -> shelf.last_modified = datetime.now(timezone.utc)
|
||||||
try:
|
try:
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
except (OperationalError, InvalidRequestError) as e:
|
except (OperationalError, InvalidRequestError) as e:
|
||||||
|
5318
cps/static/js/libs/pdf.mjs
vendored
5318
cps/static/js/libs/pdf.mjs
vendored
File diff suppressed because it is too large
Load Diff
5316
cps/static/js/libs/pdf.worker.mjs
vendored
5316
cps/static/js/libs/pdf.worker.mjs
vendored
File diff suppressed because it is too large
Load Diff
456
cps/static/js/libs/viewer.mjs
vendored
456
cps/static/js/libs/viewer.mjs
vendored
@ -2,7 +2,7 @@
|
|||||||
* @licstart The following is the entire license notice for the
|
* @licstart The following is the entire license notice for the
|
||||||
* JavaScript code in this page
|
* JavaScript code in this page
|
||||||
*
|
*
|
||||||
* Copyright 2023 Mozilla Foundation
|
* Copyright 2024 Mozilla Foundation
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -446,7 +446,7 @@ class ProgressBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDisableAutoFetch(delay = 5000) {
|
setDisableAutoFetch(delay = 5000) {
|
||||||
if (isNaN(this.#percent)) {
|
if (this.#percent === 100 || isNaN(this.#percent)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.#disableAutoFetchTimeout) {
|
if (this.#disableAutoFetchTimeout) {
|
||||||
@ -535,15 +535,20 @@ function toggleExpandedBtn(button, toggle, view = null) {
|
|||||||
|
|
||||||
;// CONCATENATED MODULE: ./web/app_options.js
|
;// CONCATENATED MODULE: ./web/app_options.js
|
||||||
{
|
{
|
||||||
var compatibilityParams = Object.create(null);
|
var compatParams = new Map();
|
||||||
const userAgent = navigator.userAgent || "";
|
const userAgent = navigator.userAgent || "";
|
||||||
const platform = navigator.platform || "";
|
const platform = navigator.platform || "";
|
||||||
const maxTouchPoints = navigator.maxTouchPoints || 1;
|
const maxTouchPoints = navigator.maxTouchPoints || 1;
|
||||||
const isAndroid = /Android/.test(userAgent);
|
const isAndroid = /Android/.test(userAgent);
|
||||||
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1;
|
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1;
|
||||||
(function checkCanvasSizeLimitation() {
|
(function () {
|
||||||
if (isIOS || isAndroid) {
|
if (isIOS || isAndroid) {
|
||||||
compatibilityParams.maxCanvasPixels = 5242880;
|
compatParams.set("maxCanvasPixels", 5242880);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
(function () {
|
||||||
|
if (isAndroid) {
|
||||||
|
compatParams.set("useSystemFonts", false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@ -552,9 +557,21 @@ const OptionKind = {
|
|||||||
VIEWER: 0x02,
|
VIEWER: 0x02,
|
||||||
API: 0x04,
|
API: 0x04,
|
||||||
WORKER: 0x08,
|
WORKER: 0x08,
|
||||||
|
EVENT_DISPATCH: 0x10,
|
||||||
PREFERENCE: 0x80
|
PREFERENCE: 0x80
|
||||||
};
|
};
|
||||||
|
const Type = {
|
||||||
|
BOOLEAN: 0x01,
|
||||||
|
NUMBER: 0x02,
|
||||||
|
OBJECT: 0x04,
|
||||||
|
STRING: 0x08,
|
||||||
|
UNDEFINED: 0x10
|
||||||
|
};
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
|
allowedGlobalEvents: {
|
||||||
|
value: null,
|
||||||
|
kind: OptionKind.BROWSER
|
||||||
|
},
|
||||||
canvasMaxAreaInBytes: {
|
canvasMaxAreaInBytes: {
|
||||||
value: -1,
|
value: -1,
|
||||||
kind: OptionKind.BROWSER + OptionKind.API
|
kind: OptionKind.BROWSER + OptionKind.API
|
||||||
@ -563,6 +580,16 @@ const defaultOptions = {
|
|||||||
value: false,
|
value: false,
|
||||||
kind: OptionKind.BROWSER
|
kind: OptionKind.BROWSER
|
||||||
},
|
},
|
||||||
|
localeProperties: {
|
||||||
|
value: {
|
||||||
|
lang: navigator.language || "en-US"
|
||||||
|
},
|
||||||
|
kind: OptionKind.BROWSER
|
||||||
|
},
|
||||||
|
nimbusDataStr: {
|
||||||
|
value: "",
|
||||||
|
kind: OptionKind.BROWSER
|
||||||
|
},
|
||||||
supportsCaretBrowsingMode: {
|
supportsCaretBrowsingMode: {
|
||||||
value: false,
|
value: false,
|
||||||
kind: OptionKind.BROWSER
|
kind: OptionKind.BROWSER
|
||||||
@ -587,6 +614,14 @@ const defaultOptions = {
|
|||||||
value: true,
|
value: true,
|
||||||
kind: OptionKind.BROWSER
|
kind: OptionKind.BROWSER
|
||||||
},
|
},
|
||||||
|
toolbarDensity: {
|
||||||
|
value: 0,
|
||||||
|
kind: OptionKind.BROWSER + OptionKind.EVENT_DISPATCH
|
||||||
|
},
|
||||||
|
altTextLearnMoreUrl: {
|
||||||
|
value: "",
|
||||||
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
|
},
|
||||||
annotationEditorMode: {
|
annotationEditorMode: {
|
||||||
value: 0,
|
value: 0,
|
||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
@ -619,6 +654,14 @@ const defaultOptions = {
|
|||||||
value: false,
|
value: false,
|
||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
},
|
},
|
||||||
|
enableAltText: {
|
||||||
|
value: false,
|
||||||
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
|
},
|
||||||
|
enableGuessAltText: {
|
||||||
|
value: true,
|
||||||
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
|
},
|
||||||
enableHighlightEditor: {
|
enableHighlightEditor: {
|
||||||
value: false,
|
value: false,
|
||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
@ -627,10 +670,6 @@ const defaultOptions = {
|
|||||||
value: false,
|
value: false,
|
||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
},
|
},
|
||||||
enableML: {
|
|
||||||
value: false,
|
|
||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
|
||||||
},
|
|
||||||
enablePermissions: {
|
enablePermissions: {
|
||||||
value: false,
|
value: false,
|
||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
@ -643,8 +682,8 @@ const defaultOptions = {
|
|||||||
value: true,
|
value: true,
|
||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
},
|
},
|
||||||
enableStampEditor: {
|
enableUpdatedAddImage: {
|
||||||
value: true,
|
value: false,
|
||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
|
||||||
},
|
},
|
||||||
externalLinkRel: {
|
externalLinkRel: {
|
||||||
@ -775,6 +814,11 @@ const defaultOptions = {
|
|||||||
value: "../web/standard_fonts/",
|
value: "../web/standard_fonts/",
|
||||||
kind: OptionKind.API
|
kind: OptionKind.API
|
||||||
},
|
},
|
||||||
|
useSystemFonts: {
|
||||||
|
value: undefined,
|
||||||
|
kind: OptionKind.API,
|
||||||
|
type: Type.BOOLEAN + Type.UNDEFINED
|
||||||
|
},
|
||||||
verbosity: {
|
verbosity: {
|
||||||
value: 1,
|
value: 1,
|
||||||
kind: OptionKind.API
|
kind: OptionKind.API
|
||||||
@ -807,62 +851,80 @@ const defaultOptions = {
|
|||||||
value: false,
|
value: false,
|
||||||
kind: OptionKind.VIEWER
|
kind: OptionKind.VIEWER
|
||||||
};
|
};
|
||||||
defaultOptions.locale = {
|
|
||||||
value: navigator.language || "en-US",
|
|
||||||
kind: OptionKind.VIEWER
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const userOptions = Object.create(null);
|
const userOptions = new Map();
|
||||||
{
|
{
|
||||||
for (const name in compatibilityParams) {
|
for (const [name, value] of compatParams) {
|
||||||
userOptions[name] = compatibilityParams[name];
|
userOptions.set(name, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class AppOptions {
|
class AppOptions {
|
||||||
|
static eventBus;
|
||||||
constructor() {
|
constructor() {
|
||||||
throw new Error("Cannot initialize AppOptions.");
|
throw new Error("Cannot initialize AppOptions.");
|
||||||
}
|
}
|
||||||
static get(name) {
|
static get(name) {
|
||||||
return userOptions[name] ?? defaultOptions[name]?.value ?? undefined;
|
return userOptions.has(name) ? userOptions.get(name) : defaultOptions[name]?.value;
|
||||||
}
|
}
|
||||||
static getAll(kind = null, defaultOnly = false) {
|
static getAll(kind = null, defaultOnly = false) {
|
||||||
const options = Object.create(null);
|
const options = Object.create(null);
|
||||||
for (const name in defaultOptions) {
|
for (const name in defaultOptions) {
|
||||||
const defaultOption = defaultOptions[name];
|
const defaultOpt = defaultOptions[name];
|
||||||
if (kind && !(kind & defaultOption.kind)) {
|
if (kind && !(kind & defaultOpt.kind)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
options[name] = defaultOnly ? defaultOption.value : userOptions[name] ?? defaultOption.value;
|
options[name] = !defaultOnly && userOptions.has(name) ? userOptions.get(name) : defaultOpt.value;
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
static set(name, value) {
|
static set(name, value) {
|
||||||
userOptions[name] = value;
|
this.setAll({
|
||||||
|
[name]: value
|
||||||
|
});
|
||||||
}
|
}
|
||||||
static setAll(options, init = false) {
|
static setAll(options, prefs = false) {
|
||||||
if (init) {
|
let events;
|
||||||
if (this.get("disablePreferences")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const name in userOptions) {
|
|
||||||
if (compatibilityParams[name] !== undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
console.warn("setAll: The Preferences may override manually set AppOptions; " + 'please use the "disablePreferences"-option in order to prevent that.');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const name in options) {
|
for (const name in options) {
|
||||||
userOptions[name] = options[name];
|
const defaultOpt = defaultOptions[name],
|
||||||
|
userOpt = options[name];
|
||||||
|
if (!defaultOpt || !(typeof userOpt === typeof defaultOpt.value || Type[(typeof userOpt).toUpperCase()] & defaultOpt.type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
kind
|
||||||
|
} = defaultOpt;
|
||||||
|
if (prefs && !(kind & OptionKind.BROWSER || kind & OptionKind.PREFERENCE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (this.eventBus && kind & OptionKind.EVENT_DISPATCH) {
|
||||||
|
(events ||= new Map()).set(name, userOpt);
|
||||||
|
}
|
||||||
|
userOptions.set(name, userOpt);
|
||||||
|
}
|
||||||
|
if (events) {
|
||||||
|
for (const [name, value] of events) {
|
||||||
|
this.eventBus.dispatch(name.toLowerCase(), {
|
||||||
|
source: this,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static remove(name) {
|
}
|
||||||
delete userOptions[name];
|
{
|
||||||
const val = compatibilityParams[name];
|
AppOptions._checkDisablePreferences = () => {
|
||||||
if (val !== undefined) {
|
if (AppOptions.get("disablePreferences")) {
|
||||||
userOptions[name] = val;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
for (const [name] of userOptions) {
|
||||||
|
if (compatParams.has(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
console.warn("The Preferences may override manually set AppOptions; " + 'please use the "disablePreferences"-option to prevent that.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ./web/pdf_link_service.js
|
;// CONCATENATED MODULE: ./web/pdf_link_service.js
|
||||||
@ -1171,26 +1233,27 @@ class PDFLinkService {
|
|||||||
if (!(typeof zoom === "object" && typeof zoom?.name === "string")) {
|
if (!(typeof zoom === "object" && typeof zoom?.name === "string")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const argsLen = args.length;
|
||||||
let allowNull = true;
|
let allowNull = true;
|
||||||
switch (zoom.name) {
|
switch (zoom.name) {
|
||||||
case "XYZ":
|
case "XYZ":
|
||||||
if (args.length !== 3) {
|
if (argsLen < 2 || argsLen > 3) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Fit":
|
case "Fit":
|
||||||
case "FitB":
|
case "FitB":
|
||||||
return args.length === 0;
|
return argsLen === 0;
|
||||||
case "FitH":
|
case "FitH":
|
||||||
case "FitBH":
|
case "FitBH":
|
||||||
case "FitV":
|
case "FitV":
|
||||||
case "FitBV":
|
case "FitBV":
|
||||||
if (args.length !== 1) {
|
if (argsLen > 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "FitR":
|
case "FitR":
|
||||||
if (args.length !== 4) {
|
if (argsLen !== 4) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
allowNull = false;
|
allowNull = false;
|
||||||
@ -1240,7 +1303,6 @@ const {
|
|||||||
noContextMenu,
|
noContextMenu,
|
||||||
normalizeUnicode,
|
normalizeUnicode,
|
||||||
OPS,
|
OPS,
|
||||||
Outliner,
|
|
||||||
PasswordResponses,
|
PasswordResponses,
|
||||||
PDFDataRangeTransport,
|
PDFDataRangeTransport,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
@ -1248,12 +1310,10 @@ const {
|
|||||||
PermissionFlag,
|
PermissionFlag,
|
||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
renderTextLayer,
|
|
||||||
setLayerDimensions,
|
setLayerDimensions,
|
||||||
shadow,
|
shadow,
|
||||||
TextLayer,
|
TextLayer,
|
||||||
UnexpectedResponseException,
|
UnexpectedResponseException,
|
||||||
updateTextLayer,
|
|
||||||
Util,
|
Util,
|
||||||
VerbosityLevel,
|
VerbosityLevel,
|
||||||
version,
|
version,
|
||||||
@ -1401,40 +1461,28 @@ class BaseExternalServices {
|
|||||||
updateEditorStates(data) {
|
updateEditorStates(data) {
|
||||||
throw new Error("Not implemented: updateEditorStates");
|
throw new Error("Not implemented: updateEditorStates");
|
||||||
}
|
}
|
||||||
async getNimbusExperimentData() {}
|
|
||||||
async getGlobalEventNames() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
dispatchGlobalEvent(_event) {}
|
dispatchGlobalEvent(_event) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ./web/preferences.js
|
;// CONCATENATED MODULE: ./web/preferences.js
|
||||||
|
|
||||||
class BasePreferences {
|
class BasePreferences {
|
||||||
#browserDefaults = Object.freeze({
|
|
||||||
canvasMaxAreaInBytes: -1,
|
|
||||||
isInAutomation: false,
|
|
||||||
supportsCaretBrowsingMode: false,
|
|
||||||
supportsDocumentFonts: true,
|
|
||||||
supportsIntegratedFind: false,
|
|
||||||
supportsMouseWheelZoomCtrlKey: true,
|
|
||||||
supportsMouseWheelZoomMetaKey: true,
|
|
||||||
supportsPinchToZoom: true
|
|
||||||
});
|
|
||||||
#defaults = Object.freeze({
|
#defaults = Object.freeze({
|
||||||
|
altTextLearnMoreUrl: "",
|
||||||
annotationEditorMode: 0,
|
annotationEditorMode: 0,
|
||||||
annotationMode: 2,
|
annotationMode: 2,
|
||||||
cursorToolOnLoad: 0,
|
cursorToolOnLoad: 0,
|
||||||
defaultZoomDelay: 400,
|
defaultZoomDelay: 400,
|
||||||
defaultZoomValue: "",
|
defaultZoomValue: "",
|
||||||
disablePageLabels: false,
|
disablePageLabels: false,
|
||||||
|
enableAltText: false,
|
||||||
|
enableGuessAltText: true,
|
||||||
enableHighlightEditor: false,
|
enableHighlightEditor: false,
|
||||||
enableHighlightFloatingButton: false,
|
enableHighlightFloatingButton: false,
|
||||||
enableML: false,
|
|
||||||
enablePermissions: false,
|
enablePermissions: false,
|
||||||
enablePrintAutoRotate: true,
|
enablePrintAutoRotate: true,
|
||||||
enableScripting: true,
|
enableScripting: true,
|
||||||
enableStampEditor: true,
|
enableUpdatedAddImage: false,
|
||||||
externalLinkTarget: 0,
|
externalLinkTarget: 0,
|
||||||
highlightEditorColors: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
|
highlightEditorColors: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
|
||||||
historyUpdateUrl: false,
|
historyUpdateUrl: false,
|
||||||
@ -1456,7 +1504,6 @@ class BasePreferences {
|
|||||||
enableXfa: true,
|
enableXfa: true,
|
||||||
viewerCssTheme: 0
|
viewerCssTheme: 0
|
||||||
});
|
});
|
||||||
#prefs = Object.create(null);
|
|
||||||
#initializedPromise = null;
|
#initializedPromise = null;
|
||||||
constructor() {
|
constructor() {
|
||||||
if (this.constructor === BasePreferences) {
|
if (this.constructor === BasePreferences) {
|
||||||
@ -1466,16 +1513,13 @@ class BasePreferences {
|
|||||||
browserPrefs,
|
browserPrefs,
|
||||||
prefs
|
prefs
|
||||||
}) => {
|
}) => {
|
||||||
const options = Object.create(null);
|
if (AppOptions._checkDisablePreferences()) {
|
||||||
for (const [name, val] of Object.entries(this.#browserDefaults)) {
|
return;
|
||||||
const prefVal = browserPrefs?.[name];
|
|
||||||
options[name] = typeof prefVal === typeof val ? prefVal : val;
|
|
||||||
}
|
}
|
||||||
for (const [name, val] of Object.entries(this.#defaults)) {
|
AppOptions.setAll({
|
||||||
const prefVal = prefs?.[name];
|
...browserPrefs,
|
||||||
options[name] = this.#prefs[name] = typeof prefVal === typeof val ? prefVal : val;
|
...prefs
|
||||||
}
|
}, true);
|
||||||
AppOptions.setAll(options, true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async _writeToStorage(prefObj) {
|
async _writeToStorage(prefObj) {
|
||||||
@ -1484,58 +1528,21 @@ class BasePreferences {
|
|||||||
async _readFromStorage(prefObj) {
|
async _readFromStorage(prefObj) {
|
||||||
throw new Error("Not implemented: _readFromStorage");
|
throw new Error("Not implemented: _readFromStorage");
|
||||||
}
|
}
|
||||||
#updatePref({
|
|
||||||
name,
|
|
||||||
value
|
|
||||||
}) {
|
|
||||||
throw new Error("Not implemented: #updatePref");
|
|
||||||
}
|
|
||||||
async reset() {
|
async reset() {
|
||||||
await this.#initializedPromise;
|
await this.#initializedPromise;
|
||||||
const oldPrefs = structuredClone(this.#prefs);
|
AppOptions.setAll(this.#defaults, true);
|
||||||
this.#prefs = Object.create(null);
|
await this._writeToStorage(this.#defaults);
|
||||||
try {
|
|
||||||
await this._writeToStorage(this.#defaults);
|
|
||||||
} catch (reason) {
|
|
||||||
this.#prefs = oldPrefs;
|
|
||||||
throw reason;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
async set(name, value) {
|
async set(name, value) {
|
||||||
await this.#initializedPromise;
|
await this.#initializedPromise;
|
||||||
const defaultValue = this.#defaults[name],
|
AppOptions.setAll({
|
||||||
oldPrefs = structuredClone(this.#prefs);
|
[name]: value
|
||||||
if (defaultValue === undefined) {
|
}, true);
|
||||||
throw new Error(`Set preference: "${name}" is undefined.`);
|
await this._writeToStorage(AppOptions.getAll(OptionKind.PREFERENCE));
|
||||||
} else if (value === undefined) {
|
|
||||||
throw new Error("Set preference: no value is specified.");
|
|
||||||
}
|
|
||||||
const valueType = typeof value,
|
|
||||||
defaultType = typeof defaultValue;
|
|
||||||
if (valueType !== defaultType) {
|
|
||||||
if (valueType === "number" && defaultType === "string") {
|
|
||||||
value = value.toString();
|
|
||||||
} else {
|
|
||||||
throw new Error(`Set preference: "${value}" is a ${valueType}, expected a ${defaultType}.`);
|
|
||||||
}
|
|
||||||
} else if (valueType === "number" && !Number.isInteger(value)) {
|
|
||||||
throw new Error(`Set preference: "${value}" must be an integer.`);
|
|
||||||
}
|
|
||||||
this.#prefs[name] = value;
|
|
||||||
try {
|
|
||||||
await this._writeToStorage(this.#prefs);
|
|
||||||
} catch (reason) {
|
|
||||||
this.#prefs = oldPrefs;
|
|
||||||
throw reason;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
async get(name) {
|
async get(name) {
|
||||||
await this.#initializedPromise;
|
await this.#initializedPromise;
|
||||||
const defaultValue = this.#defaults[name];
|
return AppOptions.get(name);
|
||||||
if (defaultValue === undefined) {
|
|
||||||
throw new Error(`Get preference: "${name}" is undefined.`);
|
|
||||||
}
|
|
||||||
return this.#prefs[name] ?? defaultValue;
|
|
||||||
}
|
}
|
||||||
get initializedPromise() {
|
get initializedPromise() {
|
||||||
return this.#initializedPromise;
|
return this.#initializedPromise;
|
||||||
@ -3098,13 +3105,19 @@ class Preferences extends BasePreferences {
|
|||||||
}
|
}
|
||||||
class ExternalServices extends BaseExternalServices {
|
class ExternalServices extends BaseExternalServices {
|
||||||
async createL10n() {
|
async createL10n() {
|
||||||
return new genericl10n_GenericL10n(AppOptions.get("locale"));
|
return new genericl10n_GenericL10n(AppOptions.get("localeProperties")?.lang);
|
||||||
}
|
}
|
||||||
createScripting() {
|
createScripting() {
|
||||||
return new GenericScripting(AppOptions.get("sandboxBundleSrc"));
|
return new GenericScripting(AppOptions.get("sandboxBundleSrc"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class MLManager {
|
class MLManager {
|
||||||
|
async isEnabledFor(_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
async deleteModel(_service) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async guess() {
|
async guess() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -8411,6 +8424,9 @@ class AnnotationLayerBuilder {
|
|||||||
}
|
}
|
||||||
this.div.hidden = true;
|
this.div.hidden = true;
|
||||||
}
|
}
|
||||||
|
hasEditableAnnotations() {
|
||||||
|
return !!this.annotationLayer?.hasEditableAnnotations();
|
||||||
|
}
|
||||||
#updatePresentationModeState(state) {
|
#updatePresentationModeState(state) {
|
||||||
if (!this.div) {
|
if (!this.div) {
|
||||||
return;
|
return;
|
||||||
@ -9142,6 +9158,7 @@ class PDFPageView {
|
|||||||
#annotationMode = AnnotationMode.ENABLE_FORMS;
|
#annotationMode = AnnotationMode.ENABLE_FORMS;
|
||||||
#enableHWA = false;
|
#enableHWA = false;
|
||||||
#hasRestrictedScaling = false;
|
#hasRestrictedScaling = false;
|
||||||
|
#isEditing = false;
|
||||||
#layerProperties = null;
|
#layerProperties = null;
|
||||||
#loadingId = null;
|
#loadingId = null;
|
||||||
#previousRotation = null;
|
#previousRotation = null;
|
||||||
@ -9296,6 +9313,9 @@ class PDFPageView {
|
|||||||
this.reset();
|
this.reset();
|
||||||
this.pdfPage?.cleanup();
|
this.pdfPage?.cleanup();
|
||||||
}
|
}
|
||||||
|
hasEditableAnnotations() {
|
||||||
|
return !!this.annotationLayer?.hasEditableAnnotations();
|
||||||
|
}
|
||||||
get _textHighlighter() {
|
get _textHighlighter() {
|
||||||
return shadow(this, "_textHighlighter", new TextHighlighter({
|
return shadow(this, "_textHighlighter", new TextHighlighter({
|
||||||
pageIndex: this.id - 1,
|
pageIndex: this.id - 1,
|
||||||
@ -9472,6 +9492,19 @@ class PDFPageView {
|
|||||||
this._resetZoomLayer();
|
this._resetZoomLayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
toggleEditingMode(isEditing) {
|
||||||
|
if (!this.hasEditableAnnotations()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#isEditing = isEditing;
|
||||||
|
this.reset({
|
||||||
|
keepZoomLayer: true,
|
||||||
|
keepAnnotationLayer: true,
|
||||||
|
keepAnnotationEditorLayer: true,
|
||||||
|
keepXfaLayer: true,
|
||||||
|
keepTextLayer: true
|
||||||
|
});
|
||||||
|
}
|
||||||
update({
|
update({
|
||||||
scale = 0,
|
scale = 0,
|
||||||
rotation = null,
|
rotation = null,
|
||||||
@ -9822,7 +9855,8 @@ class PDFPageView {
|
|||||||
annotationMode: this.#annotationMode,
|
annotationMode: this.#annotationMode,
|
||||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||||
annotationCanvasMap: this._annotationCanvasMap,
|
annotationCanvasMap: this._annotationCanvasMap,
|
||||||
pageColors
|
pageColors,
|
||||||
|
isEditing: this.#isEditing
|
||||||
};
|
};
|
||||||
const renderTask = this.renderTask = pdfPage.render(renderContext);
|
const renderTask = this.renderTask = pdfPage.render(renderContext);
|
||||||
renderTask.onContinue = renderContinueCallback;
|
renderTask.onContinue = renderContinueCallback;
|
||||||
@ -9982,8 +10016,11 @@ class PDFViewer {
|
|||||||
#enableHWA = false;
|
#enableHWA = false;
|
||||||
#enableHighlightFloatingButton = false;
|
#enableHighlightFloatingButton = false;
|
||||||
#enablePermissions = false;
|
#enablePermissions = false;
|
||||||
|
#enableUpdatedAddImage = false;
|
||||||
#eventAbortController = null;
|
#eventAbortController = null;
|
||||||
#mlManager = null;
|
#mlManager = null;
|
||||||
|
#onPageRenderedCallback = null;
|
||||||
|
#switchAnnotationEditorModeTimeoutId = null;
|
||||||
#getAllTextInProgress = false;
|
#getAllTextInProgress = false;
|
||||||
#hiddenCopyElement = null;
|
#hiddenCopyElement = null;
|
||||||
#interruptCopyCondition = false;
|
#interruptCopyCondition = false;
|
||||||
@ -9993,7 +10030,7 @@ class PDFViewer {
|
|||||||
#scaleTimeoutId = null;
|
#scaleTimeoutId = null;
|
||||||
#textLayerMode = TextLayerMode.ENABLE;
|
#textLayerMode = TextLayerMode.ENABLE;
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
const viewerVersion = "4.4.168";
|
const viewerVersion = "4.5.136";
|
||||||
if (version !== viewerVersion) {
|
if (version !== viewerVersion) {
|
||||||
throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`);
|
throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`);
|
||||||
}
|
}
|
||||||
@ -10020,6 +10057,7 @@ class PDFViewer {
|
|||||||
this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE;
|
this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE;
|
||||||
this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null;
|
this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null;
|
||||||
this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true;
|
this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true;
|
||||||
|
this.#enableUpdatedAddImage = options.enableUpdatedAddImage === true;
|
||||||
this.imageResourcesPath = options.imageResourcesPath || "";
|
this.imageResourcesPath = options.imageResourcesPath || "";
|
||||||
this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
|
this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
|
||||||
this.removePageBorders = options.removePageBorders || false;
|
this.removePageBorders = options.removePageBorders || false;
|
||||||
@ -10425,7 +10463,7 @@ class PDFViewer {
|
|||||||
if (pdfDocument.isPureXfa) {
|
if (pdfDocument.isPureXfa) {
|
||||||
console.warn("Warning: XFA-editing is not implemented.");
|
console.warn("Warning: XFA-editing is not implemented.");
|
||||||
} else if (isValidAnnotationEditorMode(mode)) {
|
} else if (isValidAnnotationEditorMode(mode)) {
|
||||||
this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, viewer, this.#altTextManager, eventBus, pdfDocument, pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#mlManager);
|
this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, viewer, this.#altTextManager, eventBus, pdfDocument, pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#enableUpdatedAddImage, this.#mlManager);
|
||||||
eventBus.dispatch("annotationeditoruimanager", {
|
eventBus.dispatch("annotationeditoruimanager", {
|
||||||
source: this,
|
source: this,
|
||||||
uiManager: this.#annotationEditorUIManager
|
uiManager: this.#annotationEditorUIManager
|
||||||
@ -10584,6 +10622,7 @@ class PDFViewer {
|
|||||||
this.viewer.removeAttribute("lang");
|
this.viewer.removeAttribute("lang");
|
||||||
this.#hiddenCopyElement?.remove();
|
this.#hiddenCopyElement?.remove();
|
||||||
this.#hiddenCopyElement = null;
|
this.#hiddenCopyElement = null;
|
||||||
|
this.#cleanupSwitchAnnotationEditorMode();
|
||||||
}
|
}
|
||||||
#ensurePageViewVisible() {
|
#ensurePageViewVisible() {
|
||||||
if (this._scrollMode !== ScrollMode.PAGE) {
|
if (this._scrollMode !== ScrollMode.PAGE) {
|
||||||
@ -10956,6 +10995,34 @@ class PDFViewer {
|
|||||||
location: this._location
|
location: this._location
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
#switchToEditAnnotationMode() {
|
||||||
|
const visible = this._getVisiblePages();
|
||||||
|
const pagesToRefresh = [];
|
||||||
|
const {
|
||||||
|
ids,
|
||||||
|
views
|
||||||
|
} = visible;
|
||||||
|
for (const page of views) {
|
||||||
|
const {
|
||||||
|
view
|
||||||
|
} = page;
|
||||||
|
if (!view.hasEditableAnnotations()) {
|
||||||
|
ids.delete(view.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pagesToRefresh.push(page);
|
||||||
|
}
|
||||||
|
if (pagesToRefresh.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.renderingQueue.renderHighestPriority({
|
||||||
|
first: pagesToRefresh[0],
|
||||||
|
last: pagesToRefresh.at(-1),
|
||||||
|
views: pagesToRefresh,
|
||||||
|
ids
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
containsElement(element) {
|
containsElement(element) {
|
||||||
return this.container.contains(element);
|
return this.container.contains(element);
|
||||||
}
|
}
|
||||||
@ -11388,6 +11455,16 @@ class PDFViewer {
|
|||||||
get containerTopLeft() {
|
get containerTopLeft() {
|
||||||
return this.#containerTopLeft ||= [this.container.offsetTop, this.container.offsetLeft];
|
return this.#containerTopLeft ||= [this.container.offsetTop, this.container.offsetLeft];
|
||||||
}
|
}
|
||||||
|
#cleanupSwitchAnnotationEditorMode() {
|
||||||
|
if (this.#onPageRenderedCallback) {
|
||||||
|
this.eventBus._off("pagerendered", this.#onPageRenderedCallback);
|
||||||
|
this.#onPageRenderedCallback = null;
|
||||||
|
}
|
||||||
|
if (this.#switchAnnotationEditorModeTimeoutId !== null) {
|
||||||
|
clearTimeout(this.#switchAnnotationEditorModeTimeoutId);
|
||||||
|
this.#switchAnnotationEditorModeTimeoutId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
get annotationEditorMode() {
|
get annotationEditorMode() {
|
||||||
return this.#annotationEditorUIManager ? this.#annotationEditorMode : AnnotationEditorType.DISABLE;
|
return this.#annotationEditorUIManager ? this.#annotationEditorMode : AnnotationEditorType.DISABLE;
|
||||||
}
|
}
|
||||||
@ -11408,12 +11485,47 @@ class PDFViewer {
|
|||||||
if (!this.pdfDocument) {
|
if (!this.pdfDocument) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#annotationEditorMode = mode;
|
const {
|
||||||
this.eventBus.dispatch("annotationeditormodechanged", {
|
eventBus
|
||||||
source: this,
|
} = this;
|
||||||
mode
|
const updater = () => {
|
||||||
});
|
this.#cleanupSwitchAnnotationEditorMode();
|
||||||
this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard);
|
this.#annotationEditorMode = mode;
|
||||||
|
this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard);
|
||||||
|
eventBus.dispatch("annotationeditormodechanged", {
|
||||||
|
source: this,
|
||||||
|
mode
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (mode === AnnotationEditorType.NONE || this.#annotationEditorMode === AnnotationEditorType.NONE) {
|
||||||
|
const isEditing = mode !== AnnotationEditorType.NONE;
|
||||||
|
if (!isEditing) {
|
||||||
|
this.pdfDocument.annotationStorage.resetModifiedIds();
|
||||||
|
}
|
||||||
|
for (const pageView of this._pages) {
|
||||||
|
pageView.toggleEditingMode(isEditing);
|
||||||
|
}
|
||||||
|
const idsToRefresh = this.#switchToEditAnnotationMode();
|
||||||
|
if (isEditing && idsToRefresh) {
|
||||||
|
this.#cleanupSwitchAnnotationEditorMode();
|
||||||
|
this.#onPageRenderedCallback = ({
|
||||||
|
pageNumber
|
||||||
|
}) => {
|
||||||
|
idsToRefresh.delete(pageNumber);
|
||||||
|
if (idsToRefresh.size === 0) {
|
||||||
|
this.#switchAnnotationEditorModeTimeoutId = setTimeout(updater, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const {
|
||||||
|
signal
|
||||||
|
} = this.#eventAbortController;
|
||||||
|
eventBus._on("pagerendered", this.#onPageRenderedCallback, {
|
||||||
|
signal
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updater();
|
||||||
}
|
}
|
||||||
set annotationEditorParams({
|
set annotationEditorParams({
|
||||||
type,
|
type,
|
||||||
@ -11721,7 +11833,7 @@ class SecondaryToolbar {
|
|||||||
|
|
||||||
class Toolbar {
|
class Toolbar {
|
||||||
#opts;
|
#opts;
|
||||||
constructor(options, eventBus) {
|
constructor(options, eventBus, toolbarDensity = 0) {
|
||||||
this.#opts = options;
|
this.#opts = options;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
const buttons = [{
|
const buttons = [{
|
||||||
@ -11806,8 +11918,13 @@ class Toolbar {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
eventBus._on("toolbardensity", this.#updateToolbarDensity.bind(this));
|
||||||
|
this.#updateToolbarDensity({
|
||||||
|
value: toolbarDensity
|
||||||
|
});
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
#updateToolbarDensity() {}
|
||||||
#setAnnotationEditorUIManager(uiManager, parentContainer) {
|
#setAnnotationEditorUIManager(uiManager, parentContainer) {
|
||||||
const colorPicker = new ColorPicker({
|
const colorPicker = new ColorPicker({
|
||||||
uiManager
|
uiManager
|
||||||
@ -12078,7 +12195,6 @@ class ViewHistory {
|
|||||||
|
|
||||||
|
|
||||||
const FORCE_PAGES_LOADED_TIMEOUT = 10000;
|
const FORCE_PAGES_LOADED_TIMEOUT = 10000;
|
||||||
const WHEEL_ZOOM_DISABLED_TIMEOUT = 1000;
|
|
||||||
const ViewOnLoad = {
|
const ViewOnLoad = {
|
||||||
UNKNOWN: -1,
|
UNKNOWN: -1,
|
||||||
PREVIOUS: 0,
|
PREVIOUS: 0,
|
||||||
@ -12110,18 +12226,17 @@ const PDFViewerApplication = {
|
|||||||
store: null,
|
store: null,
|
||||||
downloadManager: null,
|
downloadManager: null,
|
||||||
overlayManager: null,
|
overlayManager: null,
|
||||||
preferences: null,
|
preferences: new Preferences(),
|
||||||
toolbar: null,
|
toolbar: null,
|
||||||
secondaryToolbar: null,
|
secondaryToolbar: null,
|
||||||
eventBus: null,
|
eventBus: null,
|
||||||
l10n: null,
|
l10n: null,
|
||||||
annotationEditorParams: null,
|
annotationEditorParams: null,
|
||||||
isInitialViewSet: false,
|
isInitialViewSet: false,
|
||||||
downloadComplete: false,
|
|
||||||
isViewerEmbedded: window.parent !== window,
|
isViewerEmbedded: window.parent !== window,
|
||||||
url: "",
|
url: "",
|
||||||
baseUrl: "",
|
baseUrl: "",
|
||||||
_allowedGlobalEventsPromise: null,
|
mlManager: null,
|
||||||
_downloadUrl: "",
|
_downloadUrl: "",
|
||||||
_eventBusAbortController: null,
|
_eventBusAbortController: null,
|
||||||
_windowAbortController: null,
|
_windowAbortController: null,
|
||||||
@ -12141,11 +12256,9 @@ const PDFViewerApplication = {
|
|||||||
_printAnnotationStoragePromise: null,
|
_printAnnotationStoragePromise: null,
|
||||||
_touchInfo: null,
|
_touchInfo: null,
|
||||||
_isCtrlKeyDown: false,
|
_isCtrlKeyDown: false,
|
||||||
_nimbusDataPromise: null,
|
|
||||||
_caretBrowsing: null,
|
_caretBrowsing: null,
|
||||||
_isScrolling: false,
|
_isScrolling: false,
|
||||||
async initialize(appConfig) {
|
async initialize(appConfig) {
|
||||||
let l10nPromise;
|
|
||||||
this.appConfig = appConfig;
|
this.appConfig = appConfig;
|
||||||
try {
|
try {
|
||||||
await this.preferences.initializedPromise;
|
await this.preferences.initializedPromise;
|
||||||
@ -12167,8 +12280,7 @@ const PDFViewerApplication = {
|
|||||||
if (mode) {
|
if (mode) {
|
||||||
document.documentElement.classList.add(mode);
|
document.documentElement.classList.add(mode);
|
||||||
}
|
}
|
||||||
l10nPromise = this.externalServices.createL10n();
|
this.l10n = await this.externalServices.createL10n();
|
||||||
this.l10n = await l10nPromise;
|
|
||||||
document.getElementsByTagName("html")[0].dir = this.l10n.getDirection();
|
document.getElementsByTagName("html")[0].dir = this.l10n.getDirection();
|
||||||
this.l10n.translate(appConfig.appContainer || document.documentElement);
|
this.l10n.translate(appConfig.appContainer || document.documentElement);
|
||||||
if (this.isViewerEmbedded && AppOptions.get("externalLinkTarget") === LinkTarget.NONE) {
|
if (this.isViewerEmbedded && AppOptions.get("externalLinkTarget") === LinkTarget.NONE) {
|
||||||
@ -12257,7 +12369,9 @@ const PDFViewerApplication = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (params.has("locale")) {
|
if (params.has("locale")) {
|
||||||
AppOptions.set("locale", params.get("locale"));
|
AppOptions.set("localeProperties", {
|
||||||
|
lang: params.get("locale")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async _initializeViewerComponents() {
|
async _initializeViewerComponents() {
|
||||||
@ -12318,6 +12432,7 @@ const PDFViewerApplication = {
|
|||||||
annotationEditorMode,
|
annotationEditorMode,
|
||||||
annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
|
annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
|
||||||
enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"),
|
enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"),
|
||||||
|
enableUpdatedAddImage: AppOptions.get("enableUpdatedAddImage"),
|
||||||
imageResourcesPath: AppOptions.get("imageResourcesPath"),
|
imageResourcesPath: AppOptions.get("imageResourcesPath"),
|
||||||
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
|
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
|
||||||
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
||||||
@ -12355,9 +12470,6 @@ const PDFViewerApplication = {
|
|||||||
}
|
}
|
||||||
if (appConfig.annotationEditorParams) {
|
if (appConfig.annotationEditorParams) {
|
||||||
if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
|
if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
|
||||||
if (AppOptions.get("enableStampEditor")) {
|
|
||||||
appConfig.toolbar?.editorStampButton?.classList.remove("hidden");
|
|
||||||
}
|
|
||||||
const editorHighlightButton = appConfig.toolbar?.editorHighlightButton;
|
const editorHighlightButton = appConfig.toolbar?.editorHighlightButton;
|
||||||
if (editorHighlightButton && AppOptions.get("enableHighlightEditor")) {
|
if (editorHighlightButton && AppOptions.get("enableHighlightEditor")) {
|
||||||
editorHighlightButton.hidden = false;
|
editorHighlightButton.hidden = false;
|
||||||
@ -12380,7 +12492,7 @@ const PDFViewerApplication = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (appConfig.toolbar) {
|
if (appConfig.toolbar) {
|
||||||
this.toolbar = new Toolbar(appConfig.toolbar, eventBus);
|
this.toolbar = new Toolbar(appConfig.toolbar, eventBus, AppOptions.get("toolbarDensity"));
|
||||||
}
|
}
|
||||||
if (appConfig.secondaryToolbar) {
|
if (appConfig.secondaryToolbar) {
|
||||||
this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, eventBus);
|
this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, eventBus);
|
||||||
@ -12437,7 +12549,6 @@ const PDFViewerApplication = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async run(config) {
|
async run(config) {
|
||||||
this.preferences = new Preferences();
|
|
||||||
await this.initialize(config);
|
await this.initialize(config);
|
||||||
const {
|
const {
|
||||||
appConfig,
|
appConfig,
|
||||||
@ -12514,9 +12625,6 @@ const PDFViewerApplication = {
|
|||||||
get externalServices() {
|
get externalServices() {
|
||||||
return shadow(this, "externalServices", new ExternalServices());
|
return shadow(this, "externalServices", new ExternalServices());
|
||||||
},
|
},
|
||||||
get mlManager() {
|
|
||||||
return shadow(this, "mlManager", AppOptions.get("enableML") === true ? new MLManager() : null);
|
|
||||||
},
|
|
||||||
get initialized() {
|
get initialized() {
|
||||||
return this._initializedCapability.settled;
|
return this._initializedCapability.settled;
|
||||||
},
|
},
|
||||||
@ -12597,12 +12705,10 @@ const PDFViewerApplication = {
|
|||||||
let title = pdfjs_getPdfFilenameFromUrl(url, "");
|
let title = pdfjs_getPdfFilenameFromUrl(url, "");
|
||||||
if (!title) {
|
if (!title) {
|
||||||
try {
|
try {
|
||||||
title = decodeURIComponent(getFilenameFromUrl(url)) || url;
|
title = decodeURIComponent(getFilenameFromUrl(url));
|
||||||
} catch {
|
} catch {}
|
||||||
title = url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.setTitle(title);
|
this.setTitle(title || url);
|
||||||
},
|
},
|
||||||
setTitle(title = this._title) {
|
setTitle(title = this._title) {
|
||||||
this._title = title;
|
this._title = title;
|
||||||
@ -12648,7 +12754,6 @@ const PDFViewerApplication = {
|
|||||||
this.pdfLinkService.externalLinkEnabled = true;
|
this.pdfLinkService.externalLinkEnabled = true;
|
||||||
this.store = null;
|
this.store = null;
|
||||||
this.isInitialViewSet = false;
|
this.isInitialViewSet = false;
|
||||||
this.downloadComplete = false;
|
|
||||||
this.url = "";
|
this.url = "";
|
||||||
this.baseUrl = "";
|
this.baseUrl = "";
|
||||||
this._downloadUrl = "";
|
this._downloadUrl = "";
|
||||||
@ -12724,9 +12829,7 @@ const PDFViewerApplication = {
|
|||||||
async download(options = {}) {
|
async download(options = {}) {
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
if (this.downloadComplete) {
|
data = await this.pdfDocument.getData();
|
||||||
data = await this.pdfDocument.getData();
|
|
||||||
}
|
|
||||||
} catch {}
|
} catch {}
|
||||||
this.downloadManager.download(data, this._downloadUrl, this._docFilename, options);
|
this.downloadManager.download(data, this._downloadUrl, this._docFilename, options);
|
||||||
},
|
},
|
||||||
@ -12793,11 +12896,8 @@ const PDFViewerApplication = {
|
|||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
progress(level) {
|
progress(level) {
|
||||||
if (!this.loadingBar || this.downloadComplete) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const percent = Math.round(level * 100);
|
const percent = Math.round(level * 100);
|
||||||
if (percent <= this.loadingBar.percent) {
|
if (!this.loadingBar || percent <= this.loadingBar.percent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loadingBar.percent = percent;
|
this.loadingBar.percent = percent;
|
||||||
@ -12811,7 +12911,6 @@ const PDFViewerApplication = {
|
|||||||
length
|
length
|
||||||
}) => {
|
}) => {
|
||||||
this._contentLength = length;
|
this._contentLength = length;
|
||||||
this.downloadComplete = true;
|
|
||||||
this.loadingBar?.hide();
|
this.loadingBar?.hide();
|
||||||
firstPagePromise.then(() => {
|
firstPagePromise.then(() => {
|
||||||
this.eventBus.dispatch("documentloaded", {
|
this.eventBus.dispatch("documentloaded", {
|
||||||
@ -13413,9 +13512,6 @@ const PDFViewerApplication = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
addWindowResolutionChange();
|
addWindowResolutionChange();
|
||||||
window.addEventListener("visibilitychange", webViewerVisibilityChange, {
|
|
||||||
signal
|
|
||||||
});
|
|
||||||
window.addEventListener("wheel", webViewerWheel, {
|
window.addEventListener("wheel", webViewerWheel, {
|
||||||
passive: false,
|
passive: false,
|
||||||
signal
|
signal
|
||||||
@ -13730,7 +13826,7 @@ function webViewerHashchange(evt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
/*var webViewerFileInputChange = function (evt) {
|
var webViewerFileInputChange = function (evt) {
|
||||||
if (PDFViewerApplication.pdfViewer?.isInPresentationMode) {
|
if (PDFViewerApplication.pdfViewer?.isInPresentationMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -13742,7 +13838,7 @@ function webViewerHashchange(evt) {
|
|||||||
};
|
};
|
||||||
var webViewerOpenFile = function (evt) {
|
var webViewerOpenFile = function (evt) {
|
||||||
PDFViewerApplication._openFileInput?.click();
|
PDFViewerApplication._openFileInput?.click();
|
||||||
};*/
|
};
|
||||||
}
|
}
|
||||||
function webViewerPresentationMode() {
|
function webViewerPresentationMode() {
|
||||||
PDFViewerApplication.requestPresentationMode();
|
PDFViewerApplication.requestPresentationMode();
|
||||||
@ -13876,20 +13972,6 @@ function webViewerPageChanging({
|
|||||||
function webViewerResolutionChange(evt) {
|
function webViewerResolutionChange(evt) {
|
||||||
PDFViewerApplication.pdfViewer.refresh();
|
PDFViewerApplication.pdfViewer.refresh();
|
||||||
}
|
}
|
||||||
function webViewerVisibilityChange(evt) {
|
|
||||||
if (document.visibilityState === "visible") {
|
|
||||||
setZoomDisabledTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let zoomDisabledTimeout = null;
|
|
||||||
function setZoomDisabledTimeout() {
|
|
||||||
if (zoomDisabledTimeout) {
|
|
||||||
clearTimeout(zoomDisabledTimeout);
|
|
||||||
}
|
|
||||||
zoomDisabledTimeout = setTimeout(function () {
|
|
||||||
zoomDisabledTimeout = null;
|
|
||||||
}, WHEEL_ZOOM_DISABLED_TIMEOUT);
|
|
||||||
}
|
|
||||||
function webViewerWheel(evt) {
|
function webViewerWheel(evt) {
|
||||||
const {
|
const {
|
||||||
pdfViewer,
|
pdfViewer,
|
||||||
@ -13907,7 +13989,7 @@ function webViewerWheel(evt) {
|
|||||||
const origin = [evt.clientX, evt.clientY];
|
const origin = [evt.clientX, evt.clientY];
|
||||||
if (isPinchToZoom || evt.ctrlKey && supportsMouseWheelZoomCtrlKey || evt.metaKey && supportsMouseWheelZoomMetaKey) {
|
if (isPinchToZoom || evt.ctrlKey && supportsMouseWheelZoomCtrlKey || evt.metaKey && supportsMouseWheelZoomMetaKey) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
if (PDFViewerApplication._isScrolling || zoomDisabledTimeout || document.visibilityState === "hidden" || PDFViewerApplication.overlayManager.active) {
|
if (PDFViewerApplication._isScrolling || document.visibilityState === "hidden" || PDFViewerApplication.overlayManager.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isPinchToZoom && supportsPinchToZoom) {
|
if (isPinchToZoom && supportsPinchToZoom) {
|
||||||
@ -14335,14 +14417,20 @@ function webViewerReportTelemetry({
|
|||||||
}) {
|
}) {
|
||||||
PDFViewerApplication.externalServices.reportTelemetry(details);
|
PDFViewerApplication.externalServices.reportTelemetry(details);
|
||||||
}
|
}
|
||||||
|
function webViewerSetPreference({
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
}) {
|
||||||
|
PDFViewerApplication.preferences.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ./web/viewer.js
|
;// CONCATENATED MODULE: ./web/viewer.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const pdfjsVersion = "4.4.168";
|
const pdfjsVersion = "4.5.136";
|
||||||
const pdfjsBuild = "19fbc8998";
|
const pdfjsBuild = "3a21f03b0";
|
||||||
const AppConstants = {
|
const AppConstants = {
|
||||||
LinkTarget: LinkTarget,
|
LinkTarget: LinkTarget,
|
||||||
RenderingStates: RenderingStates,
|
RenderingStates: RenderingStates,
|
||||||
|
@ -20,14 +20,13 @@ import os
|
|||||||
from shutil import copyfile, copyfileobj
|
from shutil import copyfile, copyfileobj
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from .. import constants
|
from .. import constants
|
||||||
from cps import config, db, fs, gdriveutils, logger, ub
|
from cps import config, db, fs, gdriveutils, logger, ub
|
||||||
from cps.services.worker import CalibreTask, STAT_CANCELLED, STAT_ENDED
|
from cps.services.worker import CalibreTask, STAT_CANCELLED, STAT_ENDED
|
||||||
from datetime import datetime
|
|
||||||
from sqlalchemy import func, text, or_
|
from sqlalchemy import func, text, or_
|
||||||
from flask_babel import lazy_gettext as N_
|
from flask_babel import lazy_gettext as N_
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from wand.image import Image
|
from wand.image import Image
|
||||||
use_IM = True
|
use_IM = True
|
||||||
@ -123,7 +122,7 @@ class TaskGenerateCoverThumbnails(CalibreTask):
|
|||||||
.query(ub.Thumbnail) \
|
.query(ub.Thumbnail) \
|
||||||
.filter(ub.Thumbnail.type == constants.THUMBNAIL_TYPE_COVER) \
|
.filter(ub.Thumbnail.type == constants.THUMBNAIL_TYPE_COVER) \
|
||||||
.filter(ub.Thumbnail.entity_id == book_id) \
|
.filter(ub.Thumbnail.entity_id == book_id) \
|
||||||
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow())) \
|
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.now(timezone.utc))) \
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
def create_book_cover_thumbnails(self, book):
|
def create_book_cover_thumbnails(self, book):
|
||||||
@ -165,7 +164,7 @@ class TaskGenerateCoverThumbnails(CalibreTask):
|
|||||||
self.app_db_session.rollback()
|
self.app_db_session.rollback()
|
||||||
|
|
||||||
def update_book_cover_thumbnail(self, book, thumbnail):
|
def update_book_cover_thumbnail(self, book, thumbnail):
|
||||||
thumbnail.generated_at = datetime.utcnow()
|
thumbnail.generated_at = datetime.now(timezone.utc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.app_db_session.commit()
|
self.app_db_session.commit()
|
||||||
@ -322,12 +321,12 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
|
|||||||
.all()
|
.all()
|
||||||
|
|
||||||
def get_series_thumbnails(self, series_id):
|
def get_series_thumbnails(self, series_id):
|
||||||
return self.app_db_session \
|
return (self.app_db_session
|
||||||
.query(ub.Thumbnail) \
|
.query(ub.Thumbnail)
|
||||||
.filter(ub.Thumbnail.type == constants.THUMBNAIL_TYPE_SERIES) \
|
.filter(ub.Thumbnail.type == constants.THUMBNAIL_TYPE_SERIES)
|
||||||
.filter(ub.Thumbnail.entity_id == series_id) \
|
.filter(ub.Thumbnail.entity_id == series_id)
|
||||||
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow())) \
|
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.now(timezone.utc)))
|
||||||
.all()
|
.all())
|
||||||
|
|
||||||
def create_series_thumbnail(self, series, series_books, resolution):
|
def create_series_thumbnail(self, series, series_books, resolution):
|
||||||
thumbnail = ub.Thumbnail()
|
thumbnail = ub.Thumbnail()
|
||||||
@ -346,7 +345,7 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
|
|||||||
self.app_db_session.rollback()
|
self.app_db_session.rollback()
|
||||||
|
|
||||||
def update_series_thumbnail(self, series_books, thumbnail):
|
def update_series_thumbnail(self, series_books, thumbnail):
|
||||||
thumbnail.generated_at = datetime.utcnow()
|
thumbnail.generated_at = datetime.now(timezone.utc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.app_db_session.commit()
|
self.app_db_session.commit()
|
||||||
|
38
cps/ub.py
38
cps/ub.py
@ -20,7 +20,7 @@
|
|||||||
import atexit
|
import atexit
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import datetime
|
from datetime import datetime, timezone, timedelta
|
||||||
import itertools
|
import itertools
|
||||||
import uuid
|
import uuid
|
||||||
from flask import session as flask_session
|
from flask import session as flask_session
|
||||||
@ -77,7 +77,7 @@ def store_user_session():
|
|||||||
if flask_session.get('_user_id', ""):
|
if flask_session.get('_user_id', ""):
|
||||||
try:
|
try:
|
||||||
if not check_user_session(_user, _id, _random):
|
if not check_user_session(_user, _id, _random):
|
||||||
expiry = int((datetime.datetime.now() + datetime.timedelta(days=31)).timestamp())
|
expiry = int((datetime.now() + timedelta(days=31)).timestamp())
|
||||||
user_session = User_Sessions(_user, _id, _random, expiry)
|
user_session = User_Sessions(_user, _id, _random, expiry)
|
||||||
session.add(user_session)
|
session.add(user_session)
|
||||||
session.commit()
|
session.commit()
|
||||||
@ -109,7 +109,7 @@ def check_user_session(user_id, session_key, random):
|
|||||||
User_Sessions.random == random,
|
User_Sessions.random == random,
|
||||||
).one_or_none()
|
).one_or_none()
|
||||||
if found is not None:
|
if found is not None:
|
||||||
new_expiry = int((datetime.datetime.now() + datetime.timedelta(days=31)).timestamp())
|
new_expiry = int((datetime.now() + timedelta(days=31)).timestamp())
|
||||||
if new_expiry - found.expiry > 86400:
|
if new_expiry - found.expiry > 86400:
|
||||||
found.expiry = new_expiry
|
found.expiry = new_expiry
|
||||||
session.merge(found)
|
session.merge(found)
|
||||||
@ -370,8 +370,8 @@ class Shelf(Base):
|
|||||||
user_id = Column(Integer, ForeignKey('user.id'))
|
user_id = Column(Integer, ForeignKey('user.id'))
|
||||||
kobo_sync = Column(Boolean, default=False)
|
kobo_sync = Column(Boolean, default=False)
|
||||||
books = relationship("BookShelf", backref="ub_shelf", cascade="all, delete-orphan", lazy="dynamic")
|
books = relationship("BookShelf", backref="ub_shelf", cascade="all, delete-orphan", lazy="dynamic")
|
||||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
created = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
last_modified = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
last_modified = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Shelf %d:%r>' % (self.id, self.name)
|
return '<Shelf %d:%r>' % (self.id, self.name)
|
||||||
@ -385,7 +385,7 @@ class BookShelf(Base):
|
|||||||
book_id = Column(Integer)
|
book_id = Column(Integer)
|
||||||
order = Column(Integer)
|
order = Column(Integer)
|
||||||
shelf = Column(Integer, ForeignKey('shelf.id'))
|
shelf = Column(Integer, ForeignKey('shelf.id'))
|
||||||
date_added = Column(DateTime, default=datetime.datetime.utcnow)
|
date_added = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Book %r>' % self.id
|
return '<Book %r>' % self.id
|
||||||
@ -398,7 +398,7 @@ class ShelfArchive(Base):
|
|||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
uuid = Column(String)
|
uuid = Column(String)
|
||||||
user_id = Column(Integer, ForeignKey('user.id'))
|
user_id = Column(Integer, ForeignKey('user.id'))
|
||||||
last_modified = Column(DateTime, default=datetime.datetime.utcnow)
|
last_modified = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
|
|
||||||
class ReadBook(Base):
|
class ReadBook(Base):
|
||||||
@ -418,7 +418,7 @@ class ReadBook(Base):
|
|||||||
cascade="all",
|
cascade="all",
|
||||||
backref=backref("book_read_link",
|
backref=backref("book_read_link",
|
||||||
uselist=False))
|
uselist=False))
|
||||||
last_modified = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
last_modified = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||||
last_time_started_reading = Column(DateTime, nullable=True)
|
last_time_started_reading = Column(DateTime, nullable=True)
|
||||||
times_started_reading = Column(Integer, default=0, nullable=False)
|
times_started_reading = Column(Integer, default=0, nullable=False)
|
||||||
|
|
||||||
@ -441,7 +441,7 @@ class ArchivedBook(Base):
|
|||||||
user_id = Column(Integer, ForeignKey('user.id'))
|
user_id = Column(Integer, ForeignKey('user.id'))
|
||||||
book_id = Column(Integer)
|
book_id = Column(Integer)
|
||||||
is_archived = Column(Boolean, unique=False)
|
is_archived = Column(Boolean, unique=False)
|
||||||
last_modified = Column(DateTime, default=datetime.datetime.utcnow)
|
last_modified = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
|
|
||||||
class KoboSyncedBooks(Base):
|
class KoboSyncedBooks(Base):
|
||||||
@ -460,8 +460,8 @@ class KoboReadingState(Base):
|
|||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
user_id = Column(Integer, ForeignKey('user.id'))
|
user_id = Column(Integer, ForeignKey('user.id'))
|
||||||
book_id = Column(Integer)
|
book_id = Column(Integer)
|
||||||
last_modified = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
last_modified = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||||
priority_timestamp = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
priority_timestamp = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||||
current_bookmark = relationship("KoboBookmark", uselist=False, backref="kobo_reading_state", cascade="all, delete")
|
current_bookmark = relationship("KoboBookmark", uselist=False, backref="kobo_reading_state", cascade="all, delete")
|
||||||
statistics = relationship("KoboStatistics", uselist=False, backref="kobo_reading_state", cascade="all, delete")
|
statistics = relationship("KoboStatistics", uselist=False, backref="kobo_reading_state", cascade="all, delete")
|
||||||
|
|
||||||
@ -471,7 +471,7 @@ class KoboBookmark(Base):
|
|||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
kobo_reading_state_id = Column(Integer, ForeignKey('kobo_reading_state.id'))
|
kobo_reading_state_id = Column(Integer, ForeignKey('kobo_reading_state.id'))
|
||||||
last_modified = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
last_modified = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||||
location_source = Column(String)
|
location_source = Column(String)
|
||||||
location_type = Column(String)
|
location_type = Column(String)
|
||||||
location_value = Column(String)
|
location_value = Column(String)
|
||||||
@ -484,7 +484,7 @@ class KoboStatistics(Base):
|
|||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
kobo_reading_state_id = Column(Integer, ForeignKey('kobo_reading_state.id'))
|
kobo_reading_state_id = Column(Integer, ForeignKey('kobo_reading_state.id'))
|
||||||
last_modified = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
last_modified = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||||
remaining_time_minutes = Column(Integer)
|
remaining_time_minutes = Column(Integer)
|
||||||
spent_reading_minutes = Column(Integer)
|
spent_reading_minutes = Column(Integer)
|
||||||
|
|
||||||
@ -495,11 +495,11 @@ def receive_before_flush(session, flush_context, instances):
|
|||||||
for change in itertools.chain(session.new, session.dirty):
|
for change in itertools.chain(session.new, session.dirty):
|
||||||
if isinstance(change, (ReadBook, KoboStatistics, KoboBookmark)):
|
if isinstance(change, (ReadBook, KoboStatistics, KoboBookmark)):
|
||||||
if change.kobo_reading_state:
|
if change.kobo_reading_state:
|
||||||
change.kobo_reading_state.last_modified = datetime.datetime.utcnow()
|
change.kobo_reading_state.last_modified = datetime.now(timezone.utc)
|
||||||
# Maintain the last_modified bit for the Shelf table.
|
# Maintain the last_modified_bit for the Shelf table.
|
||||||
for change in itertools.chain(session.new, session.deleted):
|
for change in itertools.chain(session.new, session.deleted):
|
||||||
if isinstance(change, BookShelf):
|
if isinstance(change, BookShelf):
|
||||||
change.ub_shelf.last_modified = datetime.datetime.utcnow()
|
change.ub_shelf.last_modified = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
# Baseclass representing Downloads from calibre-web in app.db
|
# Baseclass representing Downloads from calibre-web in app.db
|
||||||
@ -539,7 +539,7 @@ class RemoteAuthToken(Base):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.auth_token = (hexlify(os.urandom(4))).decode('utf-8')
|
self.auth_token = (hexlify(os.urandom(4))).decode('utf-8')
|
||||||
self.expiration = datetime.datetime.now() + datetime.timedelta(minutes=10) # 10 min from now
|
self.expiration = datetime.now() + timedelta(minutes=10) # 10 min from now
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Token %r>' % self.id
|
return '<Token %r>' % self.id
|
||||||
@ -563,7 +563,7 @@ class Thumbnail(Base):
|
|||||||
type = Column(SmallInteger, default=constants.THUMBNAIL_TYPE_COVER)
|
type = Column(SmallInteger, default=constants.THUMBNAIL_TYPE_COVER)
|
||||||
resolution = Column(SmallInteger, default=constants.COVER_THUMBNAIL_SMALL)
|
resolution = Column(SmallInteger, default=constants.COVER_THUMBNAIL_SMALL)
|
||||||
filename = Column(String, default=filename)
|
filename = Column(String, default=filename)
|
||||||
generated_at = Column(DateTime, default=lambda: datetime.datetime.utcnow())
|
generated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
expiration = Column(DateTime, nullable=True)
|
expiration = Column(DateTime, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
@ -614,7 +614,7 @@ def migrate_Database(_session):
|
|||||||
|
|
||||||
def clean_database(_session):
|
def clean_database(_session):
|
||||||
# Remove expired remote login tokens
|
# Remove expired remote login tokens
|
||||||
now = datetime.datetime.now()
|
now = datetime.now()
|
||||||
try:
|
try:
|
||||||
_session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).\
|
_session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).\
|
||||||
filter(RemoteAuthToken.token_type != 1).delete()
|
filter(RemoteAuthToken.token_type != 1).delete()
|
||||||
|
@ -68,6 +68,13 @@ except ImportError as e:
|
|||||||
log.debug('Cannot import fb2, extracting fb2 metadata will not work: %s', e)
|
log.debug('Cannot import fb2, extracting fb2 metadata will not work: %s', e)
|
||||||
use_fb2_meta = False
|
use_fb2_meta = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from . import audio
|
||||||
|
use_audio_meta = True
|
||||||
|
except ImportError as e:
|
||||||
|
log.debug('Cannot import mutagen, extracting audio metadata will not work: %s', e)
|
||||||
|
use_audio_meta = False
|
||||||
|
|
||||||
|
|
||||||
def process(tmp_file_path, original_file_name, original_file_extension, rar_executable):
|
def process(tmp_file_path, original_file_name, original_file_extension, rar_executable):
|
||||||
meta = default_meta(tmp_file_path, original_file_name, original_file_extension)
|
meta = default_meta(tmp_file_path, original_file_name, original_file_extension)
|
||||||
@ -84,6 +91,8 @@ def process(tmp_file_path, original_file_name, original_file_extension, rar_exec
|
|||||||
original_file_name,
|
original_file_name,
|
||||||
original_file_extension,
|
original_file_extension,
|
||||||
rar_executable)
|
rar_executable)
|
||||||
|
elif extension_upper in [".MP3", ".OGG", ".FLAC", ".WAV", ".AAC", ".AIFF", ".ASF", ".MP4"] and use_audio_meta:
|
||||||
|
meta = audio.get_audio_file_info(tmp_file_path, original_file_extension, original_file_name)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.warning('cannot parse metadata, using default: %s', ex)
|
log.warning('cannot parse metadata, using default: %s', ex)
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ python-dateutil>=2.1,<2.10.0
|
|||||||
beautifulsoup4>=4.0.1,<4.13.0
|
beautifulsoup4>=4.0.1,<4.13.0
|
||||||
faust-cchardet>=2.1.18,<2.1.20
|
faust-cchardet>=2.1.18,<2.1.20
|
||||||
py7zr>=0.15.0,<0.21.0
|
py7zr>=0.15.0,<0.21.0
|
||||||
|
mutagen>=1.40.0,<1.50.0
|
||||||
|
|
||||||
# Comics
|
# Comics
|
||||||
natsort>=2.2.0,<8.5.0
|
natsort>=2.2.0,<8.5.0
|
||||||
|
@ -13,7 +13,7 @@ Wand>=0.4.4,<0.7.0
|
|||||||
unidecode>=0.04.19,<1.4.0
|
unidecode>=0.04.19,<1.4.0
|
||||||
lxml>=4.9.1,<5.3.0
|
lxml>=4.9.1,<5.3.0
|
||||||
flask-wtf>=0.14.2,<1.3.0
|
flask-wtf>=0.14.2,<1.3.0
|
||||||
chardet>=3.0.0,<4.1.0
|
chardet>=3.0.0,<5.3.0
|
||||||
advocate>=1.0.0,<1.1.0
|
advocate>=1.0.0,<1.1.0
|
||||||
Flask-Limiter>=2.3.0,<3.9.0
|
Flask-Limiter>=2.3.0,<3.9.0
|
||||||
regex>=2022.3.2,<2024.6.25
|
regex>=2022.3.2,<2024.6.25
|
||||||
|
@ -53,7 +53,7 @@ install_requires =
|
|||||||
unidecode>=0.04.19,<1.4.0
|
unidecode>=0.04.19,<1.4.0
|
||||||
lxml>=4.9.1,<5.3.0
|
lxml>=4.9.1,<5.3.0
|
||||||
flask-wtf>=0.14.2,<1.3.0
|
flask-wtf>=0.14.2,<1.3.0
|
||||||
chardet>=3.0.0,<4.1.0
|
chardet>=3.0.0,<5.3.0
|
||||||
advocate>=1.0.0,<1.1.0
|
advocate>=1.0.0,<1.1.0
|
||||||
Flask-Limiter>=2.3.0,<3.9.0
|
Flask-Limiter>=2.3.0,<3.9.0
|
||||||
regex>=2022.3.2,<2024.6.25
|
regex>=2022.3.2,<2024.6.25
|
||||||
@ -100,6 +100,7 @@ metadata =
|
|||||||
beautifulsoup4>=4.0.1,<4.13.0
|
beautifulsoup4>=4.0.1,<4.13.0
|
||||||
faust-cchardet>=2.1.18,<2.1.20
|
faust-cchardet>=2.1.18,<2.1.20
|
||||||
py7zr>=0.15.0,<0.21.0
|
py7zr>=0.15.0,<0.21.0
|
||||||
|
mutagen>=1.40.0,<1.50.0
|
||||||
comics =
|
comics =
|
||||||
natsort>=2.2.0,<8.5.0
|
natsort>=2.2.0,<8.5.0
|
||||||
comicapi>=2.2.0,<3.3.0
|
comicapi>=2.2.0,<3.3.0
|
||||||
|
Loading…
Reference in New Issue
Block a user