mirror of
https://github.com/janeczku/calibre-web.git
synced 2024-11-26 08:51:05 +02:00
Link fixes
Fixes reader button visible in detail view Fix formats to convert (added htmlz) Fix logger in updater Added request "v3" of github api on update Fix quotes parameter on external calls E-Mail logger working more stable (also on python3) Routing fixes Change import in ub
This commit is contained in:
parent
1dc6f44828
commit
4230226716
73
cps/admin.py
73
cps/admin.py
@ -21,6 +21,7 @@
|
||||
# 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 logging
|
||||
import os
|
||||
from flask import Blueprint, flash, redirect, url_for
|
||||
from flask import abort, request, make_response
|
||||
@ -39,20 +40,26 @@ from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDat
|
||||
import helper
|
||||
from werkzeug.security import generate_password_hash
|
||||
from oauth_bb import oauth_check
|
||||
try:
|
||||
from urllib.parse import quote
|
||||
from imp import reload
|
||||
except ImportError:
|
||||
from urllib import quote
|
||||
|
||||
feature_support = dict()
|
||||
try:
|
||||
from goodreads.client import GoodreadsClient
|
||||
goodreads_support = True
|
||||
feature_support['goodreads'] = True
|
||||
except ImportError:
|
||||
goodreads_support = False
|
||||
feature_support['goodreads'] = False
|
||||
|
||||
try:
|
||||
import rarfile
|
||||
rar_support = True
|
||||
feature_support['rar'] = True
|
||||
except ImportError:
|
||||
rar_support = False
|
||||
|
||||
feature_support['rar'] = False
|
||||
|
||||
feature_support['gdrive'] = gdrive_support
|
||||
admi = Blueprint('admin', __name__)
|
||||
|
||||
|
||||
@ -287,7 +294,7 @@ def configuration_helper(origin):
|
||||
db_change = False
|
||||
success = False
|
||||
filedata = None
|
||||
if gdrive_support is False:
|
||||
if not feature_support['gdrive']:
|
||||
gdriveError = _('Import of optional Google Drive requirements missing')
|
||||
else:
|
||||
if not os.path.isfile(os.path.join(config.get_main_dir, 'client_secrets.json')):
|
||||
@ -327,7 +334,7 @@ def configuration_helper(origin):
|
||||
else:
|
||||
flash(_(u'client_secrets.json is not configured for web application'), category="error")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdrive_support, gdriveError=gdriveError,
|
||||
gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, title=_(u"Basic Configuration"),
|
||||
page="config")
|
||||
# always show google drive settings, but in case of error deny support
|
||||
@ -353,7 +360,7 @@ def configuration_helper(origin):
|
||||
ub.session.commit()
|
||||
flash(_(u'Keyfile location is not valid, please enter correct path'), category="error")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdrive_support, gdriveError=gdriveError,
|
||||
gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, title=_(u"Basic Configuration"),
|
||||
page="config")
|
||||
if "config_certfile" in to_save:
|
||||
@ -365,9 +372,8 @@ def configuration_helper(origin):
|
||||
ub.session.commit()
|
||||
flash(_(u'Certfile location is not valid, please enter correct path'), category="error")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdrive_support, gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, title=_(u"Basic Configuration"),
|
||||
page="config")
|
||||
gdriveError=gdriveError, feature_support=feature_support,
|
||||
title=_(u"Basic Configuration"), page="config")
|
||||
content.config_uploading = 0
|
||||
content.config_anonbrowse = 0
|
||||
content.config_public_reg = 0
|
||||
@ -391,9 +397,8 @@ def configuration_helper(origin):
|
||||
ub.session.commit()
|
||||
flash(_(u'Please enter a LDAP provider and a DN'), category="error")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdrive_support, gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, title=_(u"Basic Configuration"),
|
||||
page="config")
|
||||
gdriveError=gdriveError, feature_support=feature_support,
|
||||
title=_(u"Basic Configuration"), page="config")
|
||||
else:
|
||||
content.config_use_ldap = 1
|
||||
content.config_ldap_provider_url = to_save["config_ldap_provider_url"]
|
||||
@ -450,9 +455,8 @@ def configuration_helper(origin):
|
||||
ub.session.commit()
|
||||
flash(_(u'Logfile location is not valid, please enter correct path'), category="error")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdrive_support, gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, title=_(u"Basic Configuration"),
|
||||
page="config")
|
||||
gdriveError=gdriveError, feature_support=feature_support,
|
||||
title=_(u"Basic Configuration"), page="config")
|
||||
else:
|
||||
content.config_logfile = to_save["config_logfile"]
|
||||
reboot_required = True
|
||||
@ -465,8 +469,7 @@ def configuration_helper(origin):
|
||||
else:
|
||||
flash(check[1], category="error")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdrive_support, goodreads=goodreads_support,
|
||||
rarfile_support=rar_support, title=_(u"Basic Configuration"))
|
||||
feature_support=feature_support, title=_(u"Basic Configuration"))
|
||||
try:
|
||||
if content.config_use_google_drive and is_gdrive_ready() and not \
|
||||
os.path.exists(os.path.join(content.config_calibre_dir, "metadata.db")):
|
||||
@ -479,20 +482,18 @@ def configuration_helper(origin):
|
||||
flash(_(u"Calibre-Web configuration updated"), category="success")
|
||||
config.loadSettings()
|
||||
app.logger.setLevel(config.config_log_level)
|
||||
logging.getLogger("book_formats").setLevel(config.config_log_level)
|
||||
logging.getLogger("uploader").setLevel(config.config_log_level)
|
||||
except Exception as e:
|
||||
flash(e, category="error")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdrive_support, gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, rarfile_support=rar_support,
|
||||
gdriveError=gdriveError, feature_support=feature_support,
|
||||
title=_(u"Basic Configuration"), page="config")
|
||||
if db_change:
|
||||
reload(db)
|
||||
if not db.setup_db():
|
||||
flash(_(u'DB location is not valid, please enter correct path'), category="error")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdrive_support, gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, rarfile_support=rar_support,
|
||||
gdriveError=gdriveError, feature_support=feature_support,
|
||||
title=_(u"Basic Configuration"), page="config")
|
||||
if reboot_required:
|
||||
# stop Server
|
||||
@ -501,15 +502,14 @@ def configuration_helper(origin):
|
||||
app.logger.info('Reboot required, restarting')
|
||||
if origin:
|
||||
success = True
|
||||
if is_gdrive_ready() and gdrive_support is True: # and config.config_use_google_drive == True:
|
||||
if is_gdrive_ready() and feature_support['gdrive'] is True: # and config.config_use_google_drive == True:
|
||||
gdrivefolders = listRootFolders()
|
||||
else:
|
||||
gdrivefolders = list()
|
||||
return render_title_template("config_edit.html", origin=origin, success=success, content=config,
|
||||
show_authenticate_google_drive=not is_gdrive_ready(),
|
||||
gdrive=gdrive_support, gdriveError=gdriveError,
|
||||
gdrivefolders=gdrivefolders, rarfile_support=rar_support,
|
||||
goodreads=goodreads_support, title=_(u"Basic Configuration"), page="config")
|
||||
gdriveError=gdriveError, gdrivefolders=gdrivefolders, feature_support=feature_support,
|
||||
title=_(u"Basic Configuration"), page="config")
|
||||
|
||||
|
||||
@admi.route("/admin/user/new", methods=["GET", "POST"])
|
||||
@ -569,20 +569,20 @@ def new_user():
|
||||
if not to_save["nickname"] or not to_save["email"] or not to_save["password"]:
|
||||
flash(_(u"Please fill out all fields!"), category="error")
|
||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||
title=_(u"Add new user"))
|
||||
registered_oauth=oauth_check, title=_(u"Add new user"))
|
||||
content.password = generate_password_hash(to_save["password"])
|
||||
content.nickname = to_save["nickname"]
|
||||
if config.config_public_reg and not check_valid_domain(to_save["email"]):
|
||||
flash(_(u"E-mail is not from valid domain"), category="error")
|
||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||
title=_(u"Add new user"))
|
||||
registered_oauth=oauth_check, title=_(u"Add new user"))
|
||||
else:
|
||||
content.email = to_save["email"]
|
||||
try:
|
||||
ub.session.add(content)
|
||||
ub.session.commit()
|
||||
flash(_(u"User '%(user)s' created", user=content.nickname), category="success")
|
||||
return redirect(url_for('admin'))
|
||||
return redirect(url_for('admin.admin'))
|
||||
except IntegrityError:
|
||||
ub.session.rollback()
|
||||
flash(_(u"Found an existing account for this e-mail address or nickname."), category="error")
|
||||
@ -591,7 +591,8 @@ def new_user():
|
||||
content.sidebar_view = config.config_default_show
|
||||
content.mature_content = bool(config.config_default_show & ub.MATURE_CONTENT)
|
||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||
languages=languages, title=_(u"Add new user"), page="newuser", registered_oauth=oauth_check)
|
||||
languages=languages, title=_(u"Add new user"), page="newuser",
|
||||
registered_oauth=oauth_check)
|
||||
|
||||
|
||||
@admi.route("/admin/mailsettings", methods=["GET", "POST"])
|
||||
@ -649,7 +650,7 @@ def edit_user(user_id):
|
||||
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
||||
ub.session.commit()
|
||||
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
|
||||
return redirect(url_for('admin'))
|
||||
return redirect(url_for('admin.admin'))
|
||||
else:
|
||||
if "password" in to_save and to_save["password"]:
|
||||
content.password = generate_password_hash(to_save["password"])
|
||||
@ -766,8 +767,8 @@ def edit_user(user_id):
|
||||
ub.session.rollback()
|
||||
flash(_(u"An unknown error occured."), category="error")
|
||||
return render_title_template("user_edit.html", translations=translations, languages=languages, new_user=0,
|
||||
content=content, downloads=downloads, title=_(u"Edit User %(nick)s",
|
||||
nick=content.nickname), page="edituser", registered_oauth=oauth_check)
|
||||
content=content, downloads=downloads, registered_oauth=oauth_check,
|
||||
title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser")
|
||||
|
||||
|
||||
@admi.route("/admin/resetpassword/<int:user_id>")
|
||||
@ -787,7 +788,7 @@ def reset_password(user_id):
|
||||
except Exception:
|
||||
ub.session.rollback()
|
||||
flash(_(u"An unknown error occurred. Please try again later."), category="error")
|
||||
return redirect(url_for('admin'))
|
||||
return redirect(url_for('admin.admin'))
|
||||
|
||||
|
||||
@admi.route("/get_update_status", methods=['GET'])
|
||||
|
@ -42,7 +42,7 @@ from iso639 import languages as isoLanguages
|
||||
|
||||
editbook = Blueprint('editbook', __name__)
|
||||
|
||||
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', 'htmlz', 'rtf', 'odt'}
|
||||
|
||||
EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
|
||||
'fb2', 'html', 'rtf', 'odt', 'mp3', 'm4a', 'm4b'}
|
||||
@ -380,7 +380,7 @@ def upload_single_file(request, book, book_id):
|
||||
# Queue uploader info
|
||||
uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
|
||||
global_WorkerThread.add_upload(current_user.nickname,
|
||||
"<a href=\"" + url_for('show_book', book_id=book.id) + "\">" + uploadText + "</a>")
|
||||
"<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>")
|
||||
|
||||
def upload_cover(request, book):
|
||||
if 'btn-upload-cover' in request.files:
|
||||
@ -589,10 +589,10 @@ def upload():
|
||||
flash(
|
||||
_("File extension '%(ext)s' is not allowed to be uploaded to this server",
|
||||
ext=file_ext), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
else:
|
||||
flash(_('File to be uploaded must have an extension'), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
# extract metadata from file
|
||||
meta = uploader.upload(requested_file)
|
||||
@ -612,12 +612,12 @@ def upload():
|
||||
os.makedirs(filepath)
|
||||
except OSError:
|
||||
flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
try:
|
||||
copyfile(meta.file_path, saved_filename)
|
||||
except OSError:
|
||||
flash(_(u"Failed to store file %(file)s (Permission denied).", file=saved_filename), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
try:
|
||||
os.unlink(meta.file_path)
|
||||
except OSError:
|
||||
|
@ -70,7 +70,7 @@ def google_drive_callback():
|
||||
f.write(credentials.to_json())
|
||||
except ValueError as error:
|
||||
app.logger.error(error)
|
||||
return redirect(url_for('configuration'))
|
||||
return redirect(url_for('admin.configuration'))
|
||||
|
||||
|
||||
@gdrive.route("/gdrive/watch/subscribe")
|
||||
@ -102,7 +102,7 @@ def watch_gdrive():
|
||||
else:
|
||||
flash(reason['message'], category="error")
|
||||
|
||||
return redirect(url_for('configuration'))
|
||||
return redirect(url_for('admin.configuration'))
|
||||
|
||||
|
||||
@gdrive.route("/gdrive/watch/revoke")
|
||||
@ -121,7 +121,7 @@ def revoke_watch_gdrive():
|
||||
ub.session.merge(settings)
|
||||
ub.session.commit()
|
||||
config.loadSettings()
|
||||
return redirect(url_for('configuration'))
|
||||
return redirect(url_for('admin.configuration'))
|
||||
|
||||
|
||||
@gdrive.route("/gdrive/watch/callback", methods=['GET', 'POST'])
|
||||
|
@ -19,13 +19,13 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import db
|
||||
from cps import config
|
||||
from cps import config, global_WorkerThread, get_locale
|
||||
from flask import current_app as app
|
||||
from tempfile import gettempdir
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import db
|
||||
import unicodedata
|
||||
import worker
|
||||
import time
|
||||
@ -40,7 +40,6 @@ try:
|
||||
import gdriveutils as gd
|
||||
except ImportError:
|
||||
pass
|
||||
# import web
|
||||
import random
|
||||
from subproc_wrapper import process_open
|
||||
import ub
|
||||
@ -244,7 +243,7 @@ def get_sorted_author(value):
|
||||
else:
|
||||
value2 = value
|
||||
except Exception:
|
||||
web.app.logger.error("Sorting author " + str(value) + "failed")
|
||||
app.logger.error("Sorting author " + str(value) + "failed")
|
||||
value2 = value
|
||||
return value2
|
||||
|
||||
@ -261,13 +260,13 @@ def delete_book_file(book, calibrepath, book_format=None):
|
||||
else:
|
||||
if os.path.isdir(path):
|
||||
if len(next(os.walk(path))[1]):
|
||||
web.app.logger.error(
|
||||
app.logger.error(
|
||||
"Deleting book " + str(book.id) + " failed, path has subfolders: " + book.path)
|
||||
return False
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
return True
|
||||
else:
|
||||
web.app.logger.error("Deleting book " + str(book.id) + " failed, book path not valid: " + book.path)
|
||||
app.logger.error("Deleting book " + str(book.id) + " failed, book path not valid: " + book.path)
|
||||
return False
|
||||
|
||||
|
||||
@ -290,7 +289,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
|
||||
if not os.path.exists(new_title_path):
|
||||
os.renames(path, new_title_path)
|
||||
else:
|
||||
web.app.logger.info("Copying title: " + path + " into existing: " + new_title_path)
|
||||
app.logger.info("Copying title: " + path + " into existing: " + new_title_path)
|
||||
for dir_name, subdir_list, file_list in os.walk(path):
|
||||
for file in file_list:
|
||||
os.renames(os.path.join(dir_name, file),
|
||||
@ -298,8 +297,8 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
|
||||
path = new_title_path
|
||||
localbook.path = localbook.path.split('/')[0] + '/' + new_titledir
|
||||
except OSError as ex:
|
||||
web.app.logger.error("Rename title from: " + path + " to " + new_title_path + ": " + str(ex))
|
||||
web.app.logger.debug(ex, exc_info=True)
|
||||
app.logger.error("Rename title from: " + path + " to " + new_title_path + ": " + str(ex))
|
||||
app.logger.debug(ex, exc_info=True)
|
||||
return _("Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s",
|
||||
src=path, dest=new_title_path, error=str(ex))
|
||||
if authordir != new_authordir:
|
||||
@ -308,8 +307,8 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
|
||||
os.renames(path, new_author_path)
|
||||
localbook.path = new_authordir + '/' + localbook.path.split('/')[1]
|
||||
except OSError as ex:
|
||||
web.app.logger.error("Rename author from: " + path + " to " + new_author_path + ": " + str(ex))
|
||||
web.app.logger.debug(ex, exc_info=True)
|
||||
app.logger.error("Rename author from: " + path + " to " + new_author_path + ": " + str(ex))
|
||||
app.logger.debug(ex, exc_info=True)
|
||||
return _("Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s",
|
||||
src=path, dest=new_author_path, error=str(ex))
|
||||
# Rename all files from old names to new names
|
||||
@ -322,8 +321,8 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
|
||||
os.path.join(path_name,new_name + '.' + file_format.format.lower()))
|
||||
file_format.name = new_name
|
||||
except OSError as ex:
|
||||
web.app.logger.error("Rename file in path " + path + " to " + new_name + ": " + str(ex))
|
||||
web.app.logger.debug(ex, exc_info=True)
|
||||
app.logger.error("Rename file in path " + path + " to " + new_name + ": " + str(ex))
|
||||
app.logger.debug(ex, exc_info=True)
|
||||
return _("Rename file in path '%(src)s' to '%(dest)s' failed with error: %(error)s",
|
||||
src=path, dest=new_name, error=str(ex))
|
||||
return False
|
||||
@ -418,17 +417,17 @@ def delete_book(book, calibrepath, book_format):
|
||||
def get_book_cover(cover_path):
|
||||
if config.config_use_google_drive:
|
||||
try:
|
||||
if not web.is_gdrive_ready():
|
||||
if not gd.is_gdrive_ready():
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
||||
path=gd.get_cover_via_gdrive(cover_path)
|
||||
if path:
|
||||
return redirect(path)
|
||||
else:
|
||||
web.app.logger.error(cover_path + '/cover.jpg not found on Google Drive')
|
||||
app.logger.error(cover_path + '/cover.jpg not found on Google Drive')
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
||||
except Exception as e:
|
||||
web.app.logger.error("Error Message: " + e.message)
|
||||
web.app.logger.exception(e)
|
||||
app.logger.error("Error Message: " + e.message)
|
||||
app.logger.exception(e)
|
||||
# traceback.print_exc()
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
|
||||
else:
|
||||
@ -439,7 +438,7 @@ def get_book_cover(cover_path):
|
||||
def save_cover(url, book_path):
|
||||
img = requests.get(url)
|
||||
if img.headers.get('content-type') != 'image/jpeg':
|
||||
web.app.logger.error("Cover is no jpg file, can't save")
|
||||
app.logger.error("Cover is no jpg file, can't save")
|
||||
return False
|
||||
|
||||
if config.config_use_google_drive:
|
||||
@ -448,13 +447,13 @@ def save_cover(url, book_path):
|
||||
f.write(img.content)
|
||||
f.close()
|
||||
gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
|
||||
web.app.logger.info("Cover is saved on Google Drive")
|
||||
app.logger.info("Cover is saved on Google Drive")
|
||||
return True
|
||||
|
||||
f = open(os.path.join(config.config_calibre_dir, book_path, "cover.jpg"), "wb")
|
||||
f.write(img.content)
|
||||
f.close()
|
||||
web.app.logger.info("Cover is saved")
|
||||
app.logger.info("Cover is saved")
|
||||
return True
|
||||
|
||||
|
||||
@ -462,7 +461,7 @@ def do_download_file(book, book_format, data, headers):
|
||||
if config.config_use_google_drive:
|
||||
startTime = time.time()
|
||||
df = gd.getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
||||
web.app.logger.debug(time.time() - startTime)
|
||||
app.logger.debug(time.time() - startTime)
|
||||
if df:
|
||||
return gd.do_gdrive_download(df, headers)
|
||||
else:
|
||||
@ -471,7 +470,7 @@ def do_download_file(book, book_format, data, headers):
|
||||
filename = os.path.join(config.config_calibre_dir, book.path)
|
||||
if not os.path.isfile(os.path.join(filename, data.name + "." + book_format)):
|
||||
# ToDo: improve error handling
|
||||
web.app.logger.error('File not found: %s' % os.path.join(filename, data.name + "." + book_format))
|
||||
app.logger.error('File not found: %s' % os.path.join(filename, data.name + "." + book_format))
|
||||
response = make_response(send_from_directory(filename, data.name + "." + book_format))
|
||||
response.headers = headers
|
||||
return response
|
||||
@ -497,7 +496,7 @@ def check_unrar(unrarLocation):
|
||||
version = value.group(1)
|
||||
except OSError as e:
|
||||
error = True
|
||||
web.app.logger.exception(e)
|
||||
app.logger.exception(e)
|
||||
version =_(u'Error excecuting UnRar')
|
||||
else:
|
||||
version = _(u'Unrar binary file not found')
|
||||
@ -522,7 +521,7 @@ def render_task_status(tasklist):
|
||||
if task['user'] == current_user.nickname or current_user.role_admin():
|
||||
# task2 = copy.deepcopy(task) # = task
|
||||
if task['formStarttime']:
|
||||
task['starttime'] = format_datetime(task['formStarttime'], format='short', locale=web.get_locale())
|
||||
task['starttime'] = format_datetime(task['formStarttime'], format='short', locale=get_locale())
|
||||
# task2['formStarttime'] = ""
|
||||
else:
|
||||
if 'starttime' not in task:
|
||||
|
243
cps/oauth.py
243
cps/oauth.py
@ -2,133 +2,136 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from flask import session
|
||||
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
try:
|
||||
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
class OAuthBackend(SQLAlchemyBackend):
|
||||
"""
|
||||
Stores and retrieves OAuth tokens using a relational database through
|
||||
the `SQLAlchemy`_ ORM.
|
||||
|
||||
class OAuthBackend(SQLAlchemyBackend):
|
||||
"""
|
||||
Stores and retrieves OAuth tokens using a relational database through
|
||||
the `SQLAlchemy`_ ORM.
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
"""
|
||||
def __init__(self, model, session,
|
||||
user=None, user_id=None, user_required=None, anon_user=None,
|
||||
cache=None):
|
||||
super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache)
|
||||
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
"""
|
||||
def __init__(self, model, session,
|
||||
user=None, user_id=None, user_required=None, anon_user=None,
|
||||
cache=None):
|
||||
super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache)
|
||||
def get(self, blueprint, user=None, user_id=None):
|
||||
if blueprint.name + '_oauth_token' in session and session[blueprint.name + '_oauth_token'] != '':
|
||||
return session[blueprint.name + '_oauth_token']
|
||||
# check cache
|
||||
cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id)
|
||||
token = self.cache.get(cache_key)
|
||||
if token:
|
||||
return token
|
||||
|
||||
def get(self, blueprint, user=None, user_id=None):
|
||||
if blueprint.name + '_oauth_token' in session and session[blueprint.name + '_oauth_token'] != '':
|
||||
return session[blueprint.name + '_oauth_token']
|
||||
# check cache
|
||||
cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id)
|
||||
token = self.cache.get(cache_key)
|
||||
if token:
|
||||
return token
|
||||
|
||||
# if not cached, make database queries
|
||||
query = (
|
||||
self.session.query(self.model)
|
||||
.filter_by(provider=blueprint.name)
|
||||
)
|
||||
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
||||
u = first(_get_real_user(ref, self.anon_user)
|
||||
for ref in (user, self.user, blueprint.config.get("user")))
|
||||
|
||||
use_provider_user_id = False
|
||||
if blueprint.name + '_oauth_user_id' in session and session[blueprint.name + '_oauth_user_id'] != '':
|
||||
query = query.filter_by(provider_user_id=session[blueprint.name + '_oauth_user_id'])
|
||||
use_provider_user_id = True
|
||||
|
||||
if self.user_required and not u and not uid and not use_provider_user_id:
|
||||
#raise ValueError("Cannot get OAuth token without an associated user")
|
||||
return None
|
||||
# check for user ID
|
||||
if hasattr(self.model, "user_id") and uid:
|
||||
query = query.filter_by(user_id=uid)
|
||||
# check for user (relationship property)
|
||||
elif hasattr(self.model, "user") and u:
|
||||
query = query.filter_by(user=u)
|
||||
# if we have the property, but not value, filter by None
|
||||
elif hasattr(self.model, "user_id"):
|
||||
query = query.filter_by(user_id=None)
|
||||
# run query
|
||||
try:
|
||||
token = query.one().token
|
||||
except NoResultFound:
|
||||
token = None
|
||||
|
||||
# cache the result
|
||||
self.cache.set(cache_key, token)
|
||||
|
||||
return token
|
||||
|
||||
def set(self, blueprint, token, user=None, user_id=None):
|
||||
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
||||
u = first(_get_real_user(ref, self.anon_user)
|
||||
# if not cached, make database queries
|
||||
query = (
|
||||
self.session.query(self.model)
|
||||
.filter_by(provider=blueprint.name)
|
||||
)
|
||||
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
||||
u = first(_get_real_user(ref, self.anon_user)
|
||||
for ref in (user, self.user, blueprint.config.get("user")))
|
||||
|
||||
if self.user_required and not u and not uid:
|
||||
raise ValueError("Cannot set OAuth token without an associated user")
|
||||
use_provider_user_id = False
|
||||
if blueprint.name + '_oauth_user_id' in session and session[blueprint.name + '_oauth_user_id'] != '':
|
||||
query = query.filter_by(provider_user_id=session[blueprint.name + '_oauth_user_id'])
|
||||
use_provider_user_id = True
|
||||
|
||||
# if there was an existing model, delete it
|
||||
existing_query = (
|
||||
self.session.query(self.model)
|
||||
.filter_by(provider=blueprint.name)
|
||||
)
|
||||
# check for user ID
|
||||
has_user_id = hasattr(self.model, "user_id")
|
||||
if has_user_id and uid:
|
||||
existing_query = existing_query.filter_by(user_id=uid)
|
||||
# check for user (relationship property)
|
||||
has_user = hasattr(self.model, "user")
|
||||
if has_user and u:
|
||||
existing_query = existing_query.filter_by(user=u)
|
||||
# queue up delete query -- won't be run until commit()
|
||||
existing_query.delete()
|
||||
# create a new model for this token
|
||||
kwargs = {
|
||||
"provider": blueprint.name,
|
||||
"token": token,
|
||||
}
|
||||
if has_user_id and uid:
|
||||
kwargs["user_id"] = uid
|
||||
if has_user and u:
|
||||
kwargs["user"] = u
|
||||
self.session.add(self.model(**kwargs))
|
||||
# commit to delete and add simultaneously
|
||||
self.session.commit()
|
||||
# invalidate cache
|
||||
self.cache.delete(self.make_cache_key(
|
||||
blueprint=blueprint, user=user, user_id=user_id
|
||||
))
|
||||
if self.user_required and not u and not uid and not use_provider_user_id:
|
||||
#raise ValueError("Cannot get OAuth token without an associated user")
|
||||
return None
|
||||
# check for user ID
|
||||
if hasattr(self.model, "user_id") and uid:
|
||||
query = query.filter_by(user_id=uid)
|
||||
# check for user (relationship property)
|
||||
elif hasattr(self.model, "user") and u:
|
||||
query = query.filter_by(user=u)
|
||||
# if we have the property, but not value, filter by None
|
||||
elif hasattr(self.model, "user_id"):
|
||||
query = query.filter_by(user_id=None)
|
||||
# run query
|
||||
try:
|
||||
token = query.one().token
|
||||
except NoResultFound:
|
||||
token = None
|
||||
|
||||
def delete(self, blueprint, user=None, user_id=None):
|
||||
query = (
|
||||
self.session.query(self.model)
|
||||
.filter_by(provider=blueprint.name)
|
||||
)
|
||||
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
||||
u = first(_get_real_user(ref, self.anon_user)
|
||||
for ref in (user, self.user, blueprint.config.get("user")))
|
||||
# cache the result
|
||||
self.cache.set(cache_key, token)
|
||||
|
||||
if self.user_required and not u and not uid:
|
||||
raise ValueError("Cannot delete OAuth token without an associated user")
|
||||
return token
|
||||
|
||||
# check for user ID
|
||||
if hasattr(self.model, "user_id") and uid:
|
||||
query = query.filter_by(user_id=uid)
|
||||
# check for user (relationship property)
|
||||
elif hasattr(self.model, "user") and u:
|
||||
query = query.filter_by(user=u)
|
||||
# if we have the property, but not value, filter by None
|
||||
elif hasattr(self.model, "user_id"):
|
||||
query = query.filter_by(user_id=None)
|
||||
# run query
|
||||
query.delete()
|
||||
self.session.commit()
|
||||
# invalidate cache
|
||||
self.cache.delete(self.make_cache_key(
|
||||
blueprint=blueprint, user=user, user_id=user_id,
|
||||
))
|
||||
def set(self, blueprint, token, user=None, user_id=None):
|
||||
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
||||
u = first(_get_real_user(ref, self.anon_user)
|
||||
for ref in (user, self.user, blueprint.config.get("user")))
|
||||
|
||||
if self.user_required and not u and not uid:
|
||||
raise ValueError("Cannot set OAuth token without an associated user")
|
||||
|
||||
# if there was an existing model, delete it
|
||||
existing_query = (
|
||||
self.session.query(self.model)
|
||||
.filter_by(provider=blueprint.name)
|
||||
)
|
||||
# check for user ID
|
||||
has_user_id = hasattr(self.model, "user_id")
|
||||
if has_user_id and uid:
|
||||
existing_query = existing_query.filter_by(user_id=uid)
|
||||
# check for user (relationship property)
|
||||
has_user = hasattr(self.model, "user")
|
||||
if has_user and u:
|
||||
existing_query = existing_query.filter_by(user=u)
|
||||
# queue up delete query -- won't be run until commit()
|
||||
existing_query.delete()
|
||||
# create a new model for this token
|
||||
kwargs = {
|
||||
"provider": blueprint.name,
|
||||
"token": token,
|
||||
}
|
||||
if has_user_id and uid:
|
||||
kwargs["user_id"] = uid
|
||||
if has_user and u:
|
||||
kwargs["user"] = u
|
||||
self.session.add(self.model(**kwargs))
|
||||
# commit to delete and add simultaneously
|
||||
self.session.commit()
|
||||
# invalidate cache
|
||||
self.cache.delete(self.make_cache_key(
|
||||
blueprint=blueprint, user=user, user_id=user_id
|
||||
))
|
||||
|
||||
def delete(self, blueprint, user=None, user_id=None):
|
||||
query = (
|
||||
self.session.query(self.model)
|
||||
.filter_by(provider=blueprint.name)
|
||||
)
|
||||
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
||||
u = first(_get_real_user(ref, self.anon_user)
|
||||
for ref in (user, self.user, blueprint.config.get("user")))
|
||||
|
||||
if self.user_required and not u and not uid:
|
||||
raise ValueError("Cannot delete OAuth token without an associated user")
|
||||
|
||||
# check for user ID
|
||||
if hasattr(self.model, "user_id") and uid:
|
||||
query = query.filter_by(user_id=uid)
|
||||
# check for user (relationship property)
|
||||
elif hasattr(self.model, "user") and u:
|
||||
query = query.filter_by(user=u)
|
||||
# if we have the property, but not value, filter by None
|
||||
elif hasattr(self.model, "user_id"):
|
||||
query = query.filter_by(user_id=None)
|
||||
# run query
|
||||
query.delete()
|
||||
self.session.commit()
|
||||
# invalidate cache
|
||||
self.cache.delete(self.make_cache_key(
|
||||
blueprint=blueprint, user=user, user_id=user_id,
|
||||
))
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
417
cps/oauth_bb.py
417
cps/oauth_bb.py
@ -20,11 +20,14 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
from flask_dance.contrib.github import make_github_blueprint, github
|
||||
from flask_dance.contrib.google import make_google_blueprint, google
|
||||
from flask_dance.consumer import oauth_authorized, oauth_error
|
||||
try:
|
||||
from flask_dance.contrib.github import make_github_blueprint, github
|
||||
from flask_dance.contrib.google import make_google_blueprint, google
|
||||
from flask_dance.consumer import oauth_authorized, oauth_error
|
||||
from oauth import OAuthBackend
|
||||
except ImportError:
|
||||
pass
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
from oauth import OAuthBackend
|
||||
from flask import flash, session, redirect, url_for, request, make_response, abort
|
||||
import json
|
||||
from cps import config, app
|
||||
@ -91,226 +94,226 @@ def logout_oauth_user():
|
||||
if oauth + '_oauth_user_id' in session:
|
||||
session.pop(oauth + '_oauth_user_id')
|
||||
|
||||
if ub.oauth_support:
|
||||
github_blueprint = make_github_blueprint(
|
||||
client_id=config.config_github_oauth_client_id,
|
||||
client_secret=config.config_github_oauth_client_secret,
|
||||
redirect_to="github_login",)
|
||||
|
||||
github_blueprint = make_github_blueprint(
|
||||
client_id=config.config_github_oauth_client_id,
|
||||
client_secret=config.config_github_oauth_client_secret,
|
||||
redirect_to="github_login",)
|
||||
|
||||
google_blueprint = make_google_blueprint(
|
||||
client_id=config.config_google_oauth_client_id,
|
||||
client_secret=config.config_google_oauth_client_secret,
|
||||
redirect_to="google_login",
|
||||
scope=[
|
||||
"https://www.googleapis.com/auth/plus.me",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
]
|
||||
)
|
||||
|
||||
app.register_blueprint(google_blueprint, url_prefix="/login")
|
||||
app.register_blueprint(github_blueprint, url_prefix='/login')
|
||||
|
||||
github_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
|
||||
google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
|
||||
|
||||
|
||||
if config.config_use_github_oauth:
|
||||
register_oauth_blueprint(github_blueprint, 'GitHub')
|
||||
if config.config_use_google_oauth:
|
||||
register_oauth_blueprint(google_blueprint, 'Google')
|
||||
|
||||
|
||||
@oauth_authorized.connect_via(github_blueprint)
|
||||
def github_logged_in(blueprint, token):
|
||||
if not token:
|
||||
flash(_("Failed to log in with GitHub."), category="error")
|
||||
return False
|
||||
|
||||
resp = blueprint.session.get("/user")
|
||||
if not resp.ok:
|
||||
flash(_("Failed to fetch user info from GitHub."), category="error")
|
||||
return False
|
||||
|
||||
github_info = resp.json()
|
||||
github_user_id = str(github_info["id"])
|
||||
return oauth_update_token(blueprint, token, github_user_id)
|
||||
|
||||
|
||||
@oauth_authorized.connect_via(google_blueprint)
|
||||
def google_logged_in(blueprint, token):
|
||||
if not token:
|
||||
flash(_("Failed to log in with Google."), category="error")
|
||||
return False
|
||||
|
||||
resp = blueprint.session.get("/oauth2/v2/userinfo")
|
||||
if not resp.ok:
|
||||
flash(_("Failed to fetch user info from Google."), category="error")
|
||||
return False
|
||||
|
||||
google_info = resp.json()
|
||||
google_user_id = str(google_info["id"])
|
||||
|
||||
return oauth_update_token(blueprint, token, google_user_id)
|
||||
|
||||
|
||||
def oauth_update_token(blueprint, token, provider_user_id):
|
||||
session[blueprint.name + "_oauth_user_id"] = provider_user_id
|
||||
session[blueprint.name + "_oauth_token"] = token
|
||||
|
||||
# Find this OAuth token in the database, or create it
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
provider=blueprint.name,
|
||||
provider_user_id=provider_user_id,
|
||||
google_blueprint = make_google_blueprint(
|
||||
client_id=config.config_google_oauth_client_id,
|
||||
client_secret=config.config_google_oauth_client_secret,
|
||||
redirect_to="google_login",
|
||||
scope=[
|
||||
"https://www.googleapis.com/auth/plus.me",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
]
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
# update token
|
||||
oauth.token = token
|
||||
except NoResultFound:
|
||||
oauth = ub.OAuth(
|
||||
|
||||
app.register_blueprint(google_blueprint, url_prefix="/login")
|
||||
app.register_blueprint(github_blueprint, url_prefix='/login')
|
||||
|
||||
github_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
|
||||
google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
|
||||
|
||||
|
||||
if config.config_use_github_oauth:
|
||||
register_oauth_blueprint(github_blueprint, 'GitHub')
|
||||
if config.config_use_google_oauth:
|
||||
register_oauth_blueprint(google_blueprint, 'Google')
|
||||
|
||||
|
||||
@oauth_authorized.connect_via(github_blueprint)
|
||||
def github_logged_in(blueprint, token):
|
||||
if not token:
|
||||
flash(_("Failed to log in with GitHub."), category="error")
|
||||
return False
|
||||
|
||||
resp = blueprint.session.get("/user")
|
||||
if not resp.ok:
|
||||
flash(_("Failed to fetch user info from GitHub."), category="error")
|
||||
return False
|
||||
|
||||
github_info = resp.json()
|
||||
github_user_id = str(github_info["id"])
|
||||
return oauth_update_token(blueprint, token, github_user_id)
|
||||
|
||||
|
||||
@oauth_authorized.connect_via(google_blueprint)
|
||||
def google_logged_in(blueprint, token):
|
||||
if not token:
|
||||
flash(_("Failed to log in with Google."), category="error")
|
||||
return False
|
||||
|
||||
resp = blueprint.session.get("/oauth2/v2/userinfo")
|
||||
if not resp.ok:
|
||||
flash(_("Failed to fetch user info from Google."), category="error")
|
||||
return False
|
||||
|
||||
google_info = resp.json()
|
||||
google_user_id = str(google_info["id"])
|
||||
|
||||
return oauth_update_token(blueprint, token, google_user_id)
|
||||
|
||||
|
||||
def oauth_update_token(blueprint, token, provider_user_id):
|
||||
session[blueprint.name + "_oauth_user_id"] = provider_user_id
|
||||
session[blueprint.name + "_oauth_token"] = token
|
||||
|
||||
# Find this OAuth token in the database, or create it
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
provider=blueprint.name,
|
||||
provider_user_id=provider_user_id,
|
||||
token=token,
|
||||
)
|
||||
try:
|
||||
ub.session.add(oauth)
|
||||
ub.session.commit()
|
||||
except Exception as e:
|
||||
app.logger.exception(e)
|
||||
ub.session.rollback()
|
||||
try:
|
||||
oauth = query.one()
|
||||
# update token
|
||||
oauth.token = token
|
||||
except NoResultFound:
|
||||
oauth = ub.OAuth(
|
||||
provider=blueprint.name,
|
||||
provider_user_id=provider_user_id,
|
||||
token=token,
|
||||
)
|
||||
try:
|
||||
ub.session.add(oauth)
|
||||
ub.session.commit()
|
||||
except Exception as e:
|
||||
app.logger.exception(e)
|
||||
ub.session.rollback()
|
||||
|
||||
# Disable Flask-Dance's default behavior for saving the OAuth token
|
||||
return False
|
||||
# Disable Flask-Dance's default behavior for saving the OAuth token
|
||||
return False
|
||||
|
||||
|
||||
def bind_oauth_or_register(provider, provider_user_id, redirect_url):
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
provider=provider,
|
||||
provider_user_id=provider_user_id,
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
# already bind with user, just login
|
||||
if oauth.user:
|
||||
login_user(oauth.user)
|
||||
return redirect(url_for('index'))
|
||||
else:
|
||||
# bind to current user
|
||||
def bind_oauth_or_register(provider, provider_user_id, redirect_url):
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
provider=provider,
|
||||
provider_user_id=provider_user_id,
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
# already bind with user, just login
|
||||
if oauth.user:
|
||||
login_user(oauth.user)
|
||||
return redirect(url_for('web.index'))
|
||||
else:
|
||||
# bind to current user
|
||||
if current_user and current_user.is_authenticated:
|
||||
oauth.user = current_user
|
||||
try:
|
||||
ub.session.add(oauth)
|
||||
ub.session.commit()
|
||||
except Exception as e:
|
||||
app.logger.exception(e)
|
||||
ub.session.rollback()
|
||||
return redirect(url_for('web.register'))
|
||||
except NoResultFound:
|
||||
return redirect(url_for(redirect_url))
|
||||
|
||||
|
||||
def get_oauth_status():
|
||||
status = []
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
user_id=current_user.id,
|
||||
)
|
||||
try:
|
||||
oauths = query.all()
|
||||
for oauth in oauths:
|
||||
status.append(oauth.provider)
|
||||
return status
|
||||
except NoResultFound:
|
||||
return None
|
||||
|
||||
|
||||
def unlink_oauth(provider):
|
||||
if request.host_url + 'me' != request.referrer:
|
||||
pass
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
provider=provider,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
if current_user and current_user.is_authenticated:
|
||||
oauth.user = current_user
|
||||
try:
|
||||
ub.session.add(oauth)
|
||||
ub.session.delete(oauth)
|
||||
ub.session.commit()
|
||||
logout_oauth_user()
|
||||
flash(_("Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success")
|
||||
except Exception as e:
|
||||
app.logger.exception(e)
|
||||
ub.session.rollback()
|
||||
return redirect(url_for('web.register'))
|
||||
except NoResultFound:
|
||||
return redirect(url_for(redirect_url))
|
||||
flash(_("Unlink to %(oauth)s failed.", oauth=oauth_check[provider]), category="error")
|
||||
except NoResultFound:
|
||||
app.logger.warning("oauth %s for user %d not fount" % (provider, current_user.id))
|
||||
flash(_("Not linked to %(oauth)s.", oauth=oauth_check[provider]), category="error")
|
||||
return redirect(url_for('profile'))
|
||||
|
||||
|
||||
def get_oauth_status():
|
||||
status = []
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
user_id=current_user.id,
|
||||
)
|
||||
try:
|
||||
oauths = query.all()
|
||||
for oauth in oauths:
|
||||
status.append(oauth.provider)
|
||||
return status
|
||||
except NoResultFound:
|
||||
return None
|
||||
# notify on OAuth provider error
|
||||
@oauth_error.connect_via(github_blueprint)
|
||||
def github_error(blueprint, error, error_description=None, error_uri=None):
|
||||
msg = (
|
||||
"OAuth error from {name}! "
|
||||
"error={error} description={description} uri={uri}"
|
||||
).format(
|
||||
name=blueprint.name,
|
||||
error=error,
|
||||
description=error_description,
|
||||
uri=error_uri,
|
||||
)
|
||||
flash(msg, category="error")
|
||||
|
||||
'''
|
||||
@oauth.route('/github')
|
||||
@github_oauth_required
|
||||
def github_login():
|
||||
if not github.authorized:
|
||||
return redirect(url_for('github.login'))
|
||||
account_info = github.get('/user')
|
||||
if account_info.ok:
|
||||
account_info_json = account_info.json()
|
||||
return bind_oauth_or_register(github_blueprint.name, account_info_json['id'], 'github.login')
|
||||
flash(_(u"GitHub Oauth error, please retry later."), category="error")
|
||||
return redirect(url_for('web.login'))
|
||||
|
||||
|
||||
@oauth.route('/unlink/github', methods=["GET"])
|
||||
@login_required
|
||||
def github_login_unlink():
|
||||
return unlink_oauth(github_blueprint.name)
|
||||
|
||||
|
||||
@oauth.route('/google')
|
||||
@google_oauth_required
|
||||
def google_login():
|
||||
if not google.authorized:
|
||||
return redirect(url_for("google.login"))
|
||||
resp = google.get("/oauth2/v2/userinfo")
|
||||
if resp.ok:
|
||||
account_info_json = resp.json()
|
||||
return bind_oauth_or_register(google_blueprint.name, account_info_json['id'], 'google.login')
|
||||
flash(_(u"Google Oauth error, please retry later."), category="error")
|
||||
return redirect(url_for('web.login'))
|
||||
'''
|
||||
|
||||
def unlink_oauth(provider):
|
||||
if request.host_url + 'me' != request.referrer:
|
||||
pass
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
provider=provider,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
if current_user and current_user.is_authenticated:
|
||||
oauth.user = current_user
|
||||
try:
|
||||
ub.session.delete(oauth)
|
||||
ub.session.commit()
|
||||
logout_oauth_user()
|
||||
flash(_("Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success")
|
||||
except Exception as e:
|
||||
app.logger.exception(e)
|
||||
ub.session.rollback()
|
||||
flash(_("Unlink to %(oauth)s failed.", oauth=oauth_check[provider]), category="error")
|
||||
except NoResultFound:
|
||||
app.logger.warning("oauth %s for user %d not fount" % (provider, current_user.id))
|
||||
flash(_("Not linked to %(oauth)s.", oauth=oauth_check[provider]), category="error")
|
||||
return redirect(url_for('profile'))
|
||||
@oauth_error.connect_via(google_blueprint)
|
||||
def google_error(blueprint, error, error_description=None, error_uri=None):
|
||||
msg = (
|
||||
"OAuth error from {name}! "
|
||||
"error={error} description={description} uri={uri}"
|
||||
).format(
|
||||
name=blueprint.name,
|
||||
error=error,
|
||||
description=error_description,
|
||||
uri=error_uri,
|
||||
)
|
||||
flash(msg, category="error")
|
||||
|
||||
|
||||
# notify on OAuth provider error
|
||||
@oauth_error.connect_via(github_blueprint)
|
||||
def github_error(blueprint, error, error_description=None, error_uri=None):
|
||||
msg = (
|
||||
"OAuth error from {name}! "
|
||||
"error={error} description={description} uri={uri}"
|
||||
).format(
|
||||
name=blueprint.name,
|
||||
error=error,
|
||||
description=error_description,
|
||||
uri=error_uri,
|
||||
)
|
||||
flash(msg, category="error")
|
||||
|
||||
'''
|
||||
@oauth.route('/github')
|
||||
@github_oauth_required
|
||||
def github_login():
|
||||
if not github.authorized:
|
||||
return redirect(url_for('github.login'))
|
||||
account_info = github.get('/user')
|
||||
if account_info.ok:
|
||||
account_info_json = account_info.json()
|
||||
return bind_oauth_or_register(github_blueprint.name, account_info_json['id'], 'github.login')
|
||||
flash(_(u"GitHub Oauth error, please retry later."), category="error")
|
||||
return redirect(url_for('login'))
|
||||
|
||||
|
||||
@oauth.route('/unlink/github', methods=["GET"])
|
||||
@login_required
|
||||
def github_login_unlink():
|
||||
return unlink_oauth(github_blueprint.name)
|
||||
|
||||
|
||||
@oauth.route('/google')
|
||||
@google_oauth_required
|
||||
def google_login():
|
||||
if not google.authorized:
|
||||
return redirect(url_for("google.login"))
|
||||
resp = google.get("/oauth2/v2/userinfo")
|
||||
if resp.ok:
|
||||
account_info_json = resp.json()
|
||||
return bind_oauth_or_register(google_blueprint.name, account_info_json['id'], 'google.login')
|
||||
flash(_(u"Google Oauth error, please retry later."), category="error")
|
||||
return redirect(url_for('login'))
|
||||
'''
|
||||
|
||||
@oauth_error.connect_via(google_blueprint)
|
||||
def google_error(blueprint, error, error_description=None, error_uri=None):
|
||||
msg = (
|
||||
"OAuth error from {name}! "
|
||||
"error={error} description={description} uri={uri}"
|
||||
).format(
|
||||
name=blueprint.name,
|
||||
error=error,
|
||||
description=error_description,
|
||||
uri=error_uri,
|
||||
)
|
||||
flash(msg, category="error")
|
||||
|
||||
'''
|
||||
@oauth.route('/unlink/google', methods=["GET"])
|
||||
@login_required
|
||||
def google_login_unlink():
|
||||
return unlink_oauth(google_blueprint.name)'''
|
||||
'''
|
||||
@oauth.route('/unlink/google', methods=["GET"])
|
||||
@login_required
|
||||
def google_login_unlink():
|
||||
return unlink_oauth(google_blueprint.name)'''
|
||||
|
@ -38,7 +38,6 @@ from werkzeug.security import check_password_hash
|
||||
from werkzeug.datastructures import Headers
|
||||
try:
|
||||
from urllib.parse import quote
|
||||
from imp import reload
|
||||
except ImportError:
|
||||
from urllib import quote
|
||||
|
||||
@ -315,7 +314,7 @@ def authenticate():
|
||||
def render_xml_template(*args, **kwargs):
|
||||
#ToDo: return time in current timezone similar to %z
|
||||
currtime = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
||||
xml = render_template(current_time=currtime, *args, **kwargs)
|
||||
xml = render_template(current_time=currtime, instance=config.config_calibre_web_title, *args, **kwargs)
|
||||
response = make_response(xml)
|
||||
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
|
||||
return response
|
||||
|
24
cps/shelf.py
24
cps/shelf.py
@ -40,7 +40,7 @@ def add_to_shelf(shelf_id, book_id):
|
||||
app.logger.info("Invalid shelf specified")
|
||||
if not request.is_xhr:
|
||||
flash(_(u"Invalid shelf specified"), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
return "Invalid shelf specified", 400
|
||||
|
||||
if not shelf.is_public and not shelf.user_id == int(current_user.id):
|
||||
@ -48,14 +48,14 @@ def add_to_shelf(shelf_id, book_id):
|
||||
if not request.is_xhr:
|
||||
flash(_(u"Sorry you are not allowed to add a book to the the shelf: %(shelfname)s", shelfname=shelf.name),
|
||||
category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
return "Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name, 403
|
||||
|
||||
if shelf.is_public and not current_user.role_edit_shelfs():
|
||||
app.logger.info("User is not allowed to edit public shelves")
|
||||
if not request.is_xhr:
|
||||
flash(_(u"You are not allowed to edit public shelves"), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
return "User is not allowed to edit public shelves", 403
|
||||
|
||||
book_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id,
|
||||
@ -64,7 +64,7 @@ def add_to_shelf(shelf_id, book_id):
|
||||
app.logger.info("Book is already part of the shelf: %s" % shelf.name)
|
||||
if not request.is_xhr:
|
||||
flash(_(u"Book is already part of the shelf: %(shelfname)s", shelfname=shelf.name), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
return "Book is already part of the shelf: %s" % shelf.name, 400
|
||||
|
||||
maxOrder = ub.session.query(func.max(ub.BookShelf.order)).filter(ub.BookShelf.shelf == shelf_id).first()
|
||||
@ -81,7 +81,7 @@ def add_to_shelf(shelf_id, book_id):
|
||||
if "HTTP_REFERER" in request.environ:
|
||||
return redirect(request.environ["HTTP_REFERER"])
|
||||
else:
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
return "", 204
|
||||
|
||||
|
||||
@ -92,17 +92,17 @@ def search_to_shelf(shelf_id):
|
||||
if shelf is None:
|
||||
app.logger.info("Invalid shelf specified")
|
||||
flash(_(u"Invalid shelf specified"), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
if not shelf.is_public and not shelf.user_id == int(current_user.id):
|
||||
app.logger.info("You are not allowed to add a book to the the shelf: %s" % shelf.name)
|
||||
flash(_(u"You are not allowed to add a book to the the shelf: %(name)s", name=shelf.name), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
if shelf.is_public and not current_user.role_edit_shelfs():
|
||||
app.logger.info("User is not allowed to edit public shelves")
|
||||
flash(_(u"User is not allowed to edit public shelves"), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
if current_user.id in searched_ids and searched_ids[current_user.id]:
|
||||
books_for_shelf = list()
|
||||
@ -120,7 +120,7 @@ def search_to_shelf(shelf_id):
|
||||
if not books_for_shelf:
|
||||
app.logger.info("Books are already part of the shelf: %s" % shelf.name)
|
||||
flash(_(u"Books are already part of the shelf: %(name)s", name=shelf.name), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
maxOrder = ub.session.query(func.max(ub.BookShelf.order)).filter(ub.BookShelf.shelf == shelf_id).first()
|
||||
if maxOrder[0] is None:
|
||||
@ -146,7 +146,7 @@ def remove_from_shelf(shelf_id, book_id):
|
||||
if shelf is None:
|
||||
app.logger.info("Invalid shelf specified")
|
||||
if not request.is_xhr:
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
return "Invalid shelf specified", 400
|
||||
|
||||
# if shelf is public and use is allowed to edit shelfs, or if shelf is private and user is owner
|
||||
@ -165,7 +165,7 @@ def remove_from_shelf(shelf_id, book_id):
|
||||
if book_shelf is None:
|
||||
app.logger.info("Book already removed from shelf")
|
||||
if not request.is_xhr:
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
return "Book already removed from shelf", 410
|
||||
|
||||
ub.session.delete(book_shelf)
|
||||
@ -180,7 +180,7 @@ def remove_from_shelf(shelf_id, book_id):
|
||||
if not request.is_xhr:
|
||||
flash(_(u"Sorry you are not allowed to remove a book from this shelf: %(sname)s", sname=shelf.name),
|
||||
category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
return "Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name, 403
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
<div class="text-center more-stuff"><h4>{{_('Delete formats:')}}</h4>
|
||||
{% for file in book.data %}
|
||||
<div class="form-group">
|
||||
<a href="{{ url_for('web.delete_book', book_id=book.id, book_format=file.format) }}" class="btn btn-danger" type="button">{{_('Delete')}} - {{file.format}}</a>
|
||||
<a href="{{ url_for('editbook.delete_book', book_id=book.id, book_format=file.format) }}" class="btn btn-danger" type="button">{{_('Delete')}} - {{file.format}}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
{% if source_formats|length > 0 and conversion_formats|length > 0 %}
|
||||
<div class="text-center more-stuff"><h4>{{_('Convert book format:')}}</h4>
|
||||
<form class="padded-bottom" action="{{ url_for('web.convert_bookformat', book_id=book.id) }}" method="post" id="book_convert_frm">
|
||||
<form class="padded-bottom" action="{{ url_for('editbook.convert_bookformat', book_id=book.id) }}" method="post" id="book_convert_frm">
|
||||
<div class="form-group">
|
||||
<div class="text-left">
|
||||
<label class="control-label" for="book_format_from">{{_('Convert from:')}}</label>
|
||||
|
@ -159,7 +159,7 @@
|
||||
<input type="checkbox" id="config_remote_login" name="config_remote_login" {% if content.config_remote_login %}checked{% endif %}>
|
||||
<label for="config_remote_login">{{_('Enable remote login ("magic link")')}}</label>
|
||||
</div>
|
||||
{% if goodreads %}
|
||||
{% if feature_support['goodreads'] %}
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_use_goodreads" name="config_use_goodreads" data-control="goodreads-settings" {% if content.config_use_goodreads %}checked{% endif %}>
|
||||
<label for="config_use_goodreads">{{_('Use')}} Goodreads</label>
|
||||
@ -176,6 +176,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if feature_support['ldap'] %}
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_use_ldap" name="config_use_ldap" data-control="ldap-settings" {% if content.config_use_ldap %}checked{% endif %}>
|
||||
<label for="config_use_ldap">{{_('Use')}} LDAP Authentication</label>
|
||||
@ -190,6 +191,8 @@
|
||||
<input type="text" class="form-control" id="config_ldap_dn" name="config_ldap_dn" value="{% if content.config_use_ldap != None %}{{ content.config_ldap_dn }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if feature_support['oauth'] %}
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_use_github_oauth" name="config_use_github_oauth" data-control="github-oauth-settings" {% if content.config_use_github_oauth %}checked{% endif %}>
|
||||
<label for="config_use_github_oauth">{{_('Use')}} GitHub OAuth</label>
|
||||
@ -220,6 +223,7 @@
|
||||
<input type="text" class="form-control" id="config_google_oauth_client_secret" name="config_google_oauth_client_secret" value="{% if content.config_google_oauth_client_secret != None %}{{ content.config_google_oauth_client_secret }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,9 +57,22 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if reader_list %}
|
||||
<div class="btn-group" role="group">
|
||||
<button id="read-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-eye-open"></span> {{_('Read in browser')}}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="read-in-browser">
|
||||
{% for format in reader_list %}
|
||||
<li><a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=format) }}">{{format}}</a></li>
|
||||
{%endfor%}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if reader_list %}
|
||||
{% if audioentries|length %}
|
||||
<div class="btn-group" role="group">
|
||||
<!--div class="btn-group" role="group">
|
||||
<button id="listen-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-music"></span> {{_('Listen in browser')}}
|
||||
<span class="caret"></span>
|
||||
@ -77,7 +90,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div-->
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
@ -2,13 +2,12 @@
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<id>urn:uuid:2853dacf-ed79-42f5-8e8a-a7bb3d1ae6a2</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<link rel="self" href="{{url_for('feed_index')}}" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="start" title="{{_('Start')}}" href="{{url_for('feed_index')}}"
|
||||
<link rel="self" href="{{url_for('opds.feed_index')}}" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="start" title="{{_('Start')}}" href="{{url_for('opds.feed_index')}}"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="search"
|
||||
href="{{url_for('feed_osd')}}"
|
||||
href="{{url_for('opds.feed_osd')}}"
|
||||
type="application/opensearchdescription+xml"/>
|
||||
<!--link title="{{_('Search')}}" type="application/atom+xml" href="{{url_for('feed_normal_search')}}?query={searchTerms}" rel="search"/-->
|
||||
<title>{{instance}}</title>
|
||||
<author>
|
||||
<name>{{instance}}</name>
|
||||
@ -16,88 +15,88 @@
|
||||
</author>
|
||||
<entry>
|
||||
<title>{{_('Hot Books')}}</title>
|
||||
<link rel="http://opds-spec.org/sort/popular" href="{{url_for('feed_hot')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_hot')}}</id>
|
||||
<link rel="http://opds-spec.org/sort/popular" href="{{url_for('opds.feed_hot')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_hot')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Popular publications from this catalog based on Downloads.')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('Best rated Books')}}</title>
|
||||
<link rel="http://opds-spec.org/recommended" href="{{url_for('feed_best_rated')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_best_rated')}}</id>
|
||||
<link rel="http://opds-spec.org/recommended" href="{{url_for('opds.feed_best_rated')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_best_rated')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Popular publications from this catalog based on Rating.')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('New Books')}}</title>
|
||||
<link rel="http://opds-spec.org/sort/new" href="{{url_for('feed_new')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_new')}}</id>
|
||||
<link rel="http://opds-spec.org/sort/new" href="{{url_for('opds.feed_new')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_new')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('The latest Books')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('Random Books')}}</title>
|
||||
<link rel="http://opds-spec.org/featured" href="{{url_for('feed_discover')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_discover')}}</id>
|
||||
<link rel="http://opds-spec.org/featured" href="{{url_for('opds.feed_discover')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_discover')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Show Random Books')}}</content>
|
||||
</entry>
|
||||
{% if not current_user.is_anonymous %}
|
||||
<entry>
|
||||
<title>{{_('Read Books')}}</title>
|
||||
<link rel="subsection" href="{{url_for('feed_read_books')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_read_books')}}</id>
|
||||
<link rel="subsection" href="{{url_for('opds.feed_read_books')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_read_books')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Read Books')}}</content>
|
||||
</entry>
|
||||
{% endif %}
|
||||
<entry>
|
||||
<title>{{_('Unread Books')}}</title>
|
||||
<link rel="subsection" href="{{url_for('feed_unread_books')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_unread_books')}}</id>
|
||||
<link rel="subsection" href="{{url_for('opds.feed_unread_books')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_unread_books')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Unread Books')}}</content>
|
||||
</entry>
|
||||
{% endif %}
|
||||
<entry>
|
||||
<title>{{_('Authors')}}</title>
|
||||
<link rel="subsection" href="{{url_for('feed_authorindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_authorindex')}}</id>
|
||||
<link rel="subsection" href="{{url_for('opds.feed_authorindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_authorindex')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Books ordered by Author')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('Publishers')}}</title>
|
||||
<link rel="subsection" href="{{url_for('feed_publisherindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_publisherindex')}}</id>
|
||||
<link rel="subsection" href="{{url_for('opds.feed_publisherindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_publisherindex')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Books ordered by publisher')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('Category list')}}</title>
|
||||
<link rel="subsection" href="{{url_for('feed_categoryindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_categoryindex')}}</id>
|
||||
<link rel="subsection" href="{{url_for('opds.feed_categoryindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_categoryindex')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Books ordered by category')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('Series list')}}</title>
|
||||
<link rel="subsection" href="{{url_for('feed_seriesindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_seriesindex')}}</id>
|
||||
<link rel="subsection" href="{{url_for('opds.feed_seriesindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_seriesindex')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Books ordered by series')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('Public Shelves')}}</title>
|
||||
<link rel="subsection" href="{{url_for('feed_shelfindex', public="public")}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_shelfindex', public="public")}}</id>
|
||||
<link rel="subsection" href="{{url_for('opds.feed_shelfindex', public="public")}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_shelfindex', public="public")}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_('Books organized in public shelfs, visible to everyone')}}</content>
|
||||
</entry>
|
||||
{% if not current_user.is_anonymous %}
|
||||
<entry>
|
||||
<title>{{_('Your Shelves')}}</title>
|
||||
<link rel="subsection" href="{{url_for('feed_shelfindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('feed_shelfindex')}}</id>
|
||||
<link rel="subsection" href="{{url_for('opds.feed_shelfindex')}}" type="application/atom+xml;profile=opds-catalog"/>
|
||||
<id>{{url_for('opds.feed_shelfindex')}}</id>
|
||||
<updated>{{ current_time }}</updated>
|
||||
<content type="text">{{_("User's own shelfs, only visible to the current user himself")}}</content>
|
||||
</entry>
|
||||
|
@ -1,53 +1,53 @@
|
||||
{
|
||||
"pubdate": "{{entry.pubdate}}",
|
||||
"title": "{{entry.title}}",
|
||||
"pubdate": "{{entry.pubdate}}",
|
||||
"title": "{{entry.title}}",
|
||||
"format_metadata": {
|
||||
{% for format in entry.data %}
|
||||
{% for format in entry.data %}
|
||||
"{{format.format}}": {
|
||||
"mtime": "{{entry.last_modified}}",
|
||||
"size": {{format.uncompressed_size}},
|
||||
"mtime": "{{entry.last_modified}}",
|
||||
"size": {{format.uncompressed_size}},
|
||||
"path": ""
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
},
|
||||
},
|
||||
"formats": [
|
||||
{% for format in entry.data %}
|
||||
{% for format in entry.data %}
|
||||
"{{format.format}}"{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
"series": null,
|
||||
"cover": "{{url_for('feed_get_cover', book_id=entry.id)}}",
|
||||
{% endfor %}
|
||||
],
|
||||
"series": null,
|
||||
"cover": "{{url_for('opds.feed_get_cover', book_id=entry.id)}}",
|
||||
"languages": [
|
||||
{% for lang in entry.languages %}
|
||||
{% for lang in entry.languages %}
|
||||
"{{lang.lang_code}}"{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
],
|
||||
"comments": "{% if entry.comments|length > 0 %}{{entry.comments[0].text.replace('"', '\\"')|safe}}{% endif %}",
|
||||
"tags": [
|
||||
{% for tag in entry.tags %}
|
||||
"{{tag.name}}"{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
"application_id": {{entry.id}},
|
||||
"series_index": {% if entry.series|length > 0 %}"{{entry.series_index}}"{% else %}null{% endif %},
|
||||
"last_modified": "{{entry.last_modified}}",
|
||||
"author_sort": "{{entry.author_sort}}",
|
||||
"uuid": "{{entry.uuid}}",
|
||||
"timestamp": "{{entry.timestamp}}",
|
||||
"thumbnail": "{{url_for('feed_get_cover', book_id=entry.id)}}",
|
||||
{% endfor %}
|
||||
],
|
||||
"application_id": {{entry.id}},
|
||||
"series_index": {% if entry.series|length > 0 %}"{{entry.series_index}}"{% else %}null{% endif %},
|
||||
"last_modified": "{{entry.last_modified}}",
|
||||
"author_sort": "{{entry.author_sort}}",
|
||||
"uuid": "{{entry.uuid}}",
|
||||
"timestamp": "{{entry.timestamp}}",
|
||||
"thumbnail": "{{url_for('opds.feed_get_cover', book_id=entry.id)}}",
|
||||
"main_format": {
|
||||
"{{entry.data[0].format|lower}}": "{{ url_for('get_opds_download_link', book_id=entry.id, book_format=entry.data[0].format|lower)}}"
|
||||
"{{entry.data[0].format|lower}}": "{{ url_for('opds.get_opds_download_link', book_id=entry.id, book_format=entry.data[0].format|lower)}}"
|
||||
},
|
||||
"rating":{% if entry.ratings.__len__() > 0 %} "{{entry.ratings[0].rating}}.0"{% else %}0.0{% endif %},
|
||||
"authors": [
|
||||
{% for author in entry.authors %}
|
||||
"{{author.name.replace('|',',')}}"{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
{% endfor %}
|
||||
],
|
||||
"other_formats": {
|
||||
{% if entry.data.__len__() > 1 %}
|
||||
{% for format in entry.data[1:] %}
|
||||
"{{format.format|lower}}": "{{ url_for('get_opds_download_link', book_id=entry.id, book_format=format.format|lower)}}"{% if not loop.last %},{% endif %}
|
||||
"{{format.format|lower}}": "{{ url_for('opds.get_opds_download_link', book_id=entry.id, book_format=format.format|lower)}}"{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %} },
|
||||
"title_sort": "{{entry.sort}}"
|
||||
|
@ -161,7 +161,7 @@
|
||||
{% if g.user.is_authenticated or g.user.is_anonymous %}
|
||||
<li class="nav-head hidden-xs public-shelves">{{_('Public Shelves')}}</li>
|
||||
{% for shelf in g.public_shelfes %}
|
||||
<li><a href="{{url_for('web.show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list public_shelf"></span>{{shelf.name|shortentitle(40)}}</a></li>
|
||||
<li><a href="{{url_for('shelf.show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list public_shelf"></span>{{shelf.name|shortentitle(40)}}</a></li>
|
||||
{% endfor %}
|
||||
<li class="nav-head hidden-xs your-shelves">{{_('Your Shelves')}}</li>
|
||||
{% for shelf in g.user.shelf %}
|
||||
|
@ -6,9 +6,9 @@
|
||||
<Developer>Janeczku</Developer>
|
||||
<Contact>https://github.com/janeczku/calibre-web</Contact>
|
||||
<Url type="text/html"
|
||||
template="{{url_for('search')}}?query={searchTerms}"/>
|
||||
template="{{url_for('opds.search')}}?query={searchTerms}"/>
|
||||
<Url type="application/atom+xml"
|
||||
template="{{url_for('feed_normal_search')}}?query={searchTerms}"/>
|
||||
template="{{url_for('opds.feed_normal_search')}}?query={searchTerms}"/>
|
||||
<SyndicationRight>open</SyndicationRight>
|
||||
<Language>{{lang}}</Language>
|
||||
<OutputEncoding>UTF-8</OutputEncoding>
|
||||
|
16
cps/ub.py
16
cps/ub.py
@ -23,7 +23,6 @@ from sqlalchemy import exc
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import *
|
||||
from flask_login import AnonymousUserMixin
|
||||
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
@ -34,8 +33,11 @@ from binascii import hexlify
|
||||
import cli
|
||||
|
||||
try:
|
||||
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
|
||||
import ldap
|
||||
oauth_support = True
|
||||
except ImportError:
|
||||
oauth_support = False
|
||||
pass
|
||||
|
||||
engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False)
|
||||
@ -207,11 +209,11 @@ class User(UserBase, Base):
|
||||
default_language = Column(String(3), default="all")
|
||||
mature_content = Column(Boolean, default=True)
|
||||
|
||||
|
||||
class OAuth(OAuthConsumerMixin, Base):
|
||||
provider_user_id = Column(String(256))
|
||||
user_id = Column(Integer, ForeignKey(User.id))
|
||||
user = relationship(User)
|
||||
if oauth_support:
|
||||
class OAuth(OAuthConsumerMixin, Base):
|
||||
provider_user_id = Column(String(256))
|
||||
user_id = Column(Integer, ForeignKey(User.id))
|
||||
user = relationship(User)
|
||||
|
||||
|
||||
# Class for anonymous user is derived from User base and completly overrides methods and properties for the
|
||||
@ -834,7 +836,7 @@ def delete_download(book_id):
|
||||
session.commit()
|
||||
|
||||
# Generate user Guest (translated text), as anoymous user, no rights
|
||||
def create_anonymous_user(session):
|
||||
def create_anonymous_user():
|
||||
user = User()
|
||||
user.nickname = "Guest"
|
||||
user.email = 'no@email'
|
||||
|
@ -18,11 +18,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from cps import config, get_locale
|
||||
from cps import config, get_locale, Server, app
|
||||
import threading
|
||||
import zipfile
|
||||
import requests
|
||||
import logging
|
||||
import time
|
||||
from io import BytesIO
|
||||
import os
|
||||
@ -35,7 +34,6 @@ import json
|
||||
from flask_babel import gettext as _
|
||||
from babel.dates import format_datetime
|
||||
|
||||
import server
|
||||
|
||||
def is_sha1(sha1):
|
||||
if len(sha1) != 40:
|
||||
@ -69,39 +67,45 @@ class Updater(threading.Thread):
|
||||
def run(self):
|
||||
try:
|
||||
self.status = 1
|
||||
r = requests.get(self._get_request_path(), stream=True)
|
||||
app.logger.debug(u'Download update file')
|
||||
headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||
r = requests.get(self._get_request_path(), stream=True, headers=headers)
|
||||
r.raise_for_status()
|
||||
|
||||
self.status = 2
|
||||
app.logger.debug(u'Opening zipfile')
|
||||
z = zipfile.ZipFile(BytesIO(r.content))
|
||||
self.status = 3
|
||||
app.logger.debug(u'Extracting zipfile')
|
||||
tmp_dir = gettempdir()
|
||||
z.extractall(tmp_dir)
|
||||
foldername = os.path.join(tmp_dir, z.namelist()[0])[:-1]
|
||||
if not os.path.isdir(foldername):
|
||||
self.status = 11
|
||||
logging.getLogger('cps.web').info(u'Extracted contents of zipfile not found in temp folder')
|
||||
app.logger.info(u'Extracted contents of zipfile not found in temp folder')
|
||||
return
|
||||
self.status = 4
|
||||
app.logger.debug(u'Replacing files')
|
||||
self.update_source(foldername, config.get_main_dir)
|
||||
self.status = 6
|
||||
app.logger.debug(u'Preparing restart of server')
|
||||
time.sleep(2)
|
||||
server.Server.setRestartTyp(True)
|
||||
server.Server.stopServer()
|
||||
Server.setRestartTyp(True)
|
||||
Server.stopServer()
|
||||
self.status = 7
|
||||
time.sleep(2)
|
||||
except requests.exceptions.HTTPError as ex:
|
||||
logging.getLogger('cps.web').info( u'HTTP Error' + ' ' + str(ex))
|
||||
app.logger.info( u'HTTP Error' + ' ' + str(ex))
|
||||
self.status = 8
|
||||
except requests.exceptions.ConnectionError:
|
||||
logging.getLogger('cps.web').info(u'Connection error')
|
||||
app.logger.info(u'Connection error')
|
||||
self.status = 9
|
||||
except requests.exceptions.Timeout:
|
||||
logging.getLogger('cps.web').info(u'Timeout while establishing connection')
|
||||
app.logger.info(u'Timeout while establishing connection')
|
||||
self.status = 10
|
||||
except requests.exceptions.RequestException:
|
||||
self.status = 11
|
||||
logging.getLogger('cps.web').info(u'General error')
|
||||
app.logger.info(u'General error')
|
||||
|
||||
def get_update_status(self):
|
||||
return self.status
|
||||
@ -149,14 +153,14 @@ class Updater(threading.Thread):
|
||||
if sys.platform == "win32" or sys.platform == "darwin":
|
||||
change_permissions = False
|
||||
else:
|
||||
logging.getLogger('cps.web').debug('Update on OS-System : ' + sys.platform)
|
||||
app.logger.debug('Update on OS-System : ' + sys.platform)
|
||||
new_permissions = os.stat(root_dst_dir)
|
||||
# print new_permissions
|
||||
for src_dir, __, files in os.walk(root_src_dir):
|
||||
dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
|
||||
if not os.path.exists(dst_dir):
|
||||
os.makedirs(dst_dir)
|
||||
logging.getLogger('cps.web').debug('Create-Dir: '+dst_dir)
|
||||
app.logger.debug('Create-Dir: '+dst_dir)
|
||||
if change_permissions:
|
||||
# print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid))
|
||||
os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid)
|
||||
@ -166,20 +170,20 @@ class Updater(threading.Thread):
|
||||
if os.path.exists(dst_file):
|
||||
if change_permissions:
|
||||
permission = os.stat(dst_file)
|
||||
logging.getLogger('cps.web').debug('Remove file before copy: '+dst_file)
|
||||
app.logger.debug('Remove file before copy: '+dst_file)
|
||||
os.remove(dst_file)
|
||||
else:
|
||||
if change_permissions:
|
||||
permission = new_permissions
|
||||
shutil.move(src_file, dst_dir)
|
||||
logging.getLogger('cps.web').debug('Move File '+src_file+' to '+dst_dir)
|
||||
app.logger.debug('Move File '+src_file+' to '+dst_dir)
|
||||
if change_permissions:
|
||||
try:
|
||||
os.chown(dst_file, permission.st_uid, permission.st_gid)
|
||||
except (Exception) as e:
|
||||
# ex = sys.exc_info()
|
||||
old_permissions = os.stat(dst_file)
|
||||
logging.getLogger('cps.web').debug('Fail change permissions of ' + str(dst_file) + '. Before: '
|
||||
app.logger.debug('Fail change permissions of ' + str(dst_file) + '. Before: '
|
||||
+ str(old_permissions.st_uid) + ':' + str(old_permissions.st_gid) + ' After: '
|
||||
+ str(permission.st_uid) + ':' + str(permission.st_gid) + ' error: '+str(e))
|
||||
return
|
||||
@ -215,15 +219,15 @@ class Updater(threading.Thread):
|
||||
for item in remove_items:
|
||||
item_path = os.path.join(destination, item[1:])
|
||||
if os.path.isdir(item_path):
|
||||
logging.getLogger('cps.web').debug("Delete dir " + item_path)
|
||||
app.logger.debug("Delete dir " + item_path)
|
||||
shutil.rmtree(item_path, ignore_errors=True)
|
||||
else:
|
||||
try:
|
||||
logging.getLogger('cps.web').debug("Delete file " + item_path)
|
||||
app.logger.debug("Delete file " + item_path)
|
||||
# log_from_thread("Delete file " + item_path)
|
||||
os.remove(item_path)
|
||||
except Exception:
|
||||
logging.getLogger('cps.web').debug("Could not remove:" + item_path)
|
||||
app.logger.debug("Could not remove:" + item_path)
|
||||
shutil.rmtree(source, ignore_errors=True)
|
||||
|
||||
def _nightly_version_info(self):
|
||||
@ -263,7 +267,8 @@ class Updater(threading.Thread):
|
||||
status['update'] = True
|
||||
|
||||
try:
|
||||
r = requests.get(repository_url + '/git/commits/' + commit['object']['sha'])
|
||||
headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||
r = requests.get(repository_url + '/git/commits/' + commit['object']['sha'], headers=headers)
|
||||
r.raise_for_status()
|
||||
update_data = r.json()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
@ -310,7 +315,8 @@ class Updater(threading.Thread):
|
||||
# check if we are more than one update behind if so, go up the tree
|
||||
if parent_commit['sha'] != status['current_commit_hash']:
|
||||
try:
|
||||
r = requests.get(parent_commit['url'])
|
||||
headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||
r = requests.get(parent_commit['url'], headers=headers)
|
||||
r.raise_for_status()
|
||||
parent_data = r.json()
|
||||
|
||||
@ -368,7 +374,8 @@ class Updater(threading.Thread):
|
||||
# check if we are more than one update behind if so, go up the tree
|
||||
if commit['sha'] != status['current_commit_hash']:
|
||||
try:
|
||||
r = requests.get(parent_commit['url'])
|
||||
headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||
r = requests.get(parent_commit['url'], headers=headers)
|
||||
r.raise_for_status()
|
||||
parent_data = r.json()
|
||||
|
||||
@ -492,7 +499,8 @@ class Updater(threading.Thread):
|
||||
else:
|
||||
status['current_commit_hash'] = version['version']
|
||||
try:
|
||||
r = requests.get(repository_url)
|
||||
headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||
r = requests.get(repository_url, headers=headers)
|
||||
commit = r.json()
|
||||
r.raise_for_status()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
|
73
cps/web.py
73
cps/web.py
@ -47,18 +47,19 @@ from cps import lm, babel, ub, config, get_locale, language_table, app
|
||||
from pagination import Pagination
|
||||
from sqlalchemy.sql.expression import text
|
||||
|
||||
from oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status
|
||||
|
||||
'''try:
|
||||
oauth_support = True
|
||||
feature_support = dict()
|
||||
try:
|
||||
from oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status
|
||||
feature_support['oauth'] = True
|
||||
except ImportError:
|
||||
oauth_support = False'''
|
||||
feature_support['oauth'] = False
|
||||
oauth_check = {}
|
||||
|
||||
try:
|
||||
import ldap
|
||||
ldap_support = True
|
||||
feature_support['ldap'] = True
|
||||
except ImportError:
|
||||
ldap_support = False
|
||||
feature_support['ldap'] = False
|
||||
|
||||
try:
|
||||
from googleapiclient.errors import HttpError
|
||||
@ -67,15 +68,15 @@ except ImportError:
|
||||
|
||||
try:
|
||||
from goodreads.client import GoodreadsClient
|
||||
goodreads_support = True
|
||||
feature_support['goodreads'] = True
|
||||
except ImportError:
|
||||
goodreads_support = False
|
||||
feature_support['goodreads'] = False
|
||||
|
||||
try:
|
||||
import Levenshtein
|
||||
levenshtein_support = True
|
||||
feature_support['levenshtein'] = True
|
||||
except ImportError:
|
||||
levenshtein_support = False
|
||||
feature_support['levenshtein'] = False
|
||||
|
||||
try:
|
||||
from functools import reduce, wraps
|
||||
@ -84,9 +85,9 @@ except ImportError:
|
||||
|
||||
try:
|
||||
import rarfile
|
||||
rar_support=True
|
||||
feature_support['rar'] = True
|
||||
except ImportError:
|
||||
rar_support=False
|
||||
feature_support['rar'] = False
|
||||
|
||||
try:
|
||||
from natsort import natsorted as sort
|
||||
@ -95,18 +96,17 @@ except ImportError:
|
||||
|
||||
try:
|
||||
from urllib.parse import quote
|
||||
from imp import reload
|
||||
except ImportError:
|
||||
from urllib import quote
|
||||
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
# Global variables
|
||||
|
||||
EXTENSIONS_AUDIO = {'mp3', 'm4a', 'm4b'}
|
||||
|
||||
# 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 feature_support['rar'] else []))'''
|
||||
|
||||
|
||||
# custom error page
|
||||
@ -346,8 +346,8 @@ def before_request():
|
||||
g.allow_upload = config.config_uploading
|
||||
g.current_theme = config.config_theme
|
||||
g.public_shelfes = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1).order_by(ub.Shelf.name).all()
|
||||
if not config.db_configured and request.endpoint not in ('web.basic_configuration', 'login') and '/static/' not in request.path:
|
||||
return redirect(url_for('web.basic_configuration'))
|
||||
if not config.db_configured and request.endpoint not in ('admin.basic_configuration', 'login') and '/static/' not in request.path:
|
||||
return redirect(url_for('admin.basic_configuration'))
|
||||
|
||||
|
||||
@web.route("/ajax/emailstat")
|
||||
@ -373,7 +373,7 @@ def get_comic_book(book_id, book_format, page):
|
||||
if bookformat.format.lower() == book_format.lower():
|
||||
cbr_file = os.path.join(config.config_calibre_dir, book.path, bookformat.name) + "." + book_format
|
||||
if book_format in ("cbr", "rar"):
|
||||
if rar_support == True:
|
||||
if feature_support['rar'] == True:
|
||||
rarfile.UNRAR_TOOL = config.config_rarfile_location
|
||||
try:
|
||||
rf = rarfile.RarFile(cbr_file)
|
||||
@ -636,7 +636,7 @@ def author(book_id, page):
|
||||
|
||||
author_info = None
|
||||
other_books = []
|
||||
if goodreads_support and config.config_use_goodreads:
|
||||
if feature_support['goodreads'] and config.config_use_goodreads:
|
||||
try:
|
||||
gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
|
||||
author_info = gc.find_author(author_name=name)
|
||||
@ -688,7 +688,7 @@ def get_unique_other_books(library_books, author_books):
|
||||
author_books)
|
||||
|
||||
# Fuzzy match book titles
|
||||
if levenshtein_support:
|
||||
if feature_support['levenshtein']:
|
||||
library_titles = reduce(lambda acc, book: acc + [book.title], library_books, [])
|
||||
other_books = filter(lambda author_book: not filter(
|
||||
lambda library_book:
|
||||
@ -1215,7 +1215,7 @@ def read_book(book_id, book_format):
|
||||
# copyfile(cbr_file, tmp_file)
|
||||
return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"),
|
||||
extension=fileext)
|
||||
'''if rar_support == True:
|
||||
'''if feature_support['rar']:
|
||||
extensionList = ["cbr","cbt","cbz"]
|
||||
else:
|
||||
extensionList = ["cbt","cbz"]
|
||||
@ -1292,7 +1292,7 @@ def register():
|
||||
try:
|
||||
ub.session.add(content)
|
||||
ub.session.commit()
|
||||
if oauth_support:
|
||||
if feature_support['oauth']:
|
||||
register_user_with_oauth(content)
|
||||
helper.send_registration_mail(to_save["email"], to_save["nickname"], password)
|
||||
except Exception:
|
||||
@ -1310,7 +1310,7 @@ def register():
|
||||
flash(_(u"This username or e-mail address is already in use."), category="error")
|
||||
return render_title_template('register.html', title=_(u"register"), page="register")
|
||||
|
||||
if oauth_support:
|
||||
if feature_support['oauth']:
|
||||
register_user_with_oauth()
|
||||
return render_title_template('register.html', config=config, title=_(u"register"), page="register")
|
||||
|
||||
@ -1318,7 +1318,7 @@ def register():
|
||||
@web.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if not config.db_configured:
|
||||
return redirect(url_for('web.basic_configuration'))
|
||||
return redirect(url_for('admin.basic_configuration'))
|
||||
if current_user is not None and current_user.is_authenticated:
|
||||
return redirect(url_for('web.index'))
|
||||
if request.method == "POST":
|
||||
@ -1358,7 +1358,7 @@ def login():
|
||||
def logout():
|
||||
if current_user is not None and current_user.is_authenticated:
|
||||
logout_user()
|
||||
if oauth_support:
|
||||
if feature_support['oauth']:
|
||||
logout_oauth_user()
|
||||
return redirect(url_for('web.login'))
|
||||
|
||||
@ -1370,7 +1370,7 @@ def remote_login():
|
||||
ub.session.add(auth_token)
|
||||
ub.session.commit()
|
||||
|
||||
verify_url = url_for('verify_token', token=auth_token.auth_token, _external=true)
|
||||
verify_url = url_for('web.verify_token', token=auth_token.auth_token, _external=true)
|
||||
|
||||
return render_title_template('remote_login.html', title=_(u"login"), token=auth_token.auth_token,
|
||||
verify_url=verify_url, page="remotelogin")
|
||||
@ -1385,7 +1385,7 @@ def verify_token(token):
|
||||
# Token not found
|
||||
if auth_token is None:
|
||||
flash(_(u"Token not found"), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
# Token expired
|
||||
if datetime.datetime.now() > auth_token.expiration:
|
||||
@ -1393,7 +1393,7 @@ def verify_token(token):
|
||||
ub.session.commit()
|
||||
|
||||
flash(_(u"Token has expired"), category="error")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
# Update token with user information
|
||||
auth_token.user_id = current_user.id
|
||||
@ -1401,7 +1401,7 @@ def verify_token(token):
|
||||
ub.session.commit()
|
||||
|
||||
flash(_(u"Success! Please return to your device"), category="success")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
|
||||
@web.route('/ajax/verify_token', methods=['POST'])
|
||||
@ -1472,7 +1472,10 @@ def profile():
|
||||
downloads = list()
|
||||
languages = speaking_language()
|
||||
translations = babel.list_translations() + [LC('en')]
|
||||
oauth_status = get_oauth_status()
|
||||
if feature_support['oauth']:
|
||||
oauth_status = get_oauth_status()
|
||||
else:
|
||||
oauth_status = None
|
||||
for book in content.downloads:
|
||||
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
|
||||
if downloadBook:
|
||||
@ -1535,9 +1538,11 @@ def profile():
|
||||
ub.session.rollback()
|
||||
flash(_(u"Found an existing account for this e-mail address."), category="error")
|
||||
return render_title_template("user_edit.html", content=content, downloads=downloads,
|
||||
title=_(u"%(name)s's profile", name=current_user.nickname, registered_oauth=oauth_check, oauth_status=oauth_status))
|
||||
title=_(u"%(name)s's profile", name=current_user.nickname,
|
||||
registered_oauth=oauth_check, oauth_status=oauth_status))
|
||||
flash(_(u"Profile updated"), category="success")
|
||||
return render_title_template("user_edit.html", translations=translations, profile=1, languages=languages,
|
||||
content=content, downloads=downloads, title=_(u"%(name)s's profile",
|
||||
name=current_user.nickname), page="me", registered_oauth=oauth_check, oauth_status=oauth_status)
|
||||
content=content, downloads=downloads, title=_(u"%(name)s's profile",
|
||||
name=current_user.nickname), page="me", registered_oauth=oauth_check,
|
||||
oauth_status=oauth_status)
|
||||
|
||||
|
@ -27,14 +27,12 @@ import socket
|
||||
import sys
|
||||
import os
|
||||
from email.generator import Generator
|
||||
from cps import config, db # , app
|
||||
# import web
|
||||
from cps import config, db, app
|
||||
from flask_babel import gettext as _
|
||||
import re
|
||||
# import gdriveutils as gd
|
||||
import gdriveutils as gd
|
||||
from subproc_wrapper import process_open
|
||||
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
from email.MIMEBase import MIMEBase
|
||||
@ -90,8 +88,8 @@ def get_attachment(bookpath, filename):
|
||||
data = file_.read()
|
||||
file_.close()
|
||||
except IOError as e:
|
||||
# web.app.logger.exception(e) # traceback.print_exc()
|
||||
# web.app.logger.error(u'The requested file could not be read. Maybe wrong permissions?')
|
||||
app.logger.exception(e) # traceback.print_exc()
|
||||
app.logger.error(u'The requested file could not be read. Maybe wrong permissions?')
|
||||
return None
|
||||
|
||||
attachment = MIMEBase('application', 'octet-stream')
|
||||
@ -116,8 +114,7 @@ class emailbase():
|
||||
|
||||
def send(self, strg):
|
||||
"""Send `strg' to the server."""
|
||||
if self.debuglevel > 0:
|
||||
print('send:', repr(strg[:300]), file=sys.stderr)
|
||||
app.logger.debug('send:' + repr(strg[:300]))
|
||||
if hasattr(self, 'sock') and self.sock:
|
||||
try:
|
||||
if self.transferSize:
|
||||
@ -141,6 +138,9 @@ class emailbase():
|
||||
else:
|
||||
raise smtplib.SMTPServerDisconnected('please run connect() first')
|
||||
|
||||
def _print_debug(self, *args):
|
||||
app.logger.debug(args)
|
||||
|
||||
def getTransferStatus(self):
|
||||
if self.transferSize:
|
||||
lock2 = threading.Lock()
|
||||
@ -254,14 +254,14 @@ class WorkerThread(threading.Thread):
|
||||
# if it does - mark the conversion task as complete and return a success
|
||||
# this will allow send to kindle workflow to continue to work
|
||||
if os.path.isfile(file_path + format_new_ext):
|
||||
# web.app.logger.info("Book id %d already converted to %s", bookid, format_new_ext)
|
||||
app.logger.info("Book id %d already converted to %s", bookid, format_new_ext)
|
||||
cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first()
|
||||
self.queue[self.current]['path'] = file_path
|
||||
self.queue[self.current]['title'] = cur_book.title
|
||||
self._handleSuccess()
|
||||
return file_path + format_new_ext
|
||||
else:
|
||||
web.app.logger.info("Book id %d - target format of %s does not exist. Moving forward with convert.", bookid, format_new_ext)
|
||||
app.logger.info("Book id %d - target format of %s does not exist. Moving forward with convert.", bookid, format_new_ext)
|
||||
|
||||
# check if converter-executable is existing
|
||||
if not os.path.exists(config.config_converterpath):
|
||||
@ -274,22 +274,22 @@ class WorkerThread(threading.Thread):
|
||||
if format_old_ext == '.epub' and format_new_ext == '.mobi':
|
||||
if config.config_ebookconverter == 1:
|
||||
'''if os.name == 'nt':
|
||||
command = web.ub.config.config_converterpath + u' "' + file_path + u'.epub"'
|
||||
command = config.config_converterpath + u' "' + file_path + u'.epub"'
|
||||
if sys.version_info < (3, 0):
|
||||
command = command.encode(sys.getfilesystemencoding())
|
||||
else:'''
|
||||
command = [config.config_converterpath, file_path + u'.epub']
|
||||
quotes = (1)
|
||||
quotes = [1]
|
||||
if config.config_ebookconverter == 2:
|
||||
# Linux py2.7 encode as list without quotes no empty element for parameters
|
||||
# linux py3.x no encode and as list without quotes no empty element for parameters
|
||||
# windows py2.7 encode as string with quotes empty element for parameters is okay
|
||||
# windows py 3.x no encode and as string with quotes empty element for parameters is okay
|
||||
# separate handling for windows and linux
|
||||
quotes = (1,2)
|
||||
quotes = [1,2]
|
||||
'''if os.name == 'nt':
|
||||
command = web.ub.config.config_converterpath + u' "' + file_path + format_old_ext + u'" "' + \
|
||||
file_path + format_new_ext + u'" ' + web.ub.config.config_calibre
|
||||
command = config.config_converterpath + u' "' + file_path + format_old_ext + u'" "' + \
|
||||
file_path + format_new_ext + u'" ' + config.config_calibre
|
||||
if sys.version_info < (3, 0):
|
||||
command = command.encode(sys.getfilesystemencoding())
|
||||
else:'''
|
||||
@ -317,13 +317,13 @@ class WorkerThread(threading.Thread):
|
||||
if conv_error:
|
||||
error_message = _(u"Kindlegen failed with Error %(error)s. Message: %(message)s",
|
||||
error=conv_error.group(1), message=conv_error.group(2).strip())
|
||||
web.app.logger.debug("convert_kindlegen: " + nextline)
|
||||
app.logger.debug("convert_kindlegen: " + nextline)
|
||||
else:
|
||||
while p.poll() is None:
|
||||
nextline = p.stdout.readline()
|
||||
if os.name == 'nt' and sys.version_info < (3, 0):
|
||||
nextline = nextline.decode('windows-1252')
|
||||
web.app.logger.debug(nextline.strip('\r\n'))
|
||||
app.logger.debug(nextline.strip('\r\n'))
|
||||
# parse progress string from calibre-converter
|
||||
progress = re.search("(\d+)%\s.*", nextline)
|
||||
if progress:
|
||||
@ -353,7 +353,7 @@ class WorkerThread(threading.Thread):
|
||||
return file_path + format_new_ext
|
||||
else:
|
||||
error_message = format_new_ext.upper() + ' format not found on disk'
|
||||
# web.app.logger.info("ebook converter failed with error while converting book")
|
||||
app.logger.info("ebook converter failed with error while converting book")
|
||||
if not error_message:
|
||||
error_message = 'Ebook converter failed with unknown error'
|
||||
self._handleError(error_message)
|
||||
@ -414,7 +414,6 @@ class WorkerThread(threading.Thread):
|
||||
def _send_raw_email(self):
|
||||
self.queue[self.current]['starttime'] = datetime.now()
|
||||
self.UIqueue[self.current]['formStarttime'] = self.queue[self.current]['starttime']
|
||||
# self.queue[self.current]['status'] = STAT_STARTED
|
||||
self.UIqueue[self.current]['stat'] = STAT_STARTED
|
||||
obj=self.queue[self.current]
|
||||
# create MIME message
|
||||
@ -446,8 +445,11 @@ class WorkerThread(threading.Thread):
|
||||
# send email
|
||||
timeout = 600 # set timeout to 5mins
|
||||
|
||||
org_stderr = sys.stderr
|
||||
sys.stderr = StderrLogger()
|
||||
# redirect output to logfile on python2 pn python3 debugoutput is caught with overwritten
|
||||
# _print_debug function
|
||||
if sys.version_info < (3, 0):
|
||||
org_smtpstderr = smtplib.stderr
|
||||
smtplib.stderr = StderrLogger()
|
||||
|
||||
if use_ssl == 2:
|
||||
self.asyncSMTP = email_SSL(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout)
|
||||
@ -455,7 +457,7 @@ class WorkerThread(threading.Thread):
|
||||
self.asyncSMTP = email(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout)
|
||||
|
||||
# link to logginglevel
|
||||
if web.ub.config.config_log_level != logging.DEBUG:
|
||||
if config.config_log_level != logging.DEBUG:
|
||||
self.asyncSMTP.set_debuglevel(0)
|
||||
else:
|
||||
self.asyncSMTP.set_debuglevel(1)
|
||||
@ -466,7 +468,9 @@ class WorkerThread(threading.Thread):
|
||||
self.asyncSMTP.sendmail(obj['settings']["mail_from"], obj['recipent'], msg)
|
||||
self.asyncSMTP.quit()
|
||||
self._handleSuccess()
|
||||
sys.stderr = org_stderr
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
smtplib.stderr = org_smtpstderr
|
||||
|
||||
except (MemoryError) as e:
|
||||
self._handleError(u'Error sending email: ' + e.message)
|
||||
@ -497,7 +501,7 @@ class WorkerThread(threading.Thread):
|
||||
return retVal
|
||||
|
||||
def _handleError(self, error_message):
|
||||
web.app.logger.error(error_message)
|
||||
app.logger.error(error_message)
|
||||
# self.queue[self.current]['status'] = STAT_FAIL
|
||||
self.UIqueue[self.current]['stat'] = STAT_FAIL
|
||||
self.UIqueue[self.current]['progress'] = "100 %"
|
||||
@ -519,13 +523,12 @@ class StderrLogger(object):
|
||||
buffer = ''
|
||||
|
||||
def __init__(self):
|
||||
self.logger = web.app.logger
|
||||
self.logger = app.logger
|
||||
|
||||
def write(self, message):
|
||||
try:
|
||||
if message == '\n':
|
||||
self.logger.debug(self.buffer)
|
||||
print(self.buffer)
|
||||
self.logger.debug(self.buffer.replace("\n","\\n"))
|
||||
self.buffer = ''
|
||||
else:
|
||||
self.buffer += message
|
||||
|
@ -11,12 +11,19 @@ PyDrive==1.3.1
|
||||
PyYAML==3.12
|
||||
rsa==3.4.2
|
||||
six==1.10.0
|
||||
|
||||
# goodreads
|
||||
goodreads>=0.3.2
|
||||
python-Levenshtein>=0.12.0
|
||||
|
||||
# ldap login
|
||||
python_ldap>=3.0.0
|
||||
|
||||
# other
|
||||
lxml>=3.8.0
|
||||
rarfile>=2.7
|
||||
natsort>=2.2.0
|
||||
|
||||
# Oauth Login
|
||||
flask-dance>=0.13.0
|
||||
sqlalchemy_utils>=0.33.5
|
||||
|
@ -13,5 +13,3 @@ SQLAlchemy>=1.1.0
|
||||
tornado>=4.1
|
||||
Wand>=0.4.4
|
||||
unidecode>=0.04.19
|
||||
flask-dance>=0.13.0
|
||||
sqlalchemy_utils>=0.33.5
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user