|
|
@ -21,7 +21,6 @@ from __future__ import division, print_function, unicode_literals
|
|
|
|
import sys
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import os
|
|
|
|
import io
|
|
|
|
import io
|
|
|
|
import json
|
|
|
|
|
|
|
|
import mimetypes
|
|
|
|
import mimetypes
|
|
|
|
import re
|
|
|
|
import re
|
|
|
|
import shutil
|
|
|
|
import shutil
|
|
|
@ -36,7 +35,7 @@ from babel.units import format_unit
|
|
|
|
from flask import send_from_directory, make_response, redirect, abort
|
|
|
|
from flask import send_from_directory, make_response, redirect, abort
|
|
|
|
from flask_babel import gettext as _
|
|
|
|
from flask_babel import gettext as _
|
|
|
|
from flask_login import current_user
|
|
|
|
from flask_login import current_user
|
|
|
|
from sqlalchemy.sql.expression import true, false, and_, or_, text, func
|
|
|
|
from sqlalchemy.sql.expression import true, false, and_, text, func
|
|
|
|
from werkzeug.datastructures import Headers
|
|
|
|
from werkzeug.datastructures import Headers
|
|
|
|
from werkzeug.security import generate_password_hash
|
|
|
|
from werkzeug.security import generate_password_hash
|
|
|
|
from . import calibre_db
|
|
|
|
from . import calibre_db
|
|
|
@ -60,10 +59,9 @@ try:
|
|
|
|
except ImportError:
|
|
|
|
except ImportError:
|
|
|
|
use_PIL = False
|
|
|
|
use_PIL = False
|
|
|
|
|
|
|
|
|
|
|
|
from . import logger, config, get_locale, db, ub, isoLanguages
|
|
|
|
from . import logger, config, get_locale, db, ub
|
|
|
|
from . import gdriveutils as gd
|
|
|
|
from . import gdriveutils as gd
|
|
|
|
from .constants import STATIC_DIR as _STATIC_DIR
|
|
|
|
from .constants import STATIC_DIR as _STATIC_DIR
|
|
|
|
from .pagination import Pagination
|
|
|
|
|
|
|
|
from .subproc_wrapper import process_wait
|
|
|
|
from .subproc_wrapper import process_wait
|
|
|
|
from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS
|
|
|
|
from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS
|
|
|
|
from .tasks.email import TaskEmail
|
|
|
|
from .tasks.email import TaskEmail
|
|
|
@ -240,22 +238,22 @@ def get_valid_filename(value, replace_whitespace=True):
|
|
|
|
value = value[:-1]+u'_'
|
|
|
|
value = value[:-1]+u'_'
|
|
|
|
value = value.replace("/", "_").replace(":", "_").strip('\0')
|
|
|
|
value = value.replace("/", "_").replace(":", "_").strip('\0')
|
|
|
|
if use_unidecode:
|
|
|
|
if use_unidecode:
|
|
|
|
value = (unidecode.unidecode(value)).strip()
|
|
|
|
value = (unidecode.unidecode(value))
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
value = value.replace(u'§', u'SS')
|
|
|
|
value = value.replace(u'§', u'SS')
|
|
|
|
value = value.replace(u'ß', u'ss')
|
|
|
|
value = value.replace(u'ß', u'ss')
|
|
|
|
value = unicodedata.normalize('NFKD', value)
|
|
|
|
value = unicodedata.normalize('NFKD', value)
|
|
|
|
re_slugify = re.compile(r'[\W\s-]', re.UNICODE)
|
|
|
|
re_slugify = re.compile(r'[\W\s-]', re.UNICODE)
|
|
|
|
if isinstance(value, str): # Python3 str, Python2 unicode
|
|
|
|
if isinstance(value, str): # Python3 str, Python2 unicode
|
|
|
|
value = re_slugify.sub('', value).strip()
|
|
|
|
value = re_slugify.sub('', value)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
value = unicode(re_slugify.sub('', value).strip())
|
|
|
|
value = unicode(re_slugify.sub('', value))
|
|
|
|
if replace_whitespace:
|
|
|
|
if replace_whitespace:
|
|
|
|
# *+:\"/<>? are replaced by _
|
|
|
|
# *+:\"/<>? are replaced by _
|
|
|
|
value = re.sub(r'[\*\+:\\\"/<>\?]+', u'_', value, flags=re.U)
|
|
|
|
value = re.sub(r'[*+:\\\"/<>?]+', u'_', value, flags=re.U)
|
|
|
|
# pipe has to be replaced with comma
|
|
|
|
# pipe has to be replaced with comma
|
|
|
|
value = re.sub(r'[\|]+', u',', value, flags=re.U)
|
|
|
|
value = re.sub(r'[|]+', u',', value, flags=re.U)
|
|
|
|
value = value[:128]
|
|
|
|
value = value[:128].strip()
|
|
|
|
if not value:
|
|
|
|
if not value:
|
|
|
|
raise ValueError("Filename cannot be empty")
|
|
|
|
raise ValueError("Filename cannot be empty")
|
|
|
|
if sys.version_info.major == 3:
|
|
|
|
if sys.version_info.major == 3:
|
|
|
@ -264,6 +262,22 @@ def get_valid_filename(value, replace_whitespace=True):
|
|
|
|
return value.decode('utf-8')
|
|
|
|
return value.decode('utf-8')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def split_authors(values):
|
|
|
|
|
|
|
|
authors_list = []
|
|
|
|
|
|
|
|
for value in values:
|
|
|
|
|
|
|
|
authors = re.split('[&;]', value)
|
|
|
|
|
|
|
|
for author in authors:
|
|
|
|
|
|
|
|
commas = author.count(',')
|
|
|
|
|
|
|
|
if commas == 1:
|
|
|
|
|
|
|
|
author_split = author.split(',')
|
|
|
|
|
|
|
|
authors_list.append(author_split[1].strip() + ' ' + author_split[0].strip())
|
|
|
|
|
|
|
|
elif commas > 1:
|
|
|
|
|
|
|
|
authors_list.extend([x.strip() for x in author.split(',')])
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
authors_list.append(author.strip())
|
|
|
|
|
|
|
|
return authors_list
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_sorted_author(value):
|
|
|
|
def get_sorted_author(value):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
if ',' not in value:
|
|
|
|
if ',' not in value:
|
|
|
@ -271,7 +285,10 @@ def get_sorted_author(value):
|
|
|
|
combined = "(" + ")|(".join(regexes) + ")"
|
|
|
|
combined = "(" + ")|(".join(regexes) + ")"
|
|
|
|
value = value.split(" ")
|
|
|
|
value = value.split(" ")
|
|
|
|
if re.match(combined, value[-1].upper()):
|
|
|
|
if re.match(combined, value[-1].upper()):
|
|
|
|
|
|
|
|
if len(value) > 1:
|
|
|
|
value2 = value[-2] + ", " + " ".join(value[:-2]) + " " + value[-1]
|
|
|
|
value2 = value[-2] + ", " + " ".join(value[:-2]) + " " + value[-1]
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
value2 = value[0]
|
|
|
|
elif len(value) == 1:
|
|
|
|
elif len(value) == 1:
|
|
|
|
value2 = value[0]
|
|
|
|
value2 = value[0]
|
|
|
|
else:
|
|
|
|
else:
|
|
|
@ -280,6 +297,9 @@ def get_sorted_author(value):
|
|
|
|
value2 = value
|
|
|
|
value2 = value
|
|
|
|
except Exception as ex:
|
|
|
|
except Exception as ex:
|
|
|
|
log.error("Sorting author %s failed: %s", value, ex)
|
|
|
|
log.error("Sorting author %s failed: %s", value, ex)
|
|
|
|
|
|
|
|
if isinstance(list, value2):
|
|
|
|
|
|
|
|
value2 = value[0]
|
|
|
|
|
|
|
|
else:
|
|
|
|
value2 = value
|
|
|
|
value2 = value
|
|
|
|
return value2
|
|
|
|
return value2
|
|
|
|
|
|
|
|
|
|
|
@ -367,6 +387,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
|
|
|
|
src=path, dest=new_author_path, error=str(ex))
|
|
|
|
src=path, dest=new_author_path, error=str(ex))
|
|
|
|
# Rename all files from old names to new names
|
|
|
|
# Rename all files from old names to new names
|
|
|
|
if authordir != new_authordir or titledir != new_titledir:
|
|
|
|
if authordir != new_authordir or titledir != new_titledir:
|
|
|
|
|
|
|
|
new_name = ""
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
new_name = get_valid_filename(localbook.title) + ' - ' + get_valid_filename(new_authordir)
|
|
|
|
new_name = get_valid_filename(localbook.title) + ' - ' + get_valid_filename(new_authordir)
|
|
|
|
path_name = os.path.join(calibrepath, new_authordir, os.path.basename(path))
|
|
|
|
path_name = os.path.join(calibrepath, new_authordir, os.path.basename(path))
|
|
|
@ -475,14 +496,14 @@ def generate_random_password():
|
|
|
|
return "".join(s[c % len(s)] for c in os.urandom(passlen))
|
|
|
|
return "".join(s[c % len(s)] for c in os.urandom(passlen))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def uniq(input):
|
|
|
|
def uniq(inpt):
|
|
|
|
output = []
|
|
|
|
output = []
|
|
|
|
for x in input:
|
|
|
|
for x in inpt:
|
|
|
|
if x not in output:
|
|
|
|
if x not in output:
|
|
|
|
output.append(x)
|
|
|
|
output.append(x)
|
|
|
|
return output
|
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
|
|
################################## External interface
|
|
|
|
# ################################# External interface #################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_dir_stucture(book_id, calibrepath, first_author=None):
|
|
|
|
def update_dir_stucture(book_id, calibrepath, first_author=None):
|
|
|
@ -559,7 +580,6 @@ def save_cover_from_url(url, book_path):
|
|
|
|
return False, _("Cover Format Error")
|
|
|
|
return False, _("Cover Format Error")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_cover_from_filestorage(filepath, saved_filename, img):
|
|
|
|
def save_cover_from_filestorage(filepath, saved_filename, img):
|
|
|
|
if hasattr(img, '_content'):
|
|
|
|
if hasattr(img, '_content'):
|
|
|
|
f = open(os.path.join(filepath, saved_filename), "wb")
|
|
|
|
f = open(os.path.join(filepath, saved_filename), "wb")
|
|
|
@ -618,7 +638,6 @@ def save_cover(img, book_path):
|
|
|
|
return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img)
|
|
|
|
return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def do_download_file(book, book_format, client, data, headers):
|
|
|
|
def do_download_file(book, book_format, client, data, headers):
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
startTime = time.time()
|
|
|
|
startTime = time.time()
|
|
|
@ -771,6 +790,7 @@ def get_cc_columns(filter_config_custom_read=False):
|
|
|
|
|
|
|
|
|
|
|
|
return cc
|
|
|
|
return cc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_download_link(book_id, book_format, client):
|
|
|
|
def get_download_link(book_id, book_format, client):
|
|
|
|
book_format = book_format.split(".")[0]
|
|
|
|
book_format = book_format.split(".")[0]
|
|
|
|
book = calibre_db.get_filtered_book(book_id)
|
|
|
|
book = calibre_db.get_filtered_book(book_id)
|
|
|
|