diff --git a/ctl b/ctl index 17eb02a..2000565 100755 --- a/ctl +++ b/ctl @@ -54,7 +54,7 @@ hash -r 2>/dev/null # allow more open files ulimit -S -n 2048 -function remove_loginscript { +function remove_autostart { if [ $SYSTEM == "Darwin" ]; then launchd_name="com.openmedialibrary.loginscript" launchd_plist="$HOME/Library/LaunchAgents/${launchd_name}.plist" @@ -64,15 +64,15 @@ function remove_loginscript { rm "$launchd_plist" fi fi -} - -if [ "$1" == "start" ]; then - remove_loginscript if [ $SYSTEM == "Linux" ]; then if [ -e "$HOME/.config/autostart/openmedialibrary.desktop" ]; then rm "$HOME/.config/autostart/openmedialibrary.desktop" fi fi +} + +if [ "$1" == "start" ]; then + remove_autostart cd "$BASE/$NAME" if [ -e $PID ]; then if ps -p `cat "$PID"` > /dev/null; then @@ -96,7 +96,7 @@ if [ "$1" == "debug" ]; then exec python3 oml server debug $PID fi if [ "$1" == "stop" ]; then - remove_loginscript + remove_autostart if [ -e $PID ]; then _PID=`cat $PID` kill $_PID diff --git a/install b/install index 9042f45..c199a7a 100755 --- a/install +++ b/install @@ -114,10 +114,10 @@ class Install(Thread): self.status['installing'] = 'setup' if sys.platform.startswith('linux'): os.system('./ctl install_launcher') - os.system('./ctl setup') - self.status['progress'] = 1 with open('config/release.json', 'w') as fd: json.dump(release, fd, indent=2) + os.system('./ctl setup') + self.status['progress'] = 1 self.status['done'] = True def download(self, url, filename): diff --git a/oml/server.py b/oml/server.py index bb7f82b..0a3d6b0 100644 --- a/oml/server.py +++ b/oml/server.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 - import os import sys import signal @@ -23,6 +22,7 @@ import setup import state import tasks import websocket +import update import logging @@ -31,13 +31,15 @@ logger = logging.getLogger(__name__) class MainHandler(OMLHandler): def get(self, path): - path = os.path.join(settings.static_path, 'html', 'oml.html') - with open(path) as fd: - content = fd.read() version = settings.MINOR_VERSION.split('-')[0] if version == 'git': version = int(time.mktime(time.gmtime())) - content = content.replace('oml.js?1', 'oml.js?%s' % version) + path = os.path.join(settings.static_path, 'html', 'oml.html') + with open(path) as fd: + content = fd.read() + content = content.replace('.js?1', '.js?%s' % version) + if state.update: + content = content.replace('oml.js', 'oml.update.js') self.set_header('Content-Type', 'text/html') self.set_header('Content-Length', str(len(content))) self.write(content) @@ -133,11 +135,11 @@ def run(): with open(PID, 'w') as pid: pid.write('%s' % os.getpid()) + state.update = update.update_available() state.PID = PID state.http_server = http_server state.main = IOLoop.instance() state.cache = Cache(ttl=60) - state.tasks = tasks.Tasks() def start_node(): import downloads @@ -156,7 +158,13 @@ def run(): else: nodes.publish_node() state.main.add_callback(publish) - state.main.add_callback(start_node) + + if not state.update: + state.tasks = tasks.Tasks() + state.main.add_callback(start_node) + else: + state.tasks = update.Update() + if ':' in settings.server['address']: host = '[%s]' % settings.server['address'] elif not settings.server['address']: diff --git a/oml/settings.py b/oml/settings.py index dcfcc57..369721a 100644 --- a/oml/settings.py +++ b/oml/settings.py @@ -78,7 +78,8 @@ else: NODE_PROTOCOL="0.4" VERSION="%s.%s" % (NODE_PROTOCOL, MINOR_VERSION) - USER_AGENT = 'OpenMediaLibrary/%s' % VERSION DEBUG_HTTP = server.get('debug_http', False) + +DB_VERSION = 0 diff --git a/oml/state.py b/oml/state.py index 876bead..48e485b 100644 --- a/oml/state.py +++ b/oml/state.py @@ -2,11 +2,13 @@ bandwidth = None host = None main = None nodes = False +node = False online = False tasks = False scraping = False downloads = False tor = False +update = False websockets = [] activity = {} diff --git a/oml/tasks.py b/oml/tasks.py index fa1bb0a..ba5bfc1 100644 --- a/oml/tasks.py +++ b/oml/tasks.py @@ -6,7 +6,7 @@ from queue import Queue from threading import Thread from websocket import trigger_event - +import state import logging logger = logging.getLogger(__name__) @@ -34,8 +34,6 @@ class Tasks(Thread): item.scan.run_import(data) elif action == 'scan': item.scan.run_scan() - elif action == 'update': - trigger_event('error', {'error': 'not implemented'}) else: trigger_event('error', {'error': 'unknown action'}) except: diff --git a/oml/tor.py b/oml/tor.py index c91920d..9f630ca 100644 --- a/oml/tor.py +++ b/oml/tor.py @@ -238,7 +238,7 @@ def install_tor(): if get_tor(): print('found existing tor installation') url = torbrowser_url() - target = os.path.join(settings.base_dir, 'tor') + target = os.path.normpath(os.path.join(settings.base_dir, '..', 'tor')) if url: print('downloading and installing tor') if sys.platform.startswith('linux'): diff --git a/oml/update.py b/oml/update.py index daba700..1a7a08a 100644 --- a/oml/update.py +++ b/oml/update.py @@ -6,10 +6,12 @@ from contextlib import closing import json import os import tarfile +from threading import Thread import urllib.request, urllib.error, urllib.parse import shutil import subprocess import sys +import time import ed25519 import ox @@ -145,7 +147,6 @@ def install(): shutil.copy(os.path.join(settings.updates_path, 'release.json'), os.path.join(settings.config_path, 'release.json')) for cmd in [ ['./ctl', 'stop'], - ['./ctl', 'setup'], ['./ctl', 'postupdate', '-o', old_version, '-n', new_version] ]: subprocess.call(cmd) @@ -153,6 +154,26 @@ def install(): return True return True +def update_available(): + db_version = settings.server.get('db_version', 0) + if db_version < settings.DB_VERSION: + return True + if not os.path.exists(os.path.join(settings.updates_path, 'release.json')): + return False + if not os.path.exists(os.path.join(settings.config_path, 'release.json')): + return False + with open(os.path.join(settings.updates_path, 'release.json')) as fd: + release = json.load(fd) + old_version = current_version('openmedialibrary') + new_version = release['modules']['openmedialibrary']['version'] + return verify(release) and old_version < new_version + +def restart_oml(update=False): + if update: + get_latest_release() + subprocess.Popen([os.path.join(settings.base_dir, 'ctl'), 'restart'], + close_fds=True, start_new_session=True) + def get_app_version(app): plist = app + '/Contents/Info.plist' if os.path.exists(plist): @@ -214,9 +235,50 @@ def restart(data): ''' restart (and upgrade if upgrades are available) ''' - if data.get('update'): - download() - subprocess.Popen([os.path.join(settings.base_dir, 'ctl'), 'restart'], - close_fds=True, start_new_session=True) + restart_oml(data.get('update')) return {} actions.register(restart, cache=False) + + +class Update(Thread): + + def __init__(self): + Thread.__init__(self) + self.daemon = True + self.start() + + def status(self, status, reload=False): + from websocket import trigger_event + trigger_event('updatestatus', { + 'reload': reload, + 'status': status, + }) + + def install(self): + while update_available(): + self.status('Downloading new version...') + while not download(): + self.status('Download failed... (try again in 10 seconds)') + time.sleep(10) + self.status('Downloading new version...') + self.status('Installing new version...') + # install right now calls stop! + ''' + if not install(): + self.status('Installation failed...') + ''' + restart_oml() + + def update_database(self): + db_version = settings.server.get('db_version', 0) + if db_version < settings.DB_VERSION: + self.status('Migrating database...') + time.sleep(1) + settings.server['db_version'] = settings.DB_VERSION + + def run(self): + self.status('Checking for Updates...') + self.update_database() + self.install() + self.status('Restarting...', True) + restart_oml() diff --git a/static/js/oml.update.js b/static/js/oml.update.js new file mode 100644 index 0000000..d2321ae --- /dev/null +++ b/static/js/oml.update.js @@ -0,0 +1,178 @@ +'use strict'; + +(function() { + + var animationInterval, + enableDebugMode = getLocalStorage('oml.enableDebugMode'), + omlVersion = getOMLVersion(), + oxjsPath = '/static/oxjs/' + (enableDebugMode ? 'dev' : 'min'), + terminal, + theme = getLocalStorage('Ox.theme') + && JSON.parse(localStorage['Ox.theme']) + || 'oxlight'; + + loadImages(function(images) { + loadScreen(images); + loadOxJS(loadOML); + }); + + function getLocalStorage(key) { + try { + return localStorage[key]; + } catch(e) {} + } + + function getOMLVersion() { + var i, path, scripts = document.getElementsByTagName('script'); + for (i = 0; i < scripts.length; i++) { + if(/oml.update.js/.test(scripts[i].src)) { + return scripts[i].src.replace(/.*\?/, ''); + } + } + } + + function loadImages(callback) { + var images = {}; + images.logo = document.createElement('img'); + images.logo.onload = function() { + images.logo.style.position = 'absolute'; + images.logo.style.left = 0; + images.logo.style.top = 0; + images.logo.style.right = 0; + images.logo.style.bottom = '96px'; + images.logo.style.width = '256px'; + images.logo.style.height = '256px'; + images.logo.style.margin = 'auto'; + images.logo.style.MozUserSelect = 'none'; + images.logo.style.MSUserSelect = 'none'; + images.logo.style.OUserSelect = 'none'; + images.logo.style.WebkitUserSelect = 'none'; + images.loadingIcon = document.createElement('img'); + images.loadingIcon.setAttribute('id', 'loadingIcon'); + images.loadingIcon.style.position = 'absolute'; + images.loadingIcon.style.left = '16px'; + images.loadingIcon.style.top = '256px' + images.loadingIcon.style.right = 0; + images.loadingIcon.style.bottom = 0; + images.loadingIcon.style.width = '32px'; + images.loadingIcon.style.height = '32px'; + images.loadingIcon.style.margin = 'auto'; + images.loadingIcon.style.MozUserSelect = 'none'; + images.loadingIcon.style.MSUserSelect = 'none'; + images.loadingIcon.style.OUserSelect = 'none'; + images.loadingIcon.style.WebkitUserSelect = 'none'; + images.loadingIcon.src = oxjsPath + + '/UI/themes/' + theme + '/svg/symbolLoading.svg'; + callback(images); + }; + images.logo.src = '/static/png/oml.png'; + } + + function loadOML(browserSupported) { + window.oml = Ox.App({ + name: 'oml', + socket: 'ws://' + document.location.host + '/ws', + url: '/api/' + }).bindEvent({ + load: function(data) { + data.browserSupported = browserSupported; + oml.ui = {}; + + oml.ui.status = Ox.Element() + .css({ + position: 'absolute', + left: 0, + right: 0, + bottom: '50px', + textAlign: 'center', + paddingLeft: '16px', + color: '#999999' + }).appendTo(Ox.$('#loadingScreen')); + oml.ui.status.html('Updating Open Media Library...'); + }, + updatestatus: function(data) { + oml.ui.status.html(data.status); + oml.reload = data.reload; + }, + close: function(data) { + }, + open: function(data) { + Ox.print('socket open'); + if (oml.reload) { + document.location.href = document.location.protocol + '//' + document.location.host; + } + } + }); + } + + function loadOxJS(callback) { + var head = document.head + || document.getElementsByTagName('head')[0] + || document.documentElement, + script = document.createElement('script'); + script.onload = function() { + Ox.load({UI: {theme: theme}}, function() { + Ox.formatUpper = function(string) { + return string.toUpperCase(); + }; + callback(); + }); + }; + script.src = oxjsPath + '/Ox.js?' + omlVersion; + script.type = 'text/javascript'; + head.appendChild(script); + } + + function loadScreen(images) { + var loadingScreen = document.createElement('div'); + loadingScreen.setAttribute('id', 'loadingScreen'); + loadingScreen.className = 'OxScreen'; + loadingScreen.style.position = 'absolute'; + loadingScreen.style.width = '100%'; + loadingScreen.style.height = '100%'; + loadingScreen.style.backgroundColor = theme == 'oxlight' ? 'rgb(224, 224, 224)' + : theme == 'oxmedium' ? 'rgb(144, 144, 144)' : 'rgb(32, 32, 32)'; + loadingScreen.style.zIndex = '1002'; + loadingScreen.appendChild(images.logo); + loadingScreen.appendChild(images.loadingIcon); + // FF3.6 document.body can be undefined here + window.onload = function() { + document.body.style.margin = 0; + document.body.appendChild(loadingScreen); + startAnimation(); + }; + // IE8 does not call onload if already loaded before set + document.body && window.onload(); + } + + function removeScreen() { + var $loadingScreen = $('#loadingScreen'); + $loadingScreen.animate({ + opacity: 0 + }, 1000, function() { + $loadingScreen.remove(); + }); + } + + function startAnimation() { + var css, deg = 0, loadingIcon = document.getElementById('loadingIcon'), + previousTime = +new Date(); + animationInterval = setInterval(function() { + var currentTime = +new Date(), + delta = (currentTime - previousTime) / 1000; + previousTime = currentTime; + deg = Math.round((deg + delta * 360) % 360 / 30) * 30; + css = 'rotate(' + deg + 'deg)'; + loadingIcon.style.MozTransform = css; + loadingIcon.style.MSTransform = css; + loadingIcon.style.OTransform = css; + loadingIcon.style.WebkitTransform = css; + loadingIcon.style.transform = css; + }, 83); + } + + function stopAnimation() { + clearInterval(animationInterval); + } + +}());