diff --git a/cps.py b/cps.py index 4c8bfbe2..00d0f356 100755 --- a/cps.py +++ b/cps.py @@ -3,7 +3,6 @@ import os import sys -import time base_path = os.path.dirname(os.path.abspath(__file__)) # Insert local directories into path @@ -22,7 +21,7 @@ if __name__ == '__main__': http_server.listen(web.ub.config.config_port) IOLoop.instance().start() - if web.global_task == 0: + if web.helper.global_task == 0: print("Performing restart of Calibre-web") os.execl(sys.executable, sys.executable, *sys.argv) else: diff --git a/cps/helper.py b/cps/helper.py index beee00c7..20d9402a 100755 --- a/cps/helper.py +++ b/cps/helper.py @@ -6,7 +6,7 @@ import ub from flask import current_app as app import logging import smtplib -import tempfile +from tempfile import gettempdir import socket import sys import os @@ -21,13 +21,22 @@ from email.MIMEText import MIMEText from email.generator import Generator from flask_babel import gettext as _ import subprocess +import threading import shutil +import requests +import zipfile +from tornado.ioloop import IOLoop + try: import unidecode use_unidecode=True except: use_unidecode=False +# Global variables +global_task = None +updater_thread = None + 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 == book_id).first() @@ -267,117 +276,146 @@ def update_dir_stucture(book_id, calibrepath): book.path = new_authordir + '/' + book.path.split('/')[1] db.session.commit() +class Updater(threading.Thread): -def file_to_list(file): - return [x.strip() for x in open(file, 'r') if not x.startswith('#EXT')] + def __init__(self): + threading.Thread.__init__(self) + self.status=0 -def one_minus_two(one, two): - return [x for x in one if x not in set(two)] + 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 reduce_dirs(delete_files, new_list): - new_delete = [] - for file in delete_files: - parts = file.split(os.sep) - sub = '' - for i in range(len(parts)): - sub = os.path.join(sub, parts[i]) - if sub == '': - sub = os.sep - count = 0 - for song in new_list: - if song.startswith(sub): - count += 1 + 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')] + + def one_minus_two(self, one, two): + return [x for x in one if x not in set(two)] + + def reduce_dirs(self, delete_files, new_list): + new_delete = [] + for file in delete_files: + parts = file.split(os.sep) + sub = '' + for i in range(len(parts)): + sub = os.path.join(sub, parts[i]) + if sub == '': + sub = os.sep + count = 0 + for song in new_list: + if song.startswith(sub): + count += 1 + break + if count == 0: + if sub != '\\': + new_delete.append(sub) break - if count == 0: - if sub != '\\': - new_delete.append(sub) - break - return list(set(new_delete)) + return list(set(new_delete)) -def reduce_files(remove_items, exclude_items): - rf = [] - for item in remove_items: - if not item in exclude_items: - rf.append(item) - return rf + def reduce_files(self, remove_items, exclude_items): + rf = [] + for item in remove_items: + if not item in exclude_items: + rf.append(item) + return rf -def moveallfiles(root_src_dir, root_dst_dir): - change_permissions = True - if sys.platform == "win32" or sys.platform == "darwin": - change_permissions=False - else: - app.logger.debug('Update on OS-System : '+sys.platform ) - #print('OS-System: '+sys.platform ) - new_permissions=os.stat(root_dst_dir) - #print new_permissions - for src_dir, dirs, 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) - #print('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) - for file_ in files: - src_file = os.path.join(src_dir, file_) - dst_file = os.path.join(dst_dir, file_) - if os.path.exists(dst_file): - if change_permissions: - permission=os.stat(dst_file) - #print('Remove file before copy: '+dst_file) - os.remove(dst_file) - else: - if change_permissions: - permission=new_permissions - shutil.move(src_file, dst_dir) - #print('Move File '+src_file+' to '+dst_dir) - if change_permissions: - try: - os.chown(dst_file, permission.st_uid, permission.st_uid) - #print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid)) - except: - e = sys.exc_info() - #print('Fail '+str(dst_file)+' error: '+str(e)) - return - - -def update_source(source,destination): - # destination files - old_list=list() - exclude = (['vendor' + os.sep + 'kindlegen.exe','vendor' + os.sep + 'kindlegen','/app.db','vendor','/update.py']) - for root, dirs, files in os.walk(destination, topdown=True): - for name in files: - old_list.append(os.path.join(root, name).replace(destination, '')) - for name in dirs: - old_list.append(os.path.join(root, name).replace(destination, '')) - # source files - new_list = list() - for root, dirs, files in os.walk(source, topdown=True): - for name in files: - new_list.append(os.path.join(root, name).replace(source, '')) - for name in dirs: - new_list.append(os.path.join(root, name).replace(source, '')) - - delete_files = one_minus_two(old_list, new_list) - #print('raw delete list', delete_files) - - rf= reduce_files(delete_files, exclude) - #print('reduced delete list', rf) - - remove_items = reduce_dirs(rf, new_list) - #print('delete files', remove_items) - - moveallfiles(source, destination) - - for item in remove_items: - item_path = os.path.join(destination, item[1:]) - if os.path.isdir(item_path): - print("Delete dir "+ item_path) - shutil.rmtree(item_path) + def moveallfiles(self, root_src_dir, root_dst_dir): + change_permissions = True + if sys.platform == "win32" or sys.platform == "darwin": + change_permissions = False else: - try: - print("Delete file "+ item_path) - os.remove(item_path) - except: - print("Could not remove:"+item_path) - shutil.rmtree(source, ignore_errors=True) + app.logger.debug('Update on OS-System : ' + sys.platform) + # print('OS-System: '+sys.platform ) + new_permissions = os.stat(root_dst_dir) + # print new_permissions + for src_dir, dirs, 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) + # print('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) + for file_ in files: + src_file = os.path.join(src_dir, file_) + dst_file = os.path.join(dst_dir, file_) + if os.path.exists(dst_file): + if change_permissions: + permission = os.stat(dst_file) + # print('Remove file before copy: '+dst_file) + os.remove(dst_file) + else: + if change_permissions: + permission = new_permissions + shutil.move(src_file, dst_dir) + # print('Move File '+src_file+' to '+dst_dir) + if change_permissions: + try: + os.chown(dst_file, permission.st_uid, permission.st_uid) + # print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid)) + except: + e = sys.exc_info() + # print('Fail '+str(dst_file)+' error: '+str(e)) + return + + def update_source(self, source, destination): + # destination files + old_list = list() + exclude = ( + ['vendor' + os.sep + 'kindlegen.exe', 'vendor' + os.sep + 'kindlegen', '/app.db', 'vendor', '/update.py']) + for root, dirs, files in os.walk(destination, topdown=True): + for name in files: + old_list.append(os.path.join(root, name).replace(destination, '')) + for name in dirs: + old_list.append(os.path.join(root, name).replace(destination, '')) + # source files + new_list = list() + for root, dirs, files in os.walk(source, topdown=True): + for name in files: + new_list.append(os.path.join(root, name).replace(source, '')) + for name in dirs: + new_list.append(os.path.join(root, name).replace(source, '')) + + delete_files = self.one_minus_two(old_list, new_list) + + rf = self.reduce_files(delete_files, exclude) + + remove_items = self.reduce_dirs(rf, new_list) + + self.moveallfiles(source, destination) + + for item in remove_items: + item_path = os.path.join(destination, item[1:]) + if os.path.isdir(item_path): + app.logger.debug("Delete dir " + item_path) + shutil.rmtree(item_path) + else: + try: + app.logger.debug("Delete file " + item_path) + os.remove(item_path) + except: + app.logger.debug("Could not remove:" + item_path) + shutil.rmtree(source, ignore_errors=True) diff --git a/cps/static/css/style.css b/cps/static/css/style.css index 5e6ea314..921a35ff 100644 --- a/cps/static/css/style.css +++ b/cps/static/css/style.css @@ -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.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; } + +.spinner {margin:0 41%;} +.spinner2 {margin:0 41%;} + diff --git a/cps/static/js/main.js b/cps/static/js/main.js index a2d56c9e..74e1a1d8 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -1,3 +1,6 @@ +var displaytext; +var updateTimerID; +var updateText; $(function() { $('.discover .row').isotope({ @@ -31,7 +34,9 @@ $(function() { url: window.location.pathname+"/../../shutdown", data: {"parameter":0}, success: function(data) { - return alert(data.text);} + $('#spinner').show(); + displaytext=data.text; + window.setTimeout(restartTimer, 3000);} }); }); $("#shutdown").click(function() { @@ -50,17 +55,66 @@ $(function() { dataType: 'json', url: window.location.pathname+"/../../get_update_status", success: function(data) { - if (data.status == true) { - $("#check_for_update").addClass('hidden'); - $("#perform_update").removeClass('hidden'); - }else{ $("#check_for_update").html(button_text); - };} + if (data.status == true) { + $("#check_for_update").addClass('hidden'); + $("#perform_update").removeClass('hidden'); + $("#update_info").removeClass('hidden'); + $("#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) { $('.discover .row').isotope('reLayout'); }); diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 31c167ee..b386e4ff 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -2,6 +2,7 @@ {% block body %}

{{_('User list')}}

+
@@ -76,11 +77,13 @@
{{_('Configuration')}}

{{_('Administration')}}

{% if not development %} -

{{_('Current commit timestamp')}}: {{commit}}

-
{{_('Restart Calibre-web')}}
-
{{_('Stop Calibre-web')}}
-
{{_('Check for update')}}
- +
{{_('Current commit timestamp')}}: {{commit}}
+ +

+
{{_('Restart Calibre-web')}}
+
{{_('Stop Calibre-web')}}
+
{{_('Check for update')}}
+ {% endif %} @@ -88,15 +91,17 @@ - + {% endblock %} diff --git a/cps/templates/layout.html b/cps/templates/layout.html index 71bd7941..76553ace 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -65,7 +65,7 @@
{{_('Nickname')}}