Commit 82d01c90 authored by Jack Darlington's avatar Jack Darlington

Merge branch 'master' into develop

# Conflicts:
#	cps/web.py
parents ff0e0be2 a7cd993c
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
import os import os
import sys import sys
import time
base_path = os.path.dirname(os.path.abspath(__file__)) base_path = os.path.dirname(os.path.abspath(__file__))
# Insert local directories into path # Insert local directories into path
...@@ -22,9 +21,9 @@ if __name__ == '__main__': ...@@ -22,9 +21,9 @@ if __name__ == '__main__':
http_server.listen(web.ub.config.config_port) http_server.listen(web.ub.config.config_port)
IOLoop.instance().start() IOLoop.instance().start()
if web.global_task == 0: if web.helper.global_task == 0:
print("Performing restart of Calibre-web") web.app.logger.info("Performing restart of Calibre-web")
os.execl(sys.executable, sys.executable, *sys.argv) os.execl(sys.executable, sys.executable, *sys.argv)
else: else:
print("Performing shutdown of Calibre-web") web.app.logger.info("Performing shutdown of Calibre-web")
sys.exit(0) sys.exit(0)
...@@ -56,6 +56,10 @@ books_languages_link = Table('books_languages_link', Base.metadata, ...@@ -56,6 +56,10 @@ books_languages_link = Table('books_languages_link', Base.metadata,
Column('lang_code', Integer, ForeignKey('languages.id'), primary_key=True) Column('lang_code', Integer, ForeignKey('languages.id'), primary_key=True)
) )
books_publishers_link = Table('books_publishers_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('publisher', Integer, ForeignKey('publishers.id'), primary_key=True)
)
class Identifiers(Base): class Identifiers(Base):
__tablename__ = 'identifiers' __tablename__ = 'identifiers'
...@@ -182,6 +186,21 @@ class Languages(Base): ...@@ -182,6 +186,21 @@ class Languages(Base):
def __repr__(self): def __repr__(self):
return u"<Languages('{0}')>".format(self.lang_code) return u"<Languages('{0}')>".format(self.lang_code)
class Publishers(Base):
__tablename__ = 'publishers'
id = Column(Integer, primary_key=True)
name = Column(String)
sort = Column(String)
def __init__(self, name,sort):
self.name = name
self.sort = sort
def __repr__(self):
return u"<Publishers('{0},{1}')>".format(self.name, self.sort)
class Data(Base): class Data(Base):
__tablename__ = 'data' __tablename__ = 'data'
...@@ -224,6 +243,7 @@ class Books(Base): ...@@ -224,6 +243,7 @@ class Books(Base):
series = relationship('Series', secondary=books_series_link, backref='books') series = relationship('Series', secondary=books_series_link, backref='books')
ratings = relationship('Ratings', secondary=books_ratings_link, backref='books') ratings = relationship('Ratings', secondary=books_ratings_link, backref='books')
languages = relationship('Languages', secondary=books_languages_link, backref='books') languages = relationship('Languages', secondary=books_languages_link, backref='books')
publishers = relationship('Publishers', secondary=books_publishers_link, backref='books')
identifiers = relationship('Identifiers', backref='books') identifiers = relationship('Identifiers', backref='books')
def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover, def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover,
......
...@@ -6,7 +6,7 @@ import ub ...@@ -6,7 +6,7 @@ import ub
from flask import current_app as app from flask import current_app as app
import logging import logging
import smtplib import smtplib
import tempfile from tempfile import gettempdir
import socket import socket
import sys import sys
import os import os
...@@ -21,13 +21,22 @@ from email.MIMEText import MIMEText ...@@ -21,13 +21,22 @@ from email.MIMEText import MIMEText
from email.generator import Generator from email.generator import Generator
from flask_babel import gettext as _ from flask_babel import gettext as _
import subprocess import subprocess
import threading
import shutil import shutil
import requests
import zipfile
from tornado.ioloop import IOLoop
try: try:
import unidecode import unidecode
use_unidecode=True use_unidecode=True
except: except:
use_unidecode=False use_unidecode=False
# Global variables
global_task = None
updater_thread = None
def update_download(book_id, user_id): def update_download(book_id, user_id):
check = ub.session.query(ub.Downloads).filter(ub.Downloads.user_id == user_id).filter(ub.Downloads.book_id == check = ub.session.query(ub.Downloads).filter(ub.Downloads.user_id == user_id).filter(ub.Downloads.book_id ==
book_id).first() book_id).first()
...@@ -267,14 +276,46 @@ def update_dir_stucture(book_id, calibrepath): ...@@ -267,14 +276,46 @@ def update_dir_stucture(book_id, calibrepath):
book.path = new_authordir + '/' + book.path.split('/')[1] book.path = new_authordir + '/' + book.path.split('/')[1]
db.session.commit() db.session.commit()
class Updater(threading.Thread):
def file_to_list(file): def __init__(self):
threading.Thread.__init__(self)
self.status=0
def run(self):
global global_task
self.status=1
r = requests.get('https://api.github.com/repos/janeczku/calibre-web/zipball/master', stream=True)
fname = re.findall("filename=(.+)", r.headers['content-disposition'])[0]
self.status=2
z = zipfile.ZipFile(StringIO(r.content))
self.status=3
tmp_dir = gettempdir()
z.extractall(tmp_dir)
self.status=4
self.update_source(os.path.join(tmp_dir,os.path.splitext(fname)[0]),ub.config.get_main_dir)
self.status=5
global_task = 0
db.session.close()
db.engine.dispose()
ub.session.close()
ub.engine.dispose()
self.status=6
# stop tornado server
server = IOLoop.instance()
server.add_callback(server.stop)
self.status=7
def get_update_status(self):
return self.status
def file_to_list(self, file):
return [x.strip() for x in open(file, 'r') if not x.startswith('#EXT')] return [x.strip() for x in open(file, 'r') if not x.startswith('#EXT')]
def one_minus_two(one, two): def one_minus_two(self, one, two):
return [x for x in one if x not in set(two)] return [x for x in one if x not in set(two)]
def reduce_dirs(delete_files, new_list): def reduce_dirs(self, delete_files, new_list):
new_delete = [] new_delete = []
for file in delete_files: for file in delete_files:
parts = file.split(os.sep) parts = file.split(os.sep)
...@@ -294,57 +335,57 @@ def reduce_dirs(delete_files, new_list): ...@@ -294,57 +335,57 @@ def reduce_dirs(delete_files, new_list):
break break
return list(set(new_delete)) return list(set(new_delete))
def reduce_files(remove_items, exclude_items): def reduce_files(self, remove_items, exclude_items):
rf = [] rf = []
for item in remove_items: for item in remove_items:
if not item in exclude_items: if not item.startswith(exclude_items):
rf.append(item) rf.append(item)
return rf return rf
def moveallfiles(root_src_dir, root_dst_dir): def moveallfiles(self, root_src_dir, root_dst_dir):
change_permissions = True change_permissions = True
if sys.platform == "win32" or sys.platform == "darwin": if sys.platform == "win32" or sys.platform == "darwin":
change_permissions=False change_permissions = False
else: else:
app.logger.debug('Update on OS-System : '+sys.platform ) logging.getLogger('cps.web').debug('Update on OS-System : ' + sys.platform)
#print('OS-System: '+sys.platform ) new_permissions = os.stat(root_dst_dir)
new_permissions=os.stat(root_dst_dir) # print new_permissions
#print new_permissions
for src_dir, dirs, files in os.walk(root_src_dir): for src_dir, dirs, files in os.walk(root_src_dir):
dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1) dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
if not os.path.exists(dst_dir): if not os.path.exists(dst_dir):
os.makedirs(dst_dir) os.makedirs(dst_dir)
#print('Create-Dir: '+dst_dir) logging.getLogger('cps.web').debug('Create-Dir: '+dst_dir)
if change_permissions: if change_permissions:
#print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid)) # 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) os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid)
for file_ in files: for file_ in files:
src_file = os.path.join(src_dir, file_) src_file = os.path.join(src_dir, file_)
dst_file = os.path.join(dst_dir, file_) dst_file = os.path.join(dst_dir, file_)
if os.path.exists(dst_file): if os.path.exists(dst_file):
if change_permissions: if change_permissions:
permission=os.stat(dst_file) permission = os.stat(dst_file)
#print('Remove file before copy: '+dst_file) logging.getLogger('cps.web').debug('Remove file before copy: '+dst_file)
os.remove(dst_file) os.remove(dst_file)
else: else:
if change_permissions: if change_permissions:
permission=new_permissions permission = new_permissions
shutil.move(src_file, dst_dir) shutil.move(src_file, dst_dir)
#print('Move File '+src_file+' to '+dst_dir) logging.getLogger('cps.web').debug('Move File '+src_file+' to '+dst_dir)
if change_permissions: if change_permissions:
try: try:
os.chown(dst_file, permission.st_uid, permission.st_uid) os.chown(dst_file, permission.st_uid, permission.st_uid)
#print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid)) # print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid))
except: except:
e = sys.exc_info() e = sys.exc_info()
#print('Fail '+str(dst_file)+' error: '+str(e)) logging.getLogger('cps.web').debug('Fail '+str(dst_file)+' error: '+str(e))
return return
def update_source(self, source, destination):
def update_source(source,destination):
# destination files # destination files
old_list=list() old_list = list()
exclude = (['vendor' + os.sep + 'kindlegen.exe','vendor' + os.sep + 'kindlegen','/app.db','vendor','/update.py']) exclude = (
'vendor' + os.sep + 'kindlegen.exe', 'vendor' + os.sep + 'kindlegen', os.sep + 'app.db',
os.sep + 'vendor',os.sep + 'calibre-web.log')
for root, dirs, files in os.walk(destination, topdown=True): for root, dirs, files in os.walk(destination, topdown=True):
for name in files: for name in files:
old_list.append(os.path.join(root, name).replace(destination, '')) old_list.append(os.path.join(root, name).replace(destination, ''))
...@@ -358,26 +399,25 @@ def update_source(source,destination): ...@@ -358,26 +399,25 @@ def update_source(source,destination):
for name in dirs: for name in dirs:
new_list.append(os.path.join(root, name).replace(source, '')) new_list.append(os.path.join(root, name).replace(source, ''))
delete_files = one_minus_two(old_list, new_list) delete_files = self.one_minus_two(old_list, new_list)
#print('raw delete list', delete_files)
rf= reduce_files(delete_files, exclude) rf = self.reduce_files(delete_files, exclude)
#print('reduced delete list', rf)
remove_items = reduce_dirs(rf, new_list) remove_items = self.reduce_dirs(rf, new_list)
#print('delete files', remove_items)
moveallfiles(source, destination) self.moveallfiles(source, destination)
for item in remove_items: for item in remove_items:
item_path = os.path.join(destination, item[1:]) item_path = os.path.join(destination, item[1:])
if os.path.isdir(item_path): if os.path.isdir(item_path):
print("Delete dir "+ item_path) logging.getLogger('cps.web').debug("Delete dir " + item_path)
shutil.rmtree(item_path) shutil.rmtree(item_path)
else: else:
try: try:
print("Delete file "+ item_path) logging.getLogger('cps.web').debug("Delete file " + item_path)
log_from_thread("Delete file " + item_path)
os.remove(item_path) os.remove(item_path)
except: except:
print("Could not remove:"+item_path) logging.getLogger('cps.web').debug("Could not remove:" + item_path)
shutil.rmtree(source, ignore_errors=True) shutil.rmtree(source, ignore_errors=True)
...@@ -47,3 +47,7 @@ span.glyphicon.glyphicon-tags {padding-right: 5px;color: #999;vertical-align: te ...@@ -47,3 +47,7 @@ span.glyphicon.glyphicon-tags {padding-right: 5px;color: #999;vertical-align: te
.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open .dropdown-toggle.btn-primary{ background-color: #1C5484; } .btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open .dropdown-toggle.btn-primary{ background-color: #1C5484; }
.btn-primary.disabled, .btn-primary[disabled], fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active, .btn-primary.disabled.active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary.active { background-color: #89B9E2; } .btn-primary.disabled, .btn-primary[disabled], fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active, .btn-primary.disabled.active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary.active { background-color: #89B9E2; }
.btn-toolbar>.btn+.btn, .btn-toolbar>.btn-group+.btn, .btn-toolbar>.btn+.btn-group, .btn-toolbar>.btn-group+.btn-group { margin-left:0px; } .btn-toolbar>.btn+.btn, .btn-toolbar>.btn-group+.btn, .btn-toolbar>.btn+.btn-group, .btn-toolbar>.btn-group+.btn-group { margin-left:0px; }
.spinner {margin:0 41%;}
.spinner2 {margin:0 41%;}
var displaytext;
var updateTimerID;
var updateText;
$(function() { $(function() {
$('.discover .row').isotope({ $('.discover .row').isotope({
...@@ -31,7 +34,9 @@ $(function() { ...@@ -31,7 +34,9 @@ $(function() {
url: window.location.pathname+"/../../shutdown", url: window.location.pathname+"/../../shutdown",
data: {"parameter":0}, data: {"parameter":0},
success: function(data) { success: function(data) {
return alert(data.text);} $('#spinner').show();
displaytext=data.text;
window.setTimeout(restartTimer, 3000);}
}); });
}); });
$("#shutdown").click(function() { $("#shutdown").click(function() {
...@@ -50,17 +55,66 @@ $(function() { ...@@ -50,17 +55,66 @@ $(function() {
dataType: 'json', dataType: 'json',
url: window.location.pathname+"/../../get_update_status", url: window.location.pathname+"/../../get_update_status",
success: function(data) { success: function(data) {
$("#check_for_update").html(button_text);
if (data.status == true) { if (data.status == true) {
$("#check_for_update").addClass('hidden'); $("#check_for_update").addClass('hidden');
$("#perform_update").removeClass('hidden'); $("#perform_update").removeClass('hidden');
}else{ $("#update_info").removeClass('hidden');
$("#check_for_update").html(button_text); $("#update_info").find('span').html(data.commit);
};} }
}
});
});
$("#perform_update").click(function() {
$('#spinner2').show();
$.ajax({
type: "POST",
dataType: 'json',
data: { start: "True"},
url: window.location.pathname+"/../../get_updater_status",
success: function(data) {
updateText=data.text
$("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]);
console.log(data.status);
updateTimerID=setInterval(updateTimer, 2000);}
}); });
}); });
}); });
function restartTimer() {
$('#spinner').hide();
$('#RestartDialog').modal('hide');
}
function updateTimer() {
$.ajax({
dataType: 'json',
url: window.location.pathname+"/../../get_updater_status",
success: function(data) {
console.log(data.status);
$("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]);
if (data.status >6){
clearInterval(updateTimerID);
$('#spinner2').hide();
$('#UpdateprogressDialog #updateFinished').removeClass('hidden');
$("#check_for_update").removeClass('hidden');
$("#perform_update").addClass('hidden');
}
},
error: function() {
console.log('Done');
clearInterval(updateTimerID);
$('#spinner2').hide();
$("#UpdateprogressDialog #Updatecontent").html(updateText[7]);
$('#UpdateprogressDialog #updateFinished').removeClass('hidden');
$("#check_for_update").removeClass('hidden');
$("#perform_update").addClass('hidden');
}
});
}
$(window).resize(function(event) { $(window).resize(function(event) {
$('.discover .row').isotope('reLayout'); $('.discover .row').isotope('reLayout');
}); });
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
{% block body %} {% block body %}
<div class="discover"> <div class="discover">
<h2>{{_('User list')}}</h2> <h2>{{_('User list')}}</h2>
<div id="divLoading"></div>
<table class="table table-striped" id="table_user"> <table class="table table-striped" id="table_user">
<tr> <tr>
<th>{{_('Nickname')}}</th> <th>{{_('Nickname')}}</th>
...@@ -76,11 +77,13 @@ ...@@ -76,11 +77,13 @@
<div class="btn btn-default"><a href="{{url_for('configuration')}}">{{_('Configuration')}}</a></div> <div class="btn btn-default"><a href="{{url_for('configuration')}}">{{_('Configuration')}}</a></div>
<h2>{{_('Administration')}}</h2> <h2>{{_('Administration')}}</h2>
{% if not development %} {% if not development %}
<p>{{_('Current commit timestamp')}}: {{commit}} </p> <div>{{_('Current commit timestamp')}}: <span>{{commit}} </span></div>
<div class="btn btn-default" data-toggle="modal" data-target="#RestartDialog">{{_('Restart Calibre-web')}}</a></div> <div class="hidden" id="update_info">{{_('Newest commit timestamp')}}: <span></span></div>
<div class="btn btn-default" data-toggle="modal" data-target="#ShutdownDialog">{{_('Stop Calibre-web')}}</a></div> <p></p>
<div class="btn btn-default" id="check_for_update">{{_('Check for update')}}</a></div> <div class="btn btn-default" data-toggle="modal" data-target="#RestartDialog">{{_('Restart Calibre-web')}}</div>
<a href="{{url_for('update')}}" class="btn btn-default hidden" id="perform_update">{{_('Perform Update')}}</a> <div class="btn btn-default" data-toggle="modal" data-target="#ShutdownDialog">{{_('Stop Calibre-web')}}</div>
<div class="btn btn-default" id="check_for_update">{{_('Check for update')}}</div>
<div class="btn btn-default hidden" id="perform_update" data-toggle="modal" data-target="#UpdateprogressDialog">{{_('Perform Update')}}</div>
{% endif %} {% endif %}
</div> </div>
<!-- Modal --> <!-- Modal -->
...@@ -88,15 +91,17 @@ ...@@ -88,15 +91,17 @@
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
<!-- Modal content--> <!-- Modal content-->
<div class="modal-content"> <div class="modal-content">
<div class="modal-header bg-info"> <div class="modal-header bg-info"></div>
</div>
<div class="modal-body text-center"> <div class="modal-body text-center">
<p>{{_('Do you really want to restart Calibre-web?')}}</p> <p>{{_('Do you really want to restart Calibre-web?')}}</p>
<button type="button" class="btn btn-default" id="restart" data-dismiss="modal">{{_('Ok')}}</button> <div id="spinner" class="spinner" style="display:none;">
<img id="img-spinner" src="/static/css/images/loading-icon.gif"/>
</div>
<p></p>
<button type="button" class="btn btn-default" id="restart" >{{_('Ok')}}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Back')}}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{{_('Back')}}</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div id="ShutdownDialog" class="modal fade" role="dialog"> <div id="ShutdownDialog" class="modal fade" role="dialog">
...@@ -114,5 +119,23 @@ ...@@ -114,5 +119,23 @@
</div> </div>
</div> </div>
<div id="UpdateprogressDialog" class="modal fade" role="dialog">
<div class="modal-dialog modal-sm">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header bg-info text-center">
<span>{{_('Updating, please do not reload page')}}</span>
</div>
<div class="modal-body text-center">
<div id="spinner2" class="spinner2" style="display:none;">
<img id="img-spinner" src="/static/css/images/loading-icon.gif"/>
</div>
<p></p>
<div id="Updatecontent"></div>
<p></p>
<button type="button" class="btn btn-default hidden" id="updateFinished" data-dismiss="modal">{{_('Ok')}}</button>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
{% if entry.identifiers|length > 0 %} {% if entry.identifiers|length > 0 %}
<div class="identifiers"> <div class="identifiers">
<p>
<span class="glyphicon glyphicon-link"></span> <span class="glyphicon glyphicon-link"></span>
{% for identifier in entry.identifiers %} {% for identifier in entry.identifiers %}
<a href="{{identifier}}" target="_blank" class="btn btn-xs btn-success" role="button">{{identifier.formatType()}}</a> <a href="{{identifier}}" target="_blank" class="btn btn-xs btn-success" role="button">{{identifier.formatType()}}</a>
...@@ -66,10 +67,16 @@ ...@@ -66,10 +67,16 @@
{% for tag in entry.tags %} {% for tag in entry.tags %}
<a href="{{ url_for('category', id=tag.id) }}" class="btn btn-xs btn-info" role="button">{{tag.name}}</a> <a href="{{ url_for('category', id=tag.id) }}" class="btn btn-xs btn-info" role="button">{{tag.name}}</a>
{%endfor%} {%endfor%}
</div> </div>
</p> </p>
{% endif %} {% endif %}
{% if entry.publishers|length > 0 %}
<div class="publishers">
<p>
<span>{{_('Publisher')}}:{% for publisher in entry.publishers %} {{publisher.name}}{% if not loop.last %},{% endif %}{% endfor %}</span>
</p>
</div>
{% endif %}
{% if entry.pubdate[:10] != '0101-01-01' %} {% if entry.pubdate[:10] != '0101-01-01' %}
<p>{{_('Publishing date')}}: {{entry.pubdate|formatdate}} </p> <p>{{_('Publishing date')}}: {{entry.pubdate|formatdate}} </p>
{% endif %} {% endif %}
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
<div class="navbar-collapse collapse"> <div class="navbar-collapse collapse">
{% if g.user.is_authenticated or g.user.is_anonymous() %} {% if g.user.is_authenticated or g.user.is_anonymous() %}
<ul class="nav navbar-nav "> <ul class="nav navbar-nav ">
<li><a href="{{url_for('advanced_search')}}"><span class="glyphicon glyphicon-search"></span> {{_('Advanced Search')}}</a></li> <li><a href="{{url_for('advanced_search')}}"><span class="glyphicon glyphicon-search"></span><span class="hidden-sm"> {{_('Advanced Search')}}</span></a></li>
</ul> </ul>
{% endif %} {% endif %}
<ul class="nav navbar-nav navbar-right" id="main-nav"> <ul class="nav navbar-nav navbar-right" id="main-nav">
......
...@@ -10,63 +10,67 @@ ...@@ -10,63 +10,67 @@
<label for="bookAuthor">{{_('Author')}}</label> <label for="bookAuthor">{{_('Author')}}</label>
<input type="text" class="form-control typeahead" name="author_name" id="bookAuthor" value="" autocomplete="off"> <input type="text" class="form-control typeahead" name="author_name" id="bookAuthor" value="" autocomplete="off">
</div> </div>
<label for="Tags">{{_('Tags')}}</label>
<div class="form-group"> <div class="form-group">
<label for="Publisher">{{_('Publisher')}}</label>
<input type="text" class="form-control" name="publisher" id="publisher" value="">
</div>
<label for="include_tag">{{_('Tags')}}</label>
<div class="form-group" id="test">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons"> <div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for tag in tags %} {% for tag in tags %}
<label id="tag_{{tag.id}}" class="btn btn-primary tags_click"> <label id="tag_{{tag.id}}" class="btn btn-primary tags_click">
<input type="checkbox" autocomplete="off" name="include_tag" value="{{tag.id}}">{{tag.name}}</input> <input type="checkbox" autocomplete="off" name="include_tag" id="include_tag" value="{{tag.id}}">{{tag.name}}</input>
</label> </label>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<label for="Tags">{{_('Exclude Tags')}}</label> <label for="exclude_tag">{{_('Exclude Tags')}}</label>
<div class="form-group"> <div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons"> <div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for tag in tags %} {% for tag in tags %}
<label id="tag_{{tag.id}}" class="btn btn-danger tags_click"> <label id="tag_{{tag.id}}" class="btn btn-danger tags_click">
<input type="checkbox" autocomplete="off" name="exclude_tag" value="{{tag.id}}">{{tag.name}}</input> <input type="checkbox" autocomplete="off" name="exclude_tag" id="exclude_tag" value="{{tag.id}}">{{tag.name}}</input>
</label> </label>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<label for="Series">{{_('Series')}}</label> <label for="include_serie">{{_('Series')}}</label>
<div class="form-group"> <div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons"> <div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for serie in series %} {% for serie in series %}
<label id="serie_{{serie.id}}" class="btn btn-primary serie_click"> <label id="serie_{{serie.id}}" class="btn btn-primary serie_click">
<input type="checkbox" autocomplete="off" name="include_serie" value="{{serie.id}}">{{serie.name}}</input> <input type="checkbox" autocomplete="off" name="include_serie" id="include_serie" value="{{serie.id}}">{{serie.name}}</input>
</label> </label>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<label for="Series">{{_('Exclude Series')}}</label> <label for="exclude_serie">{{_('Exclude Series')}}</label>
<div class="form-group"> <div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons"> <div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for serie in series %} {% for serie in series %}
<label id="serie_{{serie.id}}" class="btn btn-danger serie_click"> <label id="serie_{{serie.id}}" class="btn btn-danger serie_click">
<input type="checkbox" autocomplete="off" name="exclude_serie" value="{{serie.id}}">{{serie.name}}</input> <input type="checkbox" autocomplete="off" name="exclude_serie" id="exclude_serie" value="{{serie.id}}">{{serie.name}}</input>
</label> </label>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% if languages %} {% if languages %}
<label for="Languages">{{_('Languages')}}</label> <label for="include_language">{{_('Languages')}}</label>
<div class="form-group"> <div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons"> <div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for language in languages %} {% for language in languages %}
<label id="language_{{language.id}}" class="btn btn-primary serie_click"> <label id="language_{{language.id}}" class="btn btn-primary serie_click">
<input type="checkbox" autocomplete="off" name="include_language" value="{{language.id}}">{{language.name}}</input> <input type="checkbox" autocomplete="off" name="include_language" id="include_language" value="{{language.id}}">{{language.name}}</input>
</label> </label>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<label for="Languages">{{_('Exclude Languages')}}</label> <label for="exclude_language">{{_('Exclude Languages')}}</label>
<div class="form-group"> <div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons"> <div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for language in languages %} {% for language in languages %}
<label id="language_{{language.id}}" class="btn btn-danger language_click"> <label id="language_{{language.id}}" class="btn btn-danger language_click">
<input type="checkbox" autocomplete="off" name="exclude_language" value="{{language.id}}">{{language.name}}</input> <input type="checkbox" autocomplete="off" name="exclude_language" id="exclude_language" value="{{language.id}}">{{language.name}}</input>
</label> </label>
{% endfor %} {% endfor %}
</div> </div>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -5,7 +5,6 @@ from pydrive.auth import GoogleAuth ...@@ -5,7 +5,6 @@ from pydrive.auth import GoogleAuth
import mimetypes import mimetypes
import logging import logging
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from tempfile import gettempdir
import textwrap import textwrap
from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, \ from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, \
make_response, g, flash, abort, send_file make_response, g, flash, abort, send_file
...@@ -41,11 +40,11 @@ import sys ...@@ -41,11 +40,11 @@ import sys
import subprocess import subprocess
import re import re
import db import db
import thread
from shutil import move, copyfile from shutil import move, copyfile
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
import shutil import shutil
import StringIO import StringIO
from shutil import move
import gdriveutils import gdriveutils
import io import io
import hashlib import hashlib
...@@ -562,7 +561,9 @@ def feed_search(term): ...@@ -562,7 +561,9 @@ def feed_search(term):
filter = True filter = True
if term: if term:
entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")), entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")),
db.Books.series.any(db.Series.name.like("%" + term + "%")),
db.Books.authors.any(db.Authors.name.like("%" + term + "%")), db.Books.authors.any(db.Authors.name.like("%" + term + "%")),
db.Books.publishers.any(db.Publishers.name.like("%" + term + "%")),
db.Books.title.like("%" + term + "%"))).filter(filter).all() db.Books.title.like("%" + term + "%"))).filter(filter).all()
entriescount = len(entries) if len(entries) > 0 else 1 entriescount = len(entries) if len(entries) > 0 else 1
pagination = Pagination(1, entriescount, entriescount) pagination = Pagination(1, entriescount, entriescount)
...@@ -761,7 +762,8 @@ def get_opds_download_link(book_id, format): ...@@ -761,7 +762,8 @@ def get_opds_download_link(book_id, format):
resp, content = df.auth.Get_Http_Object().request(download_url) resp, content = df.auth.Get_Http_Object().request(download_url)
response=send_file(io.BytesIO(content)) response=send_file(io.BytesIO(content))
else: else:
file_name = helper.get_valid_filename(file_name) # file_name = helper.get_valid_filename(file_name)
response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + format))
response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + format)) response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + format))
response.headers["Content-Disposition"] = "attachment; filename=\"%s.%s\"" % (data.name, format) response.headers["Content-Disposition"] = "attachment; filename=\"%s.%s\"" % (data.name, format)
return response return response
...@@ -809,10 +811,42 @@ def get_update_status(): ...@@ -809,10 +811,42 @@ def get_update_status():
commit = requests.get('https://api.github.com/repos/janeczku/calibre-web/git/refs/heads/master').json() commit = requests.get('https://api.github.com/repos/janeczku/calibre-web/git/refs/heads/master').json()
if "object" in commit and commit['object']['sha'] != commit_id: if "object" in commit and commit['object']['sha'] != commit_id:
status['status'] = True status['status'] = True
commitdate = requests.get('https://api.github.com/repos/janeczku/calibre-web/git/commits/'+commit['object']['sha']).json()
if "committer" in commitdate:
status['commit'] = commitdate['committer']['date']
else:
status['commit'] = u'Unknown'
else: else:
status['status'] = False status['status'] = False
return json.dumps(status) return json.dumps(status)
@app.route("/get_updater_status", methods=['GET','POST'])
@login_required
@admin_required
def get_updater_status():
status = {}
if request.method == "POST":
commit = request.form.to_dict()
if "start" in commit and commit['start'] == 'True':
text={
"1": _(u'Requesting update package'),
"2": _(u'Downloading update package'),
"3": _(u'Unzipping update package'),
"4": _(u'Files are replaced'),
"5": _(u'Database connections are closed'),
"6": _(u'Server is stopped'),
"7": _(u'Update finished, please press okay and reload page')
}
status['text']=text
helper.updater_thread = helper.Updater()
helper.updater_thread.start()
status['status']=helper.updater_thread.get_update_status()
elif request.method == "GET":
try:
status['status']=helper.updater_thread.get_update_status()
except:
status['status'] = 7
return json.dumps(status)
@app.route("/get_languages_json", methods=['GET', 'POST']) @app.route("/get_languages_json", methods=['GET', 'POST'])
...@@ -1229,9 +1263,9 @@ def on_received_watch_confirmation(): ...@@ -1229,9 +1263,9 @@ def on_received_watch_confirmation():
@login_required @login_required
@admin_required @admin_required
def shutdown(): def shutdown():
global global_task # global global_task
task = int(request.args.get("parameter").strip()) task = int(request.args.get("parameter").strip())
global_task = task helper.global_task = task
if task == 1 or task == 0: # valid commandos received if task == 1 or task == 0: # valid commandos received
# close all database connections # close all database connections
db.session.close() db.session.close()
...@@ -1243,7 +1277,7 @@ def shutdown(): ...@@ -1243,7 +1277,7 @@ def shutdown():
server.add_callback(server.stop) server.add_callback(server.stop)
showtext = {} showtext = {}
if task == 0: if task == 0:
showtext['text'] = _(u'Performing Restart, please reload page') showtext['text'] = _(u'Server restarted, please reload page')
else: else:
showtext['text'] = _(u'Performing shutdown of server, please close window') showtext['text'] = _(u'Performing shutdown of server, please close window')
return json.dumps(showtext) return json.dumps(showtext)
...@@ -1254,23 +1288,10 @@ def shutdown(): ...@@ -1254,23 +1288,10 @@ def shutdown():
@login_required @login_required
@admin_required @admin_required
def update(): def update():
global global_task helper.updater_thread = helper.Updater()
r = requests.get('https://api.github.com/repos/janeczku/calibre-web/zipball/master', stream=True)
fname = re.findall("filename=(.+)", r.headers['content-disposition'])[0]
z = zipfile.ZipFile(StringIO.StringIO(r.content))
tmp_dir = gettempdir()
z.extractall(tmp_dir)
helper.update_source(os.path.join(tmp_dir,os.path.splitext(fname)[0]),config.get_main_dir)
global_task = 0
db.session.close()
db.engine.dispose()
ub.session.close()
ub.engine.dispose()
# stop tornado server
server = IOLoop.instance()
server.add_callback(server.stop)
flash(_(u"Update done"), category="info") flash(_(u"Update done"), category="info")
return logout() return ""
@app.route("/search", methods=["GET"]) @app.route("/search", methods=["GET"])
@login_required_if_no_ano @login_required_if_no_ano
...@@ -1284,6 +1305,7 @@ def search(): ...@@ -1284,6 +1305,7 @@ def search():
entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")), entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")),
db.Books.series.any(db.Series.name.like("%" + term + "%")), db.Books.series.any(db.Series.name.like("%" + term + "%")),
db.Books.authors.any(db.Authors.name.like("%" + term + "%")), db.Books.authors.any(db.Authors.name.like("%" + term + "%")),
db.Books.publishers.any(db.Publishers.name.like("%" + term + "%")),
db.Books.title.like("%" + term + "%"))).filter(filter).all() db.Books.title.like("%" + term + "%"))).filter(filter).all()
return render_title_template('search.html', searchterm=term, entries=entries) return render_title_template('search.html', searchterm=term, entries=entries)
else: else:
...@@ -1304,12 +1326,14 @@ def advanced_search(): ...@@ -1304,12 +1326,14 @@ def advanced_search():
author_name = request.args.get("author_name") author_name = request.args.get("author_name")
book_title = request.args.get("book_title") book_title = request.args.get("book_title")
publisher = request.args.get("publisher")
if author_name: author_name = author_name.strip() if author_name: author_name = author_name.strip()
if book_title: book_title = book_title.strip() if book_title: book_title = book_title.strip()
if publisher: publisher = publisher.strip()
if include_tag_inputs or exclude_tag_inputs or include_series_inputs or exclude_series_inputs or \ if include_tag_inputs or exclude_tag_inputs or include_series_inputs or exclude_series_inputs or \
include_languages_inputs or exclude_languages_inputs or author_name or book_title: include_languages_inputs or exclude_languages_inputs or author_name or book_title or publisher:
searchterm = [] searchterm = []
searchterm.extend((author_name, book_title)) searchterm.extend((author_name, book_title, publisher))
tag_names = db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all() tag_names = db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all()
searchterm.extend(tag.name for tag in tag_names) searchterm.extend(tag.name for tag in tag_names)
# searchterm = " + ".join(filter(None, searchterm)) # searchterm = " + ".join(filter(None, searchterm))
...@@ -1325,7 +1349,8 @@ def advanced_search(): ...@@ -1325,7 +1349,8 @@ def advanced_search():
searchterm.extend(language.name for language in language_names) searchterm.extend(language.name for language in language_names)
searchterm = " + ".join(filter(None, searchterm)) searchterm = " + ".join(filter(None, searchterm))
q = q.filter(db.Books.authors.any(db.Authors.name.like("%" + author_name + "%")), q = q.filter(db.Books.authors.any(db.Authors.name.like("%" + author_name + "%")),
db.Books.title.like("%" + book_title + "%")) db.Books.title.like("%" + book_title + "%"),
db.Books.publishers.any(db.Publishers.name.like("%" + publisher + "%")))
for tag in include_tag_inputs: for tag in include_tag_inputs:
q = q.filter(db.Books.tags.any(db.Tags.id == tag)) q = q.filter(db.Books.tags.any(db.Tags.id == tag))
for tag in exclude_tag_inputs: for tag in exclude_tag_inputs:
...@@ -1367,9 +1392,7 @@ def get_cover(cover_path): ...@@ -1367,9 +1392,7 @@ def get_cover(cover_path):
return redirect(download_url) return redirect(download_url)
else: else:
return send_from_directory(os.path.join(config.config_calibre_dir, cover_path), "cover.jpg") return send_from_directory(os.path.join(config.config_calibre_dir, cover_path), "cover.jpg")
resp.headers['Content-Type']='image/jpeg'
return resp
@app.route("/opds/thumb_240_240/<path:book_id>") @app.route("/opds/thumb_240_240/<path:book_id>")
@app.route("/opds/cover_240_240/<path:book_id>") @app.route("/opds/cover_240_240/<path:book_id>")
...@@ -1875,14 +1898,13 @@ def basic_configuration(): ...@@ -1875,14 +1898,13 @@ def basic_configuration():
def configuration_helper(origin): def configuration_helper(origin):
global global_task # global global_task
reboot_required = False reboot_required = False
db_change = False db_change = False
success = False success = False
if request.method == "POST": if request.method == "POST":
to_save = request.form.to_dict() to_save = request.form.to_dict()
content = ub.session.query(ub.Settings).first() content = ub.session.query(ub.Settings).first()
# ToDo: check lib vaild, and change without restart
if "config_calibre_dir" in to_save: if "config_calibre_dir" in to_save:
if content.config_calibre_dir != to_save["config_calibre_dir"]: if content.config_calibre_dir != to_save["config_calibre_dir"]:
content.config_calibre_dir = to_save["config_calibre_dir"] content.config_calibre_dir = to_save["config_calibre_dir"]
...@@ -1982,7 +2004,7 @@ def configuration_helper(origin): ...@@ -1982,7 +2004,7 @@ def configuration_helper(origin):
# stop tornado server # stop tornado server
server = IOLoop.instance() server = IOLoop.instance()
server.add_callback(server.stop) server.add_callback(server.stop)
global_task = 0 helper.global_task = 0
app.logger.info('Reboot required, restarting') app.logger.info('Reboot required, restarting')
if origin: if origin:
success = True success = True
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment