Commit 22d67c7c authored by Stavros Sachtouris's avatar Stavros Sachtouris Committed by Giorgos Korfiatis
Browse files

Disable syncing if settings are inadequate

parent 8ed2120a
......@@ -93,6 +93,7 @@ var settings_menu = new gui.MenuItem({
new_settings = import_settings();
console.log('Settings windows is closed, PUT');
put_settings(socket, new_settings);
get_status(socket);
get_settings(socket);
});
},
......@@ -125,21 +126,21 @@ var client_ready = false;
window.setInterval(function() {
var menu_modified = false;
if (!client_ready) {
if (globals.authenticated) {
settings_menu.enabled = true;
client_ready = true;
} else return;
if (!globals.authenticated) return;
client_ready = true;
}
if (client_ready) {
if (!pithos_page_menu.enabled) {
if ((get_pithos_ui() == null) && (globals.settings.url)) {
refresh_endpoints(globals.settings.url);
}
if (!settings_menu.enabled) {
if (globals.settings.url) refresh_endpoints(globals.settings.url);
settings_menu.enabled = true;
tray.menu = menu;
}
if (globals.settings.url && !pithos_page_menu.enabled) {
if (get_pithos_ui() != null) {
pithos_page_menu.enabled = true;
tray.menu = menu;
}
} else { refresh_endpoints(globals.settings.url); }
}
if (!local_folder_menu.enabled) {
if (globals.settings.directory) {
......@@ -152,27 +153,31 @@ window.setInterval(function() {
var status = globals['status'];
var new_progress = progress_item.label;
var new_pause = pause_item.label;
if (status['paused'] !== null) {
if (!status.can_sync) {
new_progress = 'Incomplete settings'
new_pause = 'inactive'
pause_item.enabled = false;
} else if (status.paused !== null) {
switch(pause_item.label) {
case pause_syncing: if (status['paused']) {
case pause_syncing: if (status.paused) {
// Update to "Paused - start syncing"
paused = true;
new_pause = start_syncing;
menu_modified = true;
} // else continue syncing
new_progress = status['progress'] + '%' + ' synced';
new_progress = status.progress + '%' + ' synced';
break;
case start_syncing: if (status['paused']) return;
case start_syncing: if (status.paused) return;
// else update to "Syncing - pause syncing"
paused = false;
new_pause = pause_syncing;
new_progress = status['progress'] + '%' + ' synced';
new_progress = status.progress + '%' + ' synced';
menu_modified = true;
break;
default:
if (status['paused']) {new_pause = start_syncing; paused=true;}
if (status.paused) {new_pause = start_syncing; paused=true;}
else {new_pause = pause_syncing; paused=false;}
new_progress = status['progress'] + '%' + ' synced';
new_progress = status.progress + '%' + ' synced';
pause_item.enabled = true;
menu_modified = true;
}
......
......@@ -18,7 +18,7 @@ var globals = {
'directory': null,
'exclude': null
},
'status': {"progress": null, "paused": null},
'status': {"progress": null, "paused": null, "can_sync": false},
'authenticated': false,
}
......
......@@ -9,6 +9,7 @@
<script type="text/javascript">
var fs = require('fs');
var exclude = null;
remove_tokens = null;
$(document).ready(function() {
var url = get_setting('url');
if (url) $('#cloud-url').val(url);
......@@ -23,12 +24,6 @@
$('#exclude').val(
fs.readFileSync(exclude, encoding='utf-8'));
} catch (err) {console.log(err);}
if (get_pithos_ui()) {
$('#get_creds').show();
} else {
console.log('No pithos view, remove credential button');
$('#get_creds').hide();
}
});
function update_exclude(new_content) {
......@@ -46,26 +41,65 @@
//set_setting('token', $(this).val())
}
function remove_cookies(win, url) {
var removed_at_least_one = false
win.cookies.getAll({url: url}, function(cookies) {
$.each(cookies, function(i, cookie) {
win.cookies.remove({url: url, name: cookie.name} );
removed_at_least_one = true;
});
});
return removed_at_least_one;
}
var gui = require('nw.gui');
var cred_win = null;
var logout_win = null;
var got_cookie = false;
var show_creds = true;
function get_credentials() {
var cookie_name = '_pithos2_a';
var account_ui = get_account_ui()
var w = gui.Window.open(account_ui + '/logout', {
focus: false, width: 20, height: 20
});
w.close();
var next = get_pithos_ui();
var w = gui.Window.open(account_ui + '/login?next=' + next, {
focus: true, width: 520, height: 920
var lurl = get_account_ui() + '/logout?next=' + get_pithos_ui()
show_creds = false;
$('#get_creds').hide();
got_cookie = false;
cred_win = gui.Window.open(lurl, {
focus: true, width: 820, height: 580, toolbar: false
});
w.cookies.onChanged.addListener(function(info) {
cred_win.cookies.onChanged.addListener(function(info) {
if (info.cookie.name === cookie_name) {
console.log('Succesfully logged in');
extract_credentials(info.cookie);
w.close();
got_cookie = true;
}
});
cred_win.on('loaded', function() {
if (got_cookie) cred_win.close();
});
cred_win.on('closed', function() {
logout_win = gui.Window.open(
get_account_ui() + '/logout',
{focus: true, width:20, height: 20 });
logout_win.hide();
logout_win.on('loaded', function() {
while(remove_cookies(logout_win, get_pithos_ui())) {}
logout_win.close();
show_creds = true;
});
});
}
window.setInterval(function() {
// Refresh get_creds visibility, until refresh_endpoints
// changes are in effect
if (get_pithos_ui() && show_creds) {
$('#get_creds').show();
} else {$('#get_creds').hide(); }
}, 500);
</script>
</head>
<body>
......@@ -85,7 +119,7 @@
</div>
<div class="small-9 columns">
<input type="text" id="cloud-url" placeholder="Authentication URL"
onchange="set_setting('url', $(this).val()); refresh_endpoints($(this).val())">
onchange="set_setting('url', $(this).val()); refresh_endpoints($(this).val());">
<small class="error" style="visibility: hidden">Invalid entry</small>
</div>
</div>
......@@ -104,7 +138,7 @@
<div class="clearfix">
<a id="get_creds"
class="button right" style="display: none;"
onclick="get_credentials();">Get credentials</a>
onclick="get_credentials();">Login to get credentials</a>
</div>
</fieldset>
......
......@@ -4,8 +4,6 @@ import logging
from os.path import abspath
from agkyra.syncer import syncer
from agkyra.config import AgkyraConfig
from kamaki.clients.astakos import AstakosClient
from kamaki.clients.pithos import PithosClient
LOG = logging.getLogger(__name__)
......@@ -49,7 +47,6 @@ class WebSocketProtocol(WebSocket):
"url": <auth url>,
"container": <container>,
"directory": <local directory>,
"pithos_url": <pithos URL>,
"exclude": <file path>
}
HELPER: {"CREATED": 201, "action": "put settings",} or
......@@ -57,7 +54,11 @@ class WebSocketProtocol(WebSocket):
-- GET STATUS --
GUI: {"method": "get", "path": "status"}
HELPER: {"progress": <int>, "paused": <boolean>, "action": "get status"} or
HELPER: {
"can_sync": <boolean>,
"progress": <int>,
"paused": <boolean>,
"action": "get status"} or
{<ERROR>: <ERROR CODE>, "action": "get status"}
"""
......@@ -66,43 +67,74 @@ class WebSocketProtocol(WebSocket):
settings = dict(
token=None, url=None,
container=None, directory=None,
exclude=None, pithos_ui=None)
status = dict(progress=0, paused=True)
exclude=None)
status = dict(progress=0, paused=True, can_sync=False)
file_syncer = None
cnf = AgkyraConfig()
def _load_settings(self):
LOG.debug('Start loading settings')
def _get_default_sync(self):
"""Get global.default_sync or pick the first sync as default
If there are no syncs, create a 'default' sync.
"""
sync = self.cnf.get('global', 'default_sync')
cloud = self.cnf.get_sync(sync, 'cloud')
url = self.cnf.get_cloud(cloud, 'url')
token = self.cnf.get_cloud(cloud, 'token')
if not sync:
for sync in self.cnf.keys('sync'):
break
self.cnf.set('global', 'default_sync', sync or 'default')
return sync or 'default'
def _get_sync_cloud(self, sync):
"""Get the <sync>.cloud or pick the first cloud and use it
In case of cloud picking, set the cloud as the <sync>.cloud for future
sessions.
If no clouds are found, create a 'default' cloud, with an empty url.
"""
try:
cloud = self.cnf.get_sync(sync, 'cloud')
except KeyError:
cloud = None
if not cloud:
for cloud in self.cnf.keys('cloud'):
break
self.cnf.set_sync(sync, 'cloud', cloud or 'default')
return cloud or 'default'
astakos = AstakosClient(url, token)
self.settings['url'], self.settings['token'] = url, token
def _load_settings(self):
LOG.debug('Start loading settings')
sync = self._get_default_sync()
cloud = self._get_sync_cloud(sync)
try:
endpoints = astakos.get_endpoints()['access']['serviceCatalog']
for endpoint in endpoints:
if endpoint['type'] == PithosClient.service_type:
pithos_ui = endpoint['endpoints'][0]['SNF:uiURL']
self.settings['pithos_ui'] = pithos_ui
break
except Exception as e:
LOG.debug('Failed to retrieve pithos_ui: %s' % e)
self.settings['url'] = self.cnf.get_cloud(cloud, 'url')
except Exception:
self.settings['url'] = None
try:
self.settings['token'] = self.cnf.get_cloud(cloud, 'token')
except Exception:
self.settings['url'] = None
for option in ('container', 'directory', 'exclude'):
self.settings[option] = self.cnf.get_sync(sync, option)
try:
self.settings[option] = self.cnf.get_sync(sync, option)
except KeyError:
LOG.debug('No %s is set' % option)
LOG.debug('Finished loading settings')
def _dump_settings(self):
LOG.debug('Saving settings')
sync = self.cnf.get('global', 'default_sync')
cloud = self.cnf.get_sync(sync, 'cloud')
if not self.settings.get('url', None):
LOG.debug('No settings to save')
return
sync = self._get_default_sync()
cloud = self._get_sync_cloud(sync)
try:
old_url = self.cnf.get_cloud(cloud, 'url') or ''
except KeyError:
old_url = self.settings['url']
old_url = self.cnf.get_cloud(cloud, 'url')
while old_url != self.settings['url']:
cloud = '%s_%s' % (cloud, sync)
try:
......@@ -111,20 +143,26 @@ class WebSocketProtocol(WebSocket):
break
self.cnf.set_cloud(cloud, 'url', self.settings['url'])
self.cnf.set_cloud(cloud, 'token', self.settings['token'])
self.cnf.set_cloud(cloud, 'token', self.settings['token'] or '')
self.cnf.set_sync(sync, 'cloud', cloud)
for option in ('directory', 'container', 'exclude'):
self.cnf.set_sync(sync, option, self.settings[option])
self.cnf.set_sync(sync, option, self.settings[option] or '')
self.cnf.write()
LOG.debug('Settings saved')
def can_sync(self):
"""Check if settings are enough to setup a syncing proccess"""
return all([self.settings.get(key, False) for key in (
'url', 'token', 'directory', 'container')])
# Syncer-related methods
def get_status(self):
from random import randint
if self.status['progress'] < 100:
self.status['progress'] += 0 if randint(0, 2) else 1
self.status['can_sync'] = self.can_sync()
return self.status
def get_settings(self):
......@@ -138,7 +176,10 @@ class WebSocketProtocol(WebSocket):
self.status['paused'] = True
def start_sync(self):
self.status['paused'] = False
if self.can_sync():
self.status['paused'] = False
else:
self.status['paused'] = True
# WebSocket connection methods
def opened(self):
......
import cmd
import sys
import logging
from agkyra.syncer import setup, syncer
from agkyra.syncer.pithos_client import PithosFileClient
from agkyra.syncer.localfs_client import LocalfsFileClient
from agkyra import config
# from config import AgkyraConfig
LOG = logging.getLogger(__name__)
LOG.addHandler(logging.FileHandler('%s/agkyra.log' % config.AGKYRA_DIR))
LOG.setLevel(logging.CRITICAL)
SYNCER_LOG = logging.getLogger('agkyra.syncer')
SYNCER_LOG.addHandler(logging.FileHandler('%s/agkyra.log' % config.AGKYRA_DIR))
SYNCER_LOG.setLevel(logging.CRITICAL)
setup.GLOBAL_SETTINGS_NAME = config.AGKYRA_DIR
class AgkyraCLI(cmd.Cmd):
"""The CLI for """
cnf = config.AgkyraConfig()
is_shell = False
def init(self):
"""initialize syncer"""
# Read settings
sync = self.cnf.get('global', 'default_sync')
LOG.info('Using sync: %s' % sync)
cloud = self.cnf.get_sync(sync, 'cloud')
url = self.cnf.get_cloud(cloud, 'url')
token = self.cnf.get_cloud(cloud, 'token')
container = self.cnf.get_sync(sync, 'container')
directory = self.cnf.get_sync(sync, 'directory')
# Prepare syncer settings
self.settings = setup.SyncerSettings(
sync, url, token, container, directory)
LOG.info('Local: %s' % directory)
LOG.info('Remote: %s of %s' % (container, url))
# self.exclude = self.cnf.get_sync(sync, 'exclude')
# Init syncer
master = PithosFileClient(self.settings)
slave = LocalfsFileClient(self.settings)
self.syncer = syncer.FileSyncer(self.settings, master, slave)
def preloop(self):
"""This runs when the shell loads"""
print 'Loading Agkyra (sometimes this takes a while)'
if not self.is_shell:
self.is_shell = True
self.prompt = '\xe2\x9a\x93 '
self.init()
self.default('')
def print_option(self, section, name, option):
"""Print a configuration option"""
section = '%s.%s' % (section, name) if name else section
value = self.cnf.get(section, option)
print ' %s: %s' % (option, value)
def list_section(self, section, name):
"""list contents of a section"""
content = dict(self.cnf.items(section))
if section in 'global' and name:
self.print_option(section, '', name)
else:
if name:
content = content[name]
for option in content.keys():
self.print_option(section, name, option)
def list_section_type(self, section):
"""print the contents of a configuration section"""
names = ['', ] if section in ('global', ) else self.cnf.keys(section)
assert names, 'Section %s not found' % section
for name in names:
print section, name
self.list_section(section, name)
def list_sections(self):
"""List all configuration sections"""
for section in self.cnf.sections():
self.list_section_type(section)
def do_list(self, line):
"""List current settings (\"help list\" for details)
list global List all settings
list global <option> Get the value of this global option
list cloud List all clouds
list cloud <name> List all options of a cloud
list cloud <name> <option> Get the value of this cloud option
list sync List all syncs
list sync <name> List all options of a sync
list sync <name> <option> Get the value of this sync option
"""
args = line.split()
try:
{
0: self.list_sections,
1: self.list_section_type,
2: self.list_section,
3: self.print_option
}[len(args)](*args)
except Exception as e:
sys.stderr.write('%s\n' % e)
cmd.Cmd.do_help(self, 'list')
def set_global_setting(self, section, option, value):
assert section in ('global'), 'Syntax error'
self.cnf.set(section, option, value)
def set_setting(self, section, name, option, value):
assert section in self.sections(), 'Syntax error'
self.cnf.set('%s.%s' % (section, name), option, value)
def do_set(self, line):
"""Set a setting"""
args = line.split()
try:
{
3: self.set_global_setting,
4: self.set_setting
}[len(args)](*args)
self.cnf.write()
except Exception as e:
sys.stderr.write('%s\n' % e)
cmd.Cmd.do_help(self, 'set')
def do_start(self, line):
"""Start syncing"""
if not getattr(self, '_syncer_initialized', False):
self.syncer.probe_and_sync_all()
self._syncer_initialized = True
if self.syncer.paused:
self.syncer.start_decide()
def do_pause(self, line):
"""Pause syncing"""
if not self.syncer.paused:
self.syncer.pause_decide()
def do_status(self, line):
"""Get current status (running/paused, progress)"""
print 'paused' if self.syncer.paused else 'running'
# def do_shell(self, line):
# """Run system, shell commands"""
# if getattr(self, 'is_shell'):
# os.system(line)
# else:
# try:
# self.prompt = '\xe2\x9a\x93 '
# self.is_shell = True
# finally:
# self.init()
# self.cmdloop()
def do_help(self, line):
"""List commands with \"help\" or detailed help with \"help cmd\""""
if not line:
self.default(line)
cmd.Cmd.do_help(self, line)
def do_quit(self, line):
"""Quit Agkyra shell"""
return True
def default(self, line):
"""print help"""
sys.stderr.write('Usage:\t%s <command> [args]\n\n' % self.prompt)
for arg in [c for c in self.get_names() if c.startswith('do_')]:
sys.stderr.write('%s\t' % arg[3:])
method = getattr(self, arg)
sys.stderr.write(method.__doc__.split('\n')[0] + '\n')
sys.stderr.write('\n')
def emptyline(self):
if not self.is_shell:
return self.default('')
def run_onecmd(self, argv):
self.prompt = argv[0]
self.init()
self.onecmd(' '.join(argv[1:]))
# AgkyraCLI().run_onecmd(sys.argv)
# or run a shell with
# AgkyraCLI().cmdloop()
"""
A Sync is a triplete consisting of at least the following:
* a cloud (a reference to a cloud set in the same config file)
* a container
* a local directory
Other parameters may also be set in the context of a sync.
The sync is identified by the "sync_id", which is a string
The operations of a sync are similar to the operations of a cloud, as they are
implemented in kamaki.cli.config
"""
import os
from re import match
from ConfigParser import Error
from kamaki.cli import config
# import Config, CLOUD_PREFIX
from kamaki.cli.config import Config
from kamaki.cli.utils import escape_ctrl_chars
CLOUD_PREFIX = config.CLOUD_PREFIX
config.HEADER = '# Agkyra configuration file version XXX\n'
AGKYRA_DIR = os.environ.get('AGKYRA_DIR', os.path.expanduser('~/.agkyra'))
config.CONFIG_PATH = '%s%sconfig.rc' % (AGKYRA_DIR, os.path.sep)
# neutralize kamaki CONFIG_ENV for this session
config.CONFIG_ENV = ''
SYNC_PREFIX = 'sync'
config.DEFAULTS = {
'global': {
'agkyra_dir': AGKYRA_DIR,
},
CLOUD_PREFIX: {
# <cloud>: {
# 'url': '',
# 'token': '',
# whatever else may be useful in this context
# },
# ... more clouds