Merge branch 'master' of https://github.com/janeczku/calibre-web into 621
This commit is contained in:
		
						commit
						93a9f65198
					
				| 
						 | 
					@ -124,8 +124,10 @@ def pdf_preview(tmp_file_path, tmp_dir):
 | 
				
			||||||
def get_versions():
 | 
					def get_versions():
 | 
				
			||||||
    if not use_generic_pdf_cover:
 | 
					    if not use_generic_pdf_cover:
 | 
				
			||||||
        IVersion = ImageVersion.MAGICK_VERSION
 | 
					        IVersion = ImageVersion.MAGICK_VERSION
 | 
				
			||||||
 | 
					        WVersion = ImageVersion.VERSION
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        IVersion = _(u'not installed')
 | 
					        IVersion = _(u'not installed')
 | 
				
			||||||
 | 
					        WVersion = _(u'not installed')
 | 
				
			||||||
    if use_pdf_meta:
 | 
					    if use_pdf_meta:
 | 
				
			||||||
        PVersion='v'+PyPdfVersion
 | 
					        PVersion='v'+PyPdfVersion
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
| 
						 | 
					@ -134,4 +136,4 @@ def get_versions():
 | 
				
			||||||
        XVersion = 'v'+'.'.join(map(str, lxmlversion))
 | 
					        XVersion = 'v'+'.'.join(map(str, lxmlversion))
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        XVersion = _(u'not installed')
 | 
					        XVersion = _(u'not installed')
 | 
				
			||||||
    return {'Image Magick': IVersion, 'PyPdf': PVersion, 'lxml':XVersion}
 | 
					    return {'Image Magick': IVersion, 'PyPdf': PVersion, 'lxml':XVersion, 'Wand Version': WVersion}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,5 +45,5 @@ def versioncheck():
 | 
				
			||||||
    elif ub.config.config_ebookconverter == 2:
 | 
					    elif ub.config.config_ebookconverter == 2:
 | 
				
			||||||
        return versionCalibre()
 | 
					        return versionCalibre()
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return {'ebook_converter':''}
 | 
					        return {'ebook_converter':_(u'not configured')}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -149,19 +149,19 @@ def getDrive(drive=None, gauth=None):
 | 
				
			||||||
        drive.auth.Refresh()
 | 
					        drive.auth.Refresh()
 | 
				
			||||||
    return drive
 | 
					    return drive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def listRootFolders(drive=None):
 | 
					def listRootFolders():
 | 
				
			||||||
    drive = getDrive(drive)
 | 
					    drive = getDrive(Gdrive.Instance().drive)
 | 
				
			||||||
    folder = "'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false"
 | 
					    folder = "'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false"
 | 
				
			||||||
    fileList = drive.ListFile({'q': folder}).GetList()
 | 
					    fileList = drive.ListFile({'q': folder}).GetList()
 | 
				
			||||||
    return fileList
 | 
					    return fileList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getEbooksFolder(drive=None):
 | 
					def getEbooksFolder(drive):
 | 
				
			||||||
    return getFolderInFolder('root',config.config_google_drive_folder,drive)
 | 
					    return getFolderInFolder('root',config.config_google_drive_folder,drive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getFolderInFolder(parentId, folderName,drive=None):
 | 
					def getFolderInFolder(parentId, folderName, drive):
 | 
				
			||||||
    drive = getDrive(drive)
 | 
					    # drive = getDrive(drive)
 | 
				
			||||||
    query=""
 | 
					    query=""
 | 
				
			||||||
    if folderName:
 | 
					    if folderName:
 | 
				
			||||||
        query = "title = '%s' and " % folderName.replace("'", "\\'")
 | 
					        query = "title = '%s' and " % folderName.replace("'", "\\'")
 | 
				
			||||||
| 
						 | 
					@ -190,7 +190,6 @@ def getEbooksFolderId(drive=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getFile(pathId, fileName, drive):
 | 
					def getFile(pathId, fileName, drive):
 | 
				
			||||||
    # drive = getDrive(Gdrive.Instance().drive)
 | 
					 | 
				
			||||||
    metaDataFile = "'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", "\\'"))
 | 
					    metaDataFile = "'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", "\\'"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fileList = drive.ListFile({'q': metaDataFile}).GetList()
 | 
					    fileList = drive.ListFile({'q': metaDataFile}).GetList()
 | 
				
			||||||
| 
						 | 
					@ -200,8 +199,8 @@ def getFile(pathId, fileName, drive):
 | 
				
			||||||
        return fileList[0]
 | 
					        return fileList[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getFolderId(path, drive=None):
 | 
					def getFolderId(path, drive):
 | 
				
			||||||
    drive = getDrive(drive)
 | 
					    # drive = getDrive(drive)
 | 
				
			||||||
    currentFolderId = getEbooksFolderId(drive)
 | 
					    currentFolderId = getEbooksFolderId(drive)
 | 
				
			||||||
    sqlCheckPath = path if path[-1] == '/' else path + '/'
 | 
					    sqlCheckPath = path if path[-1] == '/' else path + '/'
 | 
				
			||||||
    storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first()
 | 
					    storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first()
 | 
				
			||||||
| 
						 | 
					@ -249,7 +248,7 @@ def getFileFromEbooksFolder(path, fileName):
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def copyDriveFileRemote(drive, origin_file_id, copy_title):
 | 
					'''def copyDriveFileRemote(drive, origin_file_id, copy_title):
 | 
				
			||||||
    drive = getDrive(drive)
 | 
					    drive = getDrive(drive)
 | 
				
			||||||
    copied_file = {'title': copy_title}
 | 
					    copied_file = {'title': copy_title}
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
| 
						 | 
					@ -258,7 +257,7 @@ def copyDriveFileRemote(drive, origin_file_id, copy_title):
 | 
				
			||||||
        return drive.CreateFile({'id': file_data['id']})
 | 
					        return drive.CreateFile({'id': file_data['id']})
 | 
				
			||||||
    except errors.HttpError as error:
 | 
					    except errors.HttpError as error:
 | 
				
			||||||
        print ('An error occurred: %s' % error)
 | 
					        print ('An error occurred: %s' % error)
 | 
				
			||||||
    return None
 | 
					    return None'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Download metadata.db from gdrive
 | 
					# Download metadata.db from gdrive
 | 
				
			||||||
| 
						 | 
					@ -347,7 +346,6 @@ def uploadFileToEbooksFolder(destFile, f):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def watchChange(drive, channel_id, channel_type, channel_address,
 | 
					def watchChange(drive, channel_id, channel_type, channel_address,
 | 
				
			||||||
              channel_token=None, expiration=None):
 | 
					              channel_token=None, expiration=None):
 | 
				
			||||||
    # drive = getDrive(drive)
 | 
					 | 
				
			||||||
    # Watch for all changes to a user's Drive.
 | 
					    # Watch for all changes to a user's Drive.
 | 
				
			||||||
    # Args:
 | 
					    # Args:
 | 
				
			||||||
    # service: Drive API service instance.
 | 
					    # service: Drive API service instance.
 | 
				
			||||||
| 
						 | 
					@ -390,8 +388,6 @@ def watchFile(drive, file_id, channel_id, channel_type, channel_address,
 | 
				
			||||||
    Raises:
 | 
					    Raises:
 | 
				
			||||||
    apiclient.errors.HttpError: if http request to create channel fails.
 | 
					    apiclient.errors.HttpError: if http request to create channel fails.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    # drive = getDrive(drive)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    body = {
 | 
					    body = {
 | 
				
			||||||
        'id': channel_id,
 | 
					        'id': channel_id,
 | 
				
			||||||
        'type': channel_type,
 | 
					        'type': channel_type,
 | 
				
			||||||
| 
						 | 
					@ -413,8 +409,6 @@ def stopChannel(drive, channel_id, resource_id):
 | 
				
			||||||
    Raises:
 | 
					    Raises:
 | 
				
			||||||
    apiclient.errors.HttpError: if http request to create channel fails.
 | 
					    apiclient.errors.HttpError: if http request to create channel fails.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    # drive = getDrive(drive)
 | 
					 | 
				
			||||||
    # service=drive.auth.service
 | 
					 | 
				
			||||||
    body = {
 | 
					    body = {
 | 
				
			||||||
        'id': channel_id,
 | 
					        'id': channel_id,
 | 
				
			||||||
        'resourceId': resource_id
 | 
					        'resourceId': resource_id
 | 
				
			||||||
| 
						 | 
					@ -423,7 +417,6 @@ def stopChannel(drive, channel_id, resource_id):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getChangeById (drive, change_id):
 | 
					def getChangeById (drive, change_id):
 | 
				
			||||||
    # drive = getDrive(drive)
 | 
					 | 
				
			||||||
    # Print a single Change resource information.
 | 
					    # Print a single Change resource information.
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
    # Args:
 | 
					    # Args:
 | 
				
			||||||
| 
						 | 
					@ -454,11 +447,13 @@ def updateDatabaseOnEdit(ID,newPath):
 | 
				
			||||||
        storedPathName.path = newPath
 | 
					        storedPathName.path = newPath
 | 
				
			||||||
        session.commit()
 | 
					        session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Deletes the hashes in database of deleted book
 | 
					# Deletes the hashes in database of deleted book
 | 
				
			||||||
def deleteDatabaseEntry(ID):
 | 
					def deleteDatabaseEntry(ID):
 | 
				
			||||||
    session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete()
 | 
					    session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete()
 | 
				
			||||||
    session.commit()
 | 
					    session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Gets cover file from gdrive
 | 
					# Gets cover file from gdrive
 | 
				
			||||||
def get_cover_via_gdrive(cover_path):
 | 
					def get_cover_via_gdrive(cover_path):
 | 
				
			||||||
    df = getFileFromEbooksFolder(cover_path, 'cover.jpg')
 | 
					    df = getFileFromEbooksFolder(cover_path, 'cover.jpg')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										39
									
								
								cps/reverseproxy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								cps/reverseproxy.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReverseProxied(object):
 | 
				
			||||||
 | 
					    """Wrap the application in this middleware and configure the
 | 
				
			||||||
 | 
					    front-end server to add these headers, to let you quietly bind
 | 
				
			||||||
 | 
					    this to a URL other than / and to an HTTP scheme that is
 | 
				
			||||||
 | 
					    different than what is used locally.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Code courtesy of: http://flask.pocoo.org/snippets/35/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    In nginx:
 | 
				
			||||||
 | 
					    location /myprefix {
 | 
				
			||||||
 | 
					        proxy_pass http://127.0.0.1:8083;
 | 
				
			||||||
 | 
					        proxy_set_header Host $host;
 | 
				
			||||||
 | 
					        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
				
			||||||
 | 
					        proxy_set_header X-Scheme $scheme;
 | 
				
			||||||
 | 
					        proxy_set_header X-Script-Name /myprefix;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, application):
 | 
				
			||||||
 | 
					        self.app = application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __call__(self, environ, start_response):
 | 
				
			||||||
 | 
					        script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
 | 
				
			||||||
 | 
					        if script_name:
 | 
				
			||||||
 | 
					            environ['SCRIPT_NAME'] = script_name
 | 
				
			||||||
 | 
					            path_info = environ.get('PATH_INFO', '')
 | 
				
			||||||
 | 
					            if path_info and path_info.startswith(script_name):
 | 
				
			||||||
 | 
					                environ['PATH_INFO'] = path_info[len(script_name):]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        scheme = environ.get('HTTP_X_SCHEME', '')
 | 
				
			||||||
 | 
					        if scheme:
 | 
				
			||||||
 | 
					            environ['wsgi.url_scheme'] = scheme
 | 
				
			||||||
 | 
					        servr = environ.get('HTTP_X_FORWARDED_SERVER', '')
 | 
				
			||||||
 | 
					        if servr:
 | 
				
			||||||
 | 
					            environ['HTTP_HOST'] = servr
 | 
				
			||||||
 | 
					        return self.app(environ, start_response)
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,11 @@
 | 
				
			||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
# -*- coding: utf-8 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
from socket import error as SocketError
 | 
					from socket import error as SocketError
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import signal
 | 
					import signal
 | 
				
			||||||
 | 
					import web
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    from gevent.pywsgi import WSGIServer
 | 
					    from gevent.pywsgi import WSGIServer
 | 
				
			||||||
| 
						 | 
					@ -19,8 +19,6 @@ except ImportError:
 | 
				
			||||||
    from tornado import version as tornadoVersion
 | 
					    from tornado import version as tornadoVersion
 | 
				
			||||||
    gevent_present = False
 | 
					    gevent_present = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import web
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class server:
 | 
					class server:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,7 +66,8 @@ class server:
 | 
				
			||||||
                        ssl_options=ssl)
 | 
					                        ssl_options=ssl)
 | 
				
			||||||
            http_server.listen(web.ub.config.config_port)
 | 
					            http_server.listen(web.ub.config.config_port)
 | 
				
			||||||
            self.wsgiserver=IOLoop.instance()
 | 
					            self.wsgiserver=IOLoop.instance()
 | 
				
			||||||
            self.wsgiserver.start()     # wait for stop signal
 | 
					            self.wsgiserver.start()
 | 
				
			||||||
 | 
					            # wait for stop signal
 | 
				
			||||||
            self.wsgiserver.close(True)
 | 
					            self.wsgiserver.close(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.restart == True:
 | 
					        if self.restart == True:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1173,7 +1173,7 @@ msgstr "Pfad zu Konvertertool"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: cps/templates/config_edit.html:199
 | 
					#: cps/templates/config_edit.html:199
 | 
				
			||||||
msgid "Location of Unrar binary"
 | 
					msgid "Location of Unrar binary"
 | 
				
			||||||
msgstr "Ofad zum UnRar Programm"
 | 
					msgstr "Pfad zum UnRar Programm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: cps/templates/config_edit.html:215 cps/templates/layout.html:82
 | 
					#: cps/templates/config_edit.html:215 cps/templates/layout.html:82
 | 
				
			||||||
#: cps/templates/login.html:4
 | 
					#: cps/templates/login.html:4
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										548
									
								
								cps/web.py
									
									
									
									
									
								
							
							
						
						
									
										548
									
								
								cps/web.py
									
									
									
									
									
								
							| 
						 | 
					@ -1,5 +1,60 @@
 | 
				
			||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
# -*- coding: utf-8 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import mimetypes
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					from logging.handlers import RotatingFileHandler
 | 
				
			||||||
 | 
					from flask import (Flask, render_template, request, Response, redirect,
 | 
				
			||||||
 | 
					                   url_for, send_from_directory, make_response, g, flash,
 | 
				
			||||||
 | 
					                   abort, Markup)
 | 
				
			||||||
 | 
					from flask import __version__ as flaskVersion
 | 
				
			||||||
 | 
					from werkzeug import __version__ as werkzeugVersion
 | 
				
			||||||
 | 
					from jinja2 import __version__  as jinja2Version
 | 
				
			||||||
 | 
					import cache_buster
 | 
				
			||||||
 | 
					import ub
 | 
				
			||||||
 | 
					from ub import config
 | 
				
			||||||
 | 
					import helper
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					from sqlalchemy.sql.expression import func
 | 
				
			||||||
 | 
					from sqlalchemy.sql.expression import false
 | 
				
			||||||
 | 
					from sqlalchemy.exc import IntegrityError
 | 
				
			||||||
 | 
					from sqlalchemy import __version__ as sqlalchemyVersion
 | 
				
			||||||
 | 
					from math import ceil
 | 
				
			||||||
 | 
					from flask_login import (LoginManager, login_user, logout_user,
 | 
				
			||||||
 | 
					                         login_required, current_user)
 | 
				
			||||||
 | 
					from flask_principal import Principal
 | 
				
			||||||
 | 
					from flask_principal import __version__ as flask_principalVersion
 | 
				
			||||||
 | 
					from flask_babel import Babel
 | 
				
			||||||
 | 
					from flask_babel import gettext as _
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					from werkzeug.security import generate_password_hash, check_password_hash
 | 
				
			||||||
 | 
					from werkzeug.datastructures import Headers
 | 
				
			||||||
 | 
					from babel import Locale as LC
 | 
				
			||||||
 | 
					from babel import negotiate_locale
 | 
				
			||||||
 | 
					from babel import __version__ as babelVersion
 | 
				
			||||||
 | 
					from babel.dates import format_date, format_datetime
 | 
				
			||||||
 | 
					from babel.core import UnknownLocaleError
 | 
				
			||||||
 | 
					from functools import wraps
 | 
				
			||||||
 | 
					import base64
 | 
				
			||||||
 | 
					from sqlalchemy.sql import *
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					from iso639 import languages as isoLanguages
 | 
				
			||||||
 | 
					from iso639 import __version__ as iso639Version
 | 
				
			||||||
 | 
					from pytz import __version__ as pytzVersion
 | 
				
			||||||
 | 
					from uuid import uuid4
 | 
				
			||||||
 | 
					import os.path
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import db
 | 
				
			||||||
 | 
					from shutil import move, copyfile
 | 
				
			||||||
 | 
					import gdriveutils
 | 
				
			||||||
 | 
					import converter
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
 | 
					from redirect import redirect_back
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import server
 | 
				
			||||||
 | 
					from reverseproxy import ReverseProxied
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    from googleapiclient.errors import HttpError
 | 
					    from googleapiclient.errors import HttpError
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
| 
						 | 
					@ -33,58 +88,6 @@ try:
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
    sort=sorted # Just use regular sort then
 | 
					    sort=sorted # Just use regular sort then
 | 
				
			||||||
                #   may cause issues with badly named pages in cbz/cbr files
 | 
					                #   may cause issues with badly named pages in cbz/cbr files
 | 
				
			||||||
 | 
					 | 
				
			||||||
import mimetypes
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
from logging.handlers import RotatingFileHandler
 | 
					 | 
				
			||||||
from flask import (Flask, render_template, request, Response, redirect,
 | 
					 | 
				
			||||||
                   url_for, send_from_directory, make_response, g, flash,
 | 
					 | 
				
			||||||
                   abort, Markup)
 | 
					 | 
				
			||||||
from flask import __version__ as flaskVersion
 | 
					 | 
				
			||||||
import cache_buster
 | 
					 | 
				
			||||||
import ub
 | 
					 | 
				
			||||||
from ub import config
 | 
					 | 
				
			||||||
import helper
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
from sqlalchemy.sql.expression import func
 | 
					 | 
				
			||||||
from sqlalchemy.sql.expression import false
 | 
					 | 
				
			||||||
from sqlalchemy.exc import IntegrityError
 | 
					 | 
				
			||||||
from sqlalchemy import __version__ as sqlalchemyVersion
 | 
					 | 
				
			||||||
from math import ceil
 | 
					 | 
				
			||||||
from flask_login import (LoginManager, login_user, logout_user,
 | 
					 | 
				
			||||||
                         login_required, current_user)
 | 
					 | 
				
			||||||
from flask_principal import Principal
 | 
					 | 
				
			||||||
from flask_principal import __version__ as flask_principalVersion
 | 
					 | 
				
			||||||
from flask_babel import Babel
 | 
					 | 
				
			||||||
from flask_babel import gettext as _
 | 
					 | 
				
			||||||
import requests
 | 
					 | 
				
			||||||
from werkzeug.security import generate_password_hash, check_password_hash
 | 
					 | 
				
			||||||
from werkzeug.datastructures import Headers
 | 
					 | 
				
			||||||
from babel import Locale as LC
 | 
					 | 
				
			||||||
from babel import negotiate_locale
 | 
					 | 
				
			||||||
from babel import __version__ as babelVersion
 | 
					 | 
				
			||||||
from babel.dates import format_date, format_datetime
 | 
					 | 
				
			||||||
from babel.core import UnknownLocaleError
 | 
					 | 
				
			||||||
from functools import wraps
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
from sqlalchemy.sql import *
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import datetime
 | 
					 | 
				
			||||||
from iso639 import languages as isoLanguages
 | 
					 | 
				
			||||||
from iso639 import __version__ as iso639Version
 | 
					 | 
				
			||||||
from uuid import uuid4
 | 
					 | 
				
			||||||
import os.path
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import db
 | 
					 | 
				
			||||||
from shutil import move, copyfile
 | 
					 | 
				
			||||||
import gdriveutils
 | 
					 | 
				
			||||||
import converter
 | 
					 | 
				
			||||||
import tempfile
 | 
					 | 
				
			||||||
import hashlib
 | 
					 | 
				
			||||||
from redirect import redirect_back
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
import server
 | 
					 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    import cPickle
 | 
					    import cPickle
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
| 
						 | 
					@ -101,12 +104,10 @@ try:
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
    from flask_login.__about__ import __version__ as flask_loginVersion
 | 
					    from flask_login.__about__ import __version__ as flask_loginVersion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
current_milli_time = lambda: int(round(time.time() * 1000))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Global variables
 | 
					# Global variables
 | 
				
			||||||
 | 
					current_milli_time = lambda: int(round(time.time() * 1000))
 | 
				
			||||||
gdrive_watch_callback_token = 'target=calibreweb-watch_files'
 | 
					gdrive_watch_callback_token = 'target=calibreweb-watch_files'
 | 
				
			||||||
 | 
					 | 
				
			||||||
EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
 | 
					EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
 | 
				
			||||||
                      'fb2', 'html', 'rtf', 'odt'}
 | 
					                      'fb2', 'html', 'rtf', 'odt'}
 | 
				
			||||||
EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'html', 'rtf', 'odt'}
 | 
					EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'html', 'rtf', 'odt'}
 | 
				
			||||||
| 
						 | 
					@ -114,15 +115,7 @@ EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit'
 | 
				
			||||||
# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] + (['rar','cbr'] if rar_support else []))
 | 
					# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] + (['rar','cbr'] if rar_support else []))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def md5(fname):
 | 
					'''class ReverseProxied(object):
 | 
				
			||||||
    hash_md5 = hashlib.md5()
 | 
					 | 
				
			||||||
    with open(fname, "rb") as f:
 | 
					 | 
				
			||||||
        for chunk in iter(lambda: f.read(4096), b""):
 | 
					 | 
				
			||||||
            hash_md5.update(chunk)
 | 
					 | 
				
			||||||
    return hash_md5.hexdigest()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ReverseProxied(object):
 | 
					 | 
				
			||||||
    """Wrap the application in this middleware and configure the
 | 
					    """Wrap the application in this middleware and configure the
 | 
				
			||||||
    front-end server to add these headers, to let you quietly bind
 | 
					    front-end server to add these headers, to let you quietly bind
 | 
				
			||||||
    this to a URL other than / and to an HTTP scheme that is
 | 
					    this to a URL other than / and to an HTTP scheme that is
 | 
				
			||||||
| 
						 | 
					@ -157,7 +150,7 @@ class ReverseProxied(object):
 | 
				
			||||||
        servr = environ.get('HTTP_X_FORWARDED_SERVER', '')
 | 
					        servr = environ.get('HTTP_X_FORWARDED_SERVER', '')
 | 
				
			||||||
        if servr:
 | 
					        if servr:
 | 
				
			||||||
            environ['HTTP_HOST'] = servr
 | 
					            environ['HTTP_HOST'] = servr
 | 
				
			||||||
        return self.app(environ, start_response)
 | 
					        return self.app(environ, start_response)'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Main code
 | 
					# Main code
 | 
				
			||||||
| 
						 | 
					@ -1668,10 +1661,14 @@ def stats():
 | 
				
			||||||
    versions = uploader.book_formats.get_versions()
 | 
					    versions = uploader.book_formats.get_versions()
 | 
				
			||||||
    versions['Babel'] = 'v' + babelVersion
 | 
					    versions['Babel'] = 'v' + babelVersion
 | 
				
			||||||
    versions['Sqlalchemy'] = 'v' + sqlalchemyVersion
 | 
					    versions['Sqlalchemy'] = 'v' + sqlalchemyVersion
 | 
				
			||||||
 | 
					    versions['Werkzeug'] = 'v' + werkzeugVersion
 | 
				
			||||||
 | 
					    versions['Jinja2'] = 'v' + jinja2Version
 | 
				
			||||||
    versions['Flask'] = 'v' + flaskVersion
 | 
					    versions['Flask'] = 'v' + flaskVersion
 | 
				
			||||||
    versions['Flask Login'] = 'v' + flask_loginVersion
 | 
					    versions['Flask Login'] = 'v' + flask_loginVersion
 | 
				
			||||||
    versions['Flask Principal'] = 'v' + flask_principalVersion
 | 
					    versions['Flask Principal'] = 'v' + flask_principalVersion
 | 
				
			||||||
    versions['Iso 639'] = 'v' + iso639Version
 | 
					    versions['Iso 639'] = 'v' + iso639Version
 | 
				
			||||||
 | 
					    versions['pytz'] = 'v' + pytzVersion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    versions['Requests'] = 'v' + requests.__version__
 | 
					    versions['Requests'] = 'v' + requests.__version__
 | 
				
			||||||
    versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version
 | 
					    versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version
 | 
				
			||||||
    versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version
 | 
					    versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version
 | 
				
			||||||
| 
						 | 
					@ -3359,24 +3356,19 @@ def reset_password(user_id):
 | 
				
			||||||
    return redirect(url_for('admin'))
 | 
					    return redirect(url_for('admin'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route("/admin/book/<int:book_id>", methods=['GET', 'POST'])
 | 
					def render_edit_book(book_id):
 | 
				
			||||||
@login_required_if_no_ano
 | 
					 | 
				
			||||||
@edit_required
 | 
					 | 
				
			||||||
def edit_book(book_id):
 | 
					 | 
				
			||||||
    # create the function for sorting...
 | 
					 | 
				
			||||||
    db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
 | 
					    db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
 | 
				
			||||||
    cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
 | 
					    cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
 | 
				
			||||||
    book = db.session.query(db.Books)\
 | 
					    book = db.session.query(db.Books)\
 | 
				
			||||||
        .filter(db.Books.id == book_id).filter(common_filters()).first()
 | 
					        .filter(db.Books.id == book_id).filter(common_filters()).first()
 | 
				
			||||||
    author_names = []
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Book not found
 | 
					 | 
				
			||||||
    if not book:
 | 
					    if not book:
 | 
				
			||||||
        flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")
 | 
					        flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")
 | 
				
			||||||
        return redirect(url_for("index"))
 | 
					        return redirect(url_for("index"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for indx in range(0, len(book.languages)):
 | 
					    for indx in range(0, len(book.languages)):
 | 
				
			||||||
        book.languages[indx].language_name = language_table[get_locale()][book.languages[indx].lang_code]
 | 
					        book.languages[indx].language_name = language_table[get_locale()][book.languages[indx].lang_code]
 | 
				
			||||||
 | 
					    author_names = []
 | 
				
			||||||
    for authr in book.authors:
 | 
					    for authr in book.authors:
 | 
				
			||||||
        author_names.append(authr.name.replace('|', ','))
 | 
					        author_names.append(authr.name.replace('|', ','))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3395,196 +3387,14 @@ def edit_book(book_id):
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            app.logger.warning(file.format.lower() + ' already removed from list.')
 | 
					            app.logger.warning(file.format.lower() + ' already removed from list.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app.logger.debug('Allowed conversion formats: '+ ', '.join(allowed_conversion_formats))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Show form
 | 
					 | 
				
			||||||
    if request.method != 'POST':
 | 
					 | 
				
			||||||
    return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
 | 
					    return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
 | 
				
			||||||
                                 title=_(u"edit metadata"), page="editbook",
 | 
					                                 title=_(u"edit metadata"), page="editbook",
 | 
				
			||||||
                                 conversion_formats=allowed_conversion_formats,
 | 
					                                 conversion_formats=allowed_conversion_formats,
 | 
				
			||||||
                                 source_formats=valid_source_formats)
 | 
					                                 source_formats=valid_source_formats)
 | 
				
			||||||
    # Check and handle Uploaded file
 | 
					 | 
				
			||||||
    if 'btn-upload-format' in request.files:
 | 
					 | 
				
			||||||
        requested_file = request.files['btn-upload-format']
 | 
					 | 
				
			||||||
        # check for empty request
 | 
					 | 
				
			||||||
        if requested_file.filename != '':
 | 
					 | 
				
			||||||
            if '.' in requested_file.filename:
 | 
					 | 
				
			||||||
                file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
 | 
					 | 
				
			||||||
                if file_ext not in EXTENSIONS_UPLOAD:
 | 
					 | 
				
			||||||
                    flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext),
 | 
					 | 
				
			||||||
                          category="error")
 | 
					 | 
				
			||||||
                    return redirect(url_for('show_book', book_id=book.id))
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                flash(_('File to be uploaded must have an extension'), category="error")
 | 
					 | 
				
			||||||
                return redirect(url_for('show_book', book_id=book.id))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            file_name = book.path.rsplit('/', 1)[-1]
 | 
					 | 
				
			||||||
            filepath = os.path.normpath(os.path.join(config.config_calibre_dir, book.path))
 | 
					 | 
				
			||||||
            saved_filename = os.path.join(filepath, file_name + '.' + file_ext)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # check if file path exists, otherwise create it, copy file to calibre path and delete temp file
 | 
					 | 
				
			||||||
            if not os.path.exists(filepath):
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    os.makedirs(filepath)
 | 
					 | 
				
			||||||
                except OSError:
 | 
					 | 
				
			||||||
                    flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
 | 
					 | 
				
			||||||
                    return redirect(url_for('show_book', book_id=book.id))
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                requested_file.save(saved_filename)
 | 
					 | 
				
			||||||
            except OSError:
 | 
					 | 
				
			||||||
                flash(_(u"Failed to store file %(file)s.", file=saved_filename), category="error")
 | 
					 | 
				
			||||||
                return redirect(url_for('show_book', book_id=book.id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            file_size = os.path.getsize(saved_filename)
 | 
					 | 
				
			||||||
            is_format = db.session.query(db.Data).filter(db.Data.book == book_id).filter(db.Data.format == file_ext.upper()).first()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Format entry already exists, no need to update the database
 | 
					 | 
				
			||||||
            if is_format:
 | 
					 | 
				
			||||||
                app.logger.info('Book format already existing')
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
 | 
					 | 
				
			||||||
                db.session.add(db_format)
 | 
					 | 
				
			||||||
                db.session.commit()
 | 
					 | 
				
			||||||
                db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Queue uploader info
 | 
					 | 
				
			||||||
            uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
 | 
					 | 
				
			||||||
            helper.global_WorkerThread.add_upload(current_user.nickname, 
 | 
					 | 
				
			||||||
                "<a href=\"" + url_for('show_book', book_id=book.id) + "\">" + uploadText + "</a>")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if 'btn-upload-cover' in request.files:
 | 
					 | 
				
			||||||
        requested_file = request.files['btn-upload-cover']
 | 
					 | 
				
			||||||
        # check for empty request
 | 
					 | 
				
			||||||
        if requested_file.filename != '':
 | 
					 | 
				
			||||||
            file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
 | 
					 | 
				
			||||||
            filepath = os.path.normpath(os.path.join(config.config_calibre_dir, book.path))
 | 
					 | 
				
			||||||
            saved_filename = os.path.join(filepath,  'cover.' + file_ext)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # check if file path exists, otherwise create it, copy file to calibre path and delete temp file
 | 
					 | 
				
			||||||
            if not os.path.exists(filepath):
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    os.makedirs(filepath)
 | 
					 | 
				
			||||||
                except OSError:
 | 
					 | 
				
			||||||
                    flash(_(u"Failed to create path for cover %(path)s (Permission denied).", cover=filepath), category="error")
 | 
					 | 
				
			||||||
                    return redirect(url_for('show_book', book_id=book.id))
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                requested_file.save(saved_filename)
 | 
					 | 
				
			||||||
                # im=Image.open(saved_filename)
 | 
					 | 
				
			||||||
                book.has_cover = 1
 | 
					 | 
				
			||||||
            except OSError:
 | 
					 | 
				
			||||||
                flash(_(u"Failed to store cover-file %(cover)s.", cover=saved_filename), category="error")
 | 
					 | 
				
			||||||
                return redirect(url_for('show_book', book_id=book.id))
 | 
					 | 
				
			||||||
            except IOError:
 | 
					 | 
				
			||||||
                flash(_(u"Cover-file is not a valid image file" % saved_filename), category="error")
 | 
					 | 
				
			||||||
                return redirect(url_for('show_book', book_id=book.id))
 | 
					 | 
				
			||||||
    to_save = request.form.to_dict()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        # Update book
 | 
					 | 
				
			||||||
        edited_books_id = set()
 | 
					 | 
				
			||||||
        #handle book title
 | 
					 | 
				
			||||||
        if book.title != to_save["book_title"]:
 | 
					 | 
				
			||||||
            book.title = to_save["book_title"]
 | 
					 | 
				
			||||||
            edited_books_id.add(book.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # handle author(s)
 | 
					 | 
				
			||||||
        input_authors = to_save["author_name"].split('&')
 | 
					 | 
				
			||||||
        input_authors = list(map(lambda it: it.strip().replace(',', '|'), input_authors))
 | 
					 | 
				
			||||||
        # we have all author names now
 | 
					 | 
				
			||||||
        if input_authors == ['']:
 | 
					 | 
				
			||||||
            input_authors = [_(u'unknown')]  # prevent empty Author
 | 
					 | 
				
			||||||
        if book.authors:
 | 
					 | 
				
			||||||
            author0_before_edit = book.authors[0].name
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            author0_before_edit = db.Authors(_(u'unknown'), '', 0)
 | 
					 | 
				
			||||||
        modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author')
 | 
					 | 
				
			||||||
        if book.authors:
 | 
					 | 
				
			||||||
            if author0_before_edit != book.authors[0].name:
 | 
					 | 
				
			||||||
                edited_books_id.add(book.id)
 | 
					 | 
				
			||||||
                book.author_sort = helper.get_sorted_author(input_authors[0])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if config.config_use_google_drive:
 | 
					 | 
				
			||||||
            gdriveutils.updateGdriveCalibreFromLocal()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        error = False
 | 
					 | 
				
			||||||
        for b in edited_books_id:
 | 
					 | 
				
			||||||
            error = helper.update_dir_stucture(b, config.config_calibre_dir)
 | 
					 | 
				
			||||||
            if error:   # stop on error
 | 
					 | 
				
			||||||
                flash(error, category="error")
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not error:
 | 
					 | 
				
			||||||
            if to_save["cover_url"]:
 | 
					 | 
				
			||||||
                if helper.save_cover(to_save["cover_url"], book.path) is True:
 | 
					 | 
				
			||||||
                    book.has_cover = 1
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    flash(_(u"Cover is not a jpg file, can't save"), category="error")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if book.series_index != to_save["series_index"]:
 | 
					 | 
				
			||||||
                book.series_index = to_save["series_index"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Handle book comments/description
 | 
					 | 
				
			||||||
            if len(book.comments):
 | 
					 | 
				
			||||||
                book.comments[0].text = to_save["description"]
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                book.comments.append(db.Comments(text=to_save["description"], book=book.id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Handle book tags
 | 
					 | 
				
			||||||
            input_tags = to_save["tags"].split(',')
 | 
					 | 
				
			||||||
            input_tags = list(map(lambda it: it.strip(), input_tags))
 | 
					 | 
				
			||||||
            modify_database_object(input_tags, book.tags, db.Tags, db.session, 'tags')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Handle book series
 | 
					 | 
				
			||||||
            input_series = [to_save["series"].strip()]
 | 
					 | 
				
			||||||
            input_series = [x for x in input_series if x != '']
 | 
					 | 
				
			||||||
            modify_database_object(input_series, book.series, db.Series, db.session, 'series')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if to_save["pubdate"]:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    book.pubdate = datetime.datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
 | 
					 | 
				
			||||||
                except ValueError:
 | 
					 | 
				
			||||||
                    book.pubdate = db.Books.DEFAULT_PUBDATE
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                book.pubdate = db.Books.DEFAULT_PUBDATE
 | 
					 | 
				
			||||||
            '''if len(book.publishers):
 | 
					 | 
				
			||||||
                if to_save["publisher"] != book.publishers[0].name:
 | 
					 | 
				
			||||||
                    modify_database_object(to_save["publisher"], book.publishers, db.Publishers, db.session, 'series')
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                modify_database_object(to_save["publisher"], book.publishers, db.Publishers, db.session, 'series')'''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # handle book languages
 | 
					 | 
				
			||||||
            input_languages = to_save["languages"].split(',')
 | 
					 | 
				
			||||||
            # input_languages = list(map(lambda it: it.strip().lower(), input_languages))
 | 
					 | 
				
			||||||
            input_languages = [x.strip().lower() for x in input_languages if x != '']
 | 
					 | 
				
			||||||
            input_l = []
 | 
					 | 
				
			||||||
            invers_lang_table = [x.lower() for x in language_table[get_locale()].values()]
 | 
					 | 
				
			||||||
            for lang in input_languages:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    res = list(language_table[get_locale()].keys())[invers_lang_table.index(lang)]
 | 
					 | 
				
			||||||
                    input_l.append(res)
 | 
					 | 
				
			||||||
                except ValueError:
 | 
					 | 
				
			||||||
                    app.logger.error('%s is not a valid language' % lang)
 | 
					 | 
				
			||||||
                    flash(_(u"%(langname)s is not a valid language", langname=lang), category="error")
 | 
					 | 
				
			||||||
            modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if to_save["rating"].strip():
 | 
					 | 
				
			||||||
                old_rating = False
 | 
					 | 
				
			||||||
                if len(book.ratings) > 0:
 | 
					 | 
				
			||||||
                    old_rating = book.ratings[0].rating
 | 
					 | 
				
			||||||
                ratingx2 = int(float(to_save["rating"]) * 2)
 | 
					 | 
				
			||||||
                if ratingx2 != old_rating:
 | 
					 | 
				
			||||||
                    is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first()
 | 
					 | 
				
			||||||
                    if is_rating:
 | 
					 | 
				
			||||||
                        book.ratings.append(is_rating)
 | 
					 | 
				
			||||||
                    else:
 | 
					 | 
				
			||||||
                        new_rating = db.Ratings(rating=ratingx2)
 | 
					 | 
				
			||||||
                        book.ratings.append(new_rating)
 | 
					 | 
				
			||||||
                    if old_rating:
 | 
					 | 
				
			||||||
                        book.ratings.remove(book.ratings[0])
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                if len(book.ratings) > 0:
 | 
					 | 
				
			||||||
                    book.ratings.remove(book.ratings[0])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def edit_cc_data(book_id, book, to_save):
 | 
				
			||||||
 | 
					    cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
 | 
				
			||||||
    for c in cc:
 | 
					    for c in cc:
 | 
				
			||||||
        cc_string = "custom_column_" + str(c.id)
 | 
					        cc_string = "custom_column_" + str(c.id)
 | 
				
			||||||
        if not c.is_multiple:
 | 
					        if not c.is_multiple:
 | 
				
			||||||
| 
						 | 
					@ -3660,29 +3470,227 @@ def edit_book(book_id):
 | 
				
			||||||
            input_tags = list(map(lambda it: it.strip(), input_tags))
 | 
					            input_tags = list(map(lambda it: it.strip(), input_tags))
 | 
				
			||||||
            modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session,
 | 
					            modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session,
 | 
				
			||||||
                                   'custom')
 | 
					                                   'custom')
 | 
				
			||||||
 | 
					        return cc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def upload_single_file(request, book, book_id):
 | 
				
			||||||
 | 
					    # Check and handle Uploaded file
 | 
				
			||||||
 | 
					    if 'btn-upload-format' in request.files:
 | 
				
			||||||
 | 
					        requested_file = request.files['btn-upload-format']
 | 
				
			||||||
 | 
					        # check for empty request
 | 
				
			||||||
 | 
					        if requested_file.filename != '':
 | 
				
			||||||
 | 
					            if '.' in requested_file.filename:
 | 
				
			||||||
 | 
					                file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
 | 
				
			||||||
 | 
					                if file_ext not in EXTENSIONS_UPLOAD:
 | 
				
			||||||
 | 
					                    flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext),
 | 
				
			||||||
 | 
					                          category="error")
 | 
				
			||||||
 | 
					                    return redirect(url_for('show_book', book_id=book.id))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                flash(_('File to be uploaded must have an extension'), category="error")
 | 
				
			||||||
 | 
					                return redirect(url_for('show_book', book_id=book.id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            file_name = book.path.rsplit('/', 1)[-1]
 | 
				
			||||||
 | 
					            filepath = os.path.normpath(os.path.join(config.config_calibre_dir, book.path))
 | 
				
			||||||
 | 
					            saved_filename = os.path.join(filepath, file_name + '.' + file_ext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # check if file path exists, otherwise create it, copy file to calibre path and delete temp file
 | 
				
			||||||
 | 
					            if not os.path.exists(filepath):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    os.makedirs(filepath)
 | 
				
			||||||
 | 
					                except OSError:
 | 
				
			||||||
 | 
					                    flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
 | 
				
			||||||
 | 
					                    return redirect(url_for('show_book', book_id=book.id))
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                requested_file.save(saved_filename)
 | 
				
			||||||
 | 
					            except OSError:
 | 
				
			||||||
 | 
					                flash(_(u"Failed to store file %(file)s.", file=saved_filename), category="error")
 | 
				
			||||||
 | 
					                return redirect(url_for('show_book', book_id=book.id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            file_size = os.path.getsize(saved_filename)
 | 
				
			||||||
 | 
					            is_format = db.session.query(db.Data).filter(db.Data.book == book_id).filter(db.Data.format == file_ext.upper()).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Format entry already exists, no need to update the database
 | 
				
			||||||
 | 
					            if is_format:
 | 
				
			||||||
 | 
					                app.logger.info('Book format already existing')
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
 | 
				
			||||||
 | 
					                db.session.add(db_format)
 | 
				
			||||||
 | 
					                db.session.commit()
 | 
				
			||||||
 | 
					                db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Queue uploader info
 | 
				
			||||||
 | 
					            uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
 | 
				
			||||||
 | 
					            helper.global_WorkerThread.add_upload(current_user.nickname,
 | 
				
			||||||
 | 
					                "<a href=\"" + url_for('show_book', book_id=book.id) + "\">" + uploadText + "</a>")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def upload_cover(request, book):
 | 
				
			||||||
 | 
					    if 'btn-upload-cover' in request.files:
 | 
				
			||||||
 | 
					        requested_file = request.files['btn-upload-cover']
 | 
				
			||||||
 | 
					        # check for empty request
 | 
				
			||||||
 | 
					        if requested_file.filename != '':
 | 
				
			||||||
 | 
					            file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
 | 
				
			||||||
 | 
					            filepath = os.path.normpath(os.path.join(config.config_calibre_dir, book.path))
 | 
				
			||||||
 | 
					            saved_filename = os.path.join(filepath,  'cover.' + file_ext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # check if file path exists, otherwise create it, copy file to calibre path and delete temp file
 | 
				
			||||||
 | 
					            if not os.path.exists(filepath):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    os.makedirs(filepath)
 | 
				
			||||||
 | 
					                except OSError:
 | 
				
			||||||
 | 
					                    flash(_(u"Failed to create path for cover %(path)s (Permission denied).", cover=filepath), category="error")
 | 
				
			||||||
 | 
					                    return redirect(url_for('show_book', book_id=book.id))
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                requested_file.save(saved_filename)
 | 
				
			||||||
 | 
					                # im=Image.open(saved_filename)
 | 
				
			||||||
 | 
					                book.has_cover = 1
 | 
				
			||||||
 | 
					            except OSError:
 | 
				
			||||||
 | 
					                flash(_(u"Failed to store cover-file %(cover)s.", cover=saved_filename), category="error")
 | 
				
			||||||
 | 
					                return redirect(url_for('show_book', book_id=book.id))
 | 
				
			||||||
 | 
					            except IOError:
 | 
				
			||||||
 | 
					                flash(_(u"Cover-file is not a valid image file" % saved_filename), category="error")
 | 
				
			||||||
 | 
					                return redirect(url_for('show_book', book_id=book.id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route("/admin/book/<int:book_id>", methods=['GET', 'POST'])
 | 
				
			||||||
 | 
					@login_required_if_no_ano
 | 
				
			||||||
 | 
					@edit_required
 | 
				
			||||||
 | 
					def edit_book(book_id):
 | 
				
			||||||
 | 
					    # Show form
 | 
				
			||||||
 | 
					    if request.method != 'POST':
 | 
				
			||||||
 | 
					        return render_edit_book(book_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # create the function for sorting...
 | 
				
			||||||
 | 
					    db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
 | 
				
			||||||
 | 
					    book = db.session.query(db.Books)\
 | 
				
			||||||
 | 
					        .filter(db.Books.id == book_id).filter(common_filters()).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Book not found
 | 
				
			||||||
 | 
					    if not book:
 | 
				
			||||||
 | 
					        flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")
 | 
				
			||||||
 | 
					        return redirect(url_for("index"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    upload_single_file(request, book, book_id)
 | 
				
			||||||
 | 
					    upload_cover(request, book)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        to_save = request.form.to_dict()
 | 
				
			||||||
 | 
					        # Update book
 | 
				
			||||||
 | 
					        edited_books_id = None
 | 
				
			||||||
 | 
					        #handle book title
 | 
				
			||||||
 | 
					        if book.title != to_save["book_title"]:
 | 
				
			||||||
 | 
					            if to_save["book_title"] == '':
 | 
				
			||||||
 | 
					                to_save["book_title"] = _(u'unknown')
 | 
				
			||||||
 | 
					            book.title = to_save["book_title"]
 | 
				
			||||||
 | 
					            edited_books_id = book.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # handle author(s)
 | 
				
			||||||
 | 
					        input_authors = to_save["author_name"].split('&')
 | 
				
			||||||
 | 
					        input_authors = list(map(lambda it: it.strip().replace(',', '|'), input_authors))
 | 
				
			||||||
 | 
					        # we have all author names now
 | 
				
			||||||
 | 
					        if input_authors == ['']:
 | 
				
			||||||
 | 
					            input_authors = [_(u'unknown')]  # prevent empty Author
 | 
				
			||||||
 | 
					        if book.authors:
 | 
				
			||||||
 | 
					            author0_before_edit = book.authors[0].name
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            author0_before_edit = db.Authors(_(u'unknown'), '', 0)
 | 
				
			||||||
 | 
					        modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author')
 | 
				
			||||||
 | 
					        if book.authors:
 | 
				
			||||||
 | 
					            if author0_before_edit != book.authors[0].name:
 | 
				
			||||||
 | 
					                edited_books_id = book.id
 | 
				
			||||||
 | 
					                book.author_sort = helper.get_sorted_author(input_authors[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if config.config_use_google_drive:
 | 
				
			||||||
 | 
					            gdriveutils.updateGdriveCalibreFromLocal()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        error = False
 | 
				
			||||||
 | 
					        if edited_books_id:
 | 
				
			||||||
 | 
					            error = helper.update_dir_stucture(edited_books_id, config.config_calibre_dir)
 | 
				
			||||||
 | 
					            if error:   # stop on error
 | 
				
			||||||
 | 
					                flash(error, category="error")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not error:
 | 
				
			||||||
 | 
					            if to_save["cover_url"]:
 | 
				
			||||||
 | 
					                if helper.save_cover(to_save["cover_url"], book.path) is True:
 | 
				
			||||||
 | 
					                    book.has_cover = 1
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    flash(_(u"Cover is not a jpg file, can't save"), category="error")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if book.series_index != to_save["series_index"]:
 | 
				
			||||||
 | 
					                book.series_index = to_save["series_index"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Handle book comments/description
 | 
				
			||||||
 | 
					            if len(book.comments):
 | 
				
			||||||
 | 
					                book.comments[0].text = to_save["description"]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                book.comments.append(db.Comments(text=to_save["description"], book=book.id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Handle book tags
 | 
				
			||||||
 | 
					            input_tags = to_save["tags"].split(',')
 | 
				
			||||||
 | 
					            input_tags = list(map(lambda it: it.strip(), input_tags))
 | 
				
			||||||
 | 
					            modify_database_object(input_tags, book.tags, db.Tags, db.session, 'tags')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Handle book series
 | 
				
			||||||
 | 
					            input_series = [to_save["series"].strip()]
 | 
				
			||||||
 | 
					            input_series = [x for x in input_series if x != '']
 | 
				
			||||||
 | 
					            modify_database_object(input_series, book.series, db.Series, db.session, 'series')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if to_save["pubdate"]:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    book.pubdate = datetime.datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
 | 
				
			||||||
 | 
					                except ValueError:
 | 
				
			||||||
 | 
					                    book.pubdate = db.Books.DEFAULT_PUBDATE
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                book.pubdate = db.Books.DEFAULT_PUBDATE
 | 
				
			||||||
 | 
					            '''if len(book.publishers):
 | 
				
			||||||
 | 
					                if to_save["publisher"] != book.publishers[0].name:
 | 
				
			||||||
 | 
					                    modify_database_object(to_save["publisher"], book.publishers, db.Publishers, db.session, 'series')
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                modify_database_object(to_save["publisher"], book.publishers, db.Publishers, db.session, 'series')'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # handle book languages
 | 
				
			||||||
 | 
					            input_languages = to_save["languages"].split(',')
 | 
				
			||||||
 | 
					            input_languages = [x.strip().lower() for x in input_languages if x != '']
 | 
				
			||||||
 | 
					            input_l = []
 | 
				
			||||||
 | 
					            invers_lang_table = [x.lower() for x in language_table[get_locale()].values()]
 | 
				
			||||||
 | 
					            for lang in input_languages:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    res = list(language_table[get_locale()].keys())[invers_lang_table.index(lang)]
 | 
				
			||||||
 | 
					                    input_l.append(res)
 | 
				
			||||||
 | 
					                except ValueError:
 | 
				
			||||||
 | 
					                    app.logger.error('%s is not a valid language' % lang)
 | 
				
			||||||
 | 
					                    flash(_(u"%(langname)s is not a valid language", langname=lang), category="error")
 | 
				
			||||||
 | 
					            modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # handle book ratings
 | 
				
			||||||
 | 
					            if to_save["rating"].strip():
 | 
				
			||||||
 | 
					                old_rating = False
 | 
				
			||||||
 | 
					                if len(book.ratings) > 0:
 | 
				
			||||||
 | 
					                    old_rating = book.ratings[0].rating
 | 
				
			||||||
 | 
					                ratingx2 = int(float(to_save["rating"]) * 2)
 | 
				
			||||||
 | 
					                if ratingx2 != old_rating:
 | 
				
			||||||
 | 
					                    is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first()
 | 
				
			||||||
 | 
					                    if is_rating:
 | 
				
			||||||
 | 
					                        book.ratings.append(is_rating)
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        new_rating = db.Ratings(rating=ratingx2)
 | 
				
			||||||
 | 
					                        book.ratings.append(new_rating)
 | 
				
			||||||
 | 
					                    if old_rating:
 | 
				
			||||||
 | 
					                        book.ratings.remove(book.ratings[0])
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                if len(book.ratings) > 0:
 | 
				
			||||||
 | 
					                    book.ratings.remove(book.ratings[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # handle cc data
 | 
				
			||||||
 | 
					            edit_cc_data(book_id, book, to_save)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            db.session.commit()
 | 
					            db.session.commit()
 | 
				
			||||||
            if config.config_use_google_drive:
 | 
					            if config.config_use_google_drive:
 | 
				
			||||||
                gdriveutils.updateGdriveCalibreFromLocal()
 | 
					                gdriveutils.updateGdriveCalibreFromLocal()
 | 
				
			||||||
            if "detail_view" in to_save:
 | 
					            if "detail_view" in to_save:
 | 
				
			||||||
                return redirect(url_for('show_book', book_id=book.id))
 | 
					                return redirect(url_for('show_book', book_id=book.id))
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                for indx in range(0, len(book.languages)):
 | 
					                return render_edit_book(book_id)
 | 
				
			||||||
                    try:
 | 
					 | 
				
			||||||
                        book.languages[indx].language_name = LC.parse(book.languages[indx].lang_code).get_language_name(
 | 
					 | 
				
			||||||
                            get_locale())
 | 
					 | 
				
			||||||
                    except UnknownLocaleError:
 | 
					 | 
				
			||||||
                        book.languages[indx].language_name = _(
 | 
					 | 
				
			||||||
                            isoLanguages.get(part3=book.languages[indx].lang_code).name)
 | 
					 | 
				
			||||||
                author_names = []
 | 
					 | 
				
			||||||
                for authr in book.authors:
 | 
					 | 
				
			||||||
                    author_names.append(authr.name)
 | 
					 | 
				
			||||||
                return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
 | 
					 | 
				
			||||||
                                             title=_(u"edit metadata"), page="editbook")
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            db.session.rollback()
 | 
					            db.session.rollback()
 | 
				
			||||||
            flash(error, category="error")
 | 
					            flash(error, category="error")
 | 
				
			||||||
            return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
 | 
					            return render_edit_book(book_id)
 | 
				
			||||||
                                         title=_(u"edit metadata"), page="editbook")
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        app.logger.exception(e)
 | 
					        app.logger.exception(e)
 | 
				
			||||||
        db.session.rollback()
 | 
					        db.session.rollback()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user