diff --git a/agkyra/exclude.cnf b/agkyra/exclude.cnf new file mode 100644 index 0000000000000000000000000000000000000000..a423cbf72687de8cf6eb04cc68daf5880a946759 --- /dev/null +++ b/agkyra/exclude.cnf @@ -0,0 +1,2 @@ +.* +lalaumpa diff --git a/agkyra/gui/about.html b/agkyra/gui/about.html index fc1db07ada1a44a7bdf2f6b343ae0e882d47a4d9..b4b7a8fe429584b038e9a60182fe72aff8ace80f 100644 --- a/agkyra/gui/about.html +++ b/agkyra/gui/about.html @@ -8,7 +8,7 @@ <style> .box { margin: 0 auto; - width: 95%; + width: 90%; height: 256px; overflow: auto; background: #e0e0e0; @@ -22,9 +22,9 @@ <h3>Agkyra... it syncs</h3> <p><b>Agkyra</b> is a minimal syncing client for Pithos+.<br/>It syncs a Pithos+ container with a local folder.</p> - <p class="disclaimer">Developed and supported by the Okeanos/Synnefo development team of GRNET<br/><b>okeanos-dev@grnet.gr</b></p> + <p class="disclaimer">Developed and supported by the Okeanos/Synnefo development team of GRNET<br/><b>contact: okeanos-dev@grnet.gr</b></p> - <h3 class="disclaimer">Copyright 2015: Greek Research and Technology Network<br/>Licensed under:</h3> + <h3 class="disclaimer">(C) 2015: Greek Research and Technology Network<br/>Licensed under:</h3> <embed class="box" src="COPYRIGHT" /> </div> <footer class="footer js-footer"> diff --git a/agkyra/gui/menu.html b/agkyra/gui/menu.html new file mode 100644 index 0000000000000000000000000000000000000000..1b76a8a7792a8ec0694af53f92be2d21ae6a01c9 --- /dev/null +++ b/agkyra/gui/menu.html @@ -0,0 +1,166 @@ +<!DOCTYPE html> +<html> +<head><title>GUI for Agkyra Pithos+ Syncing Client</title></head> +<body> + <script src="protocol.js"></script> + <script src="settings.js"></script> + <script type="text/javascript"> + + // Setup GUI +var windows = { + "settings": null, + "about": null, + "index": gui.Window.get() +} +function closeWindows() { + for (win in windows) if (windows[win]) windows[win].close(); +} + +// GUI components +var tray = new gui.Tray({ + // tooltip: 'Paused (0% synced)', + title: 'Agkyra syncs with Pithos+', + icon: 'images/tray.png' +}); + +var menu = new gui.Menu(); + +// Progress and Pause +var start_syncing = 'Start Syncing'; +var start_icon = 'images/play.png'; +var pause_syncing = 'Pause Syncing'; +var paused = true; + +progress_item = new gui.MenuItem({ + // progress menu item + label: 'Initializing', + type: 'normal', + enabled: false +}); +menu.append(progress_item); +menu.append(new gui.MenuItem({type: 'separator'})); +pause_item = new gui.MenuItem({ + // pause menu item + icon: 'images/play_pause.png', + label: '', + type: 'normal', + click: function() { + if (paused) {post_start(socket);} else {post_pause(socket);} + } +}); +pause_item.enabled = false; +menu.append(pause_item); + +// Update progress +window.setInterval(function() { + var status = globals['status']; + var new_progress = progress_item.label; + var new_pause = pause_item.label; + var menu_modified = false; + if (status['paused'] !== null) { + switch(pause_item.label) { + 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'; + break; + case start_syncing: if (status['paused']) return; + // else update to "Syncing - pause syncing" + paused = false; + new_pause = pause_syncing; + new_progress = status['progress'] + '%' + ' synced'; + menu_modified = true; + break; + default: + if (status['paused']) {new_pause = start_syncing; paused=true;} + else {new_pause = pause_syncing; paused=false;} + new_progress = status['progress'] + '%' + ' synced'; + pause_item.enabled = true; + menu_modified = true; + } + } + if (new_pause != pause_item.label) { + pause_item.label = new_pause; + menu_modified = true; + } + if (new_progress != progress_item.label) { + progress_item.label = new_progress; + menu_modified = true; + } + if (menu_modified) { + if (paused) progress_item.label += ' - paused'; + tray.menu = menu; + } + get_status(socket); +}, 1500); + +// Menu actions contents +menu.append(new gui.MenuItem({ + label: 'Open local folder', + icon: 'images/folder.png', + click: function () { + var dir = globals['settings']['directory']; + console.log('Open ' + dir); + gui.Shell.showItemInFolder(dir); + } +})); + +menu.append(new gui.MenuItem({ + label: 'Launch Pithos+ page', + icon: 'images/pithos.png', + click: function () { + var pithos_url = globals['settings']['pithos_url']; + console.log('Visit ' + pithos_url); + gui.Shell.openExternal(pithos_url); + } +})); + +// Settings and About +menu.append(new gui.MenuItem({type: 'separator'})); +menu.append(new gui.MenuItem({ + label: 'Settings', + icon: 'images/settings.png', + click: function () { + export_settings(globals.settings); + if (windows['settings']) windows['settings'].close(); + windows['settings'] = gui.Window.open("settings.html", { + toolbar: false, focus: true, + width: 841, height: 520, + }); + windows['settings'].on('closed', function() { + new_settings = import_settings(); + console.log('Settings windows is closed, PUT'); + put_settings(socket, new_settings); + get_settings(socket); + }); + }, +})); + +menu.append(new gui.MenuItem({ + label: 'About', + icon: 'images/about.png', + click: function () { + if (windows['about']) windows['about'].close(); + windows['about'] = gui.Window.open("about.html", { + toolbar: false, resizable: false, focus: true, + width: 679, height: 420 + }); + } +})); + +// Quit +menu.append(new gui.MenuItem({type: 'separator'})); +menu.append(new gui.MenuItem({ + label: 'Quit Agkyra', + icon: 'images/exit.png', + click: function() {post_shutdown(socket);} +})); + +tray.menu = menu; + + </script> +</body> +</html> \ No newline at end of file diff --git a/agkyra/gui/package.json b/agkyra/gui/package.json index 2374ca5c8bbab9b3dcef5c332f16c6006a22992a..edf1c1a7f8d32ba91dc69c2a0603e8800d2adfeb 100644 --- a/agkyra/gui/package.json +++ b/agkyra/gui/package.json @@ -1,6 +1,6 @@ { "name": "Agkyra", - "main": "index.html", + "main": "menu.html", "description": "A Pithos+ Syncing Client for Desktop", "version": 0.0, "window": { diff --git a/agkyra/gui/protocol.js b/agkyra/gui/protocol.js index 8be2c88068ddf0d8f09e73b7fa14bb7fe8bd71ac..5d198f48c723cfae3ad78e4f9aca5a56cfc7eebe 100644 --- a/agkyra/gui/protocol.js +++ b/agkyra/gui/protocol.js @@ -10,7 +10,6 @@ function send_json(socket, msg) { socket.send(JSON.stringify(msg)) } -var requests = [] var globals = { 'settings': { 'token': null, @@ -25,7 +24,6 @@ var globals = { // Protocol: requests ::: responses function post_gui_id(socket) { - requests.push('post gui_id'); send_json(socket, {"method": "post", "gui_id": cnf['gui_id']}) } // expected response: {"ACCEPTED": 202} @@ -36,30 +34,25 @@ function post_shutdown(socket) { function post_pause(socket) { console.log('SEND post pause'); - requests.push('post pause'); send_json(socket, {'method': 'post', 'path': 'pause'}); } // expected response: {"OK": 200} function post_start(socket) { console.log('SEND post start'); - requests.push('post start'); send_json(socket, {'method': 'post', 'path': 'start'}); } // expected response: {"OK": 200} function get_settings(socket) { - requests.push('get settings'); send_json(socket, {'method': 'get', 'path': 'settings'}); } // expected response: {settings JSON} function put_settings(socket, new_settings) { - requests.push('put settings'); new_settings['method'] = 'put'; new_settings['path'] = 'settings'; send_json(socket, new_settings); } // expected response: {"CREATED": 201} function get_status(socket) { - requests.push('get status'); send_json(socket, {'method': 'get', 'path': 'status'}); } // expected response {"progress": ..., "paused": ...} @@ -72,7 +65,8 @@ socket.onopen = function() { } socket.onmessage = function(e) { var r = JSON.parse(e.data) - switch(requests.shift()) { + //console.log('RECV: ' + r['action']) + switch(r['action']) { case 'post gui_id': if (r['ACCEPTED'] === 202) { get_settings(this); @@ -101,7 +95,8 @@ socket.onmessage = function(e) { console.log('Helper: ' + JSON.stringify(r)); } break; - case 'get status': globals['status'] = r; + case 'get status': + globals['status'] = r; break; default: console.log('Incomprehensible response ' + r); @@ -110,153 +105,9 @@ socket.onmessage = function(e) { }; socket.onerror = function (e) { console.log('GUI - helper error' + e.data); - gui.Window.get().close(); + closeWindows(); } socket.onclose = function() { console.log('Connection to helper closed'); closeWindows(); } - -// Setup GUI -var windows = { - "settings": null, - "about": null, - "index": gui.Window.get() -} -function closeWindows() { - for (win in windows) if (windows[win]) windows[win].close(); -} - -// GUI components -var tray = new gui.Tray({ - // tooltip: 'Paused (0% synced)', - title: 'Agkyra syncs with Pithos+', - icon: 'images/tray.png' -}); - -var menu = new gui.Menu(); - -// Progress and Pause -var start_syncing = 'Start Syncing'; -var start_icon = 'images/play.png'; -var pause_syncing = 'Pause Syncing'; -var paused = true; - -progress_item = new gui.MenuItem({ - // progress menu item - label: 'Initializing', - type: 'normal', - enabled: false -}); -menu.append(progress_item); -menu.append(new gui.MenuItem({type: 'separator'})); -pause_item = new gui.MenuItem({ - // pause menu item - icon: 'images/play_pause.png', - label: '', - type: 'normal', - click: function() { - if (paused) {post_start(socket);} else {post_pause(socket);} - } -}); -pause_item.enabled = false; -menu.append(pause_item); - -// Update progress -window.setInterval(function() { - var status = globals['status']; - var new_progress = progress_item.label; - var new_pause = pause_item.label; - var menu_modified = false; - if (status['paused'] !== null) { - switch(pause_item.label) { - 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'; - break; - case start_syncing: if (status['paused']) return; - // else update to "Syncing - pause syncing" - paused = false; - new_pause = pause_syncing; - new_progress = status['progress'] + '%' + ' synced'; - menu_modified = true; - break; - default: - if (status['paused']) {new_pause = start_syncing; paused=true;} - else {new_pause = pause_syncing; paused=false;} - new_progress = status['progress'] + '%' + ' synced'; - pause_item.enabled = true; - menu_modified = true; - } - } - if (new_pause != pause_item.label) { - pause_item.label = new_pause; - menu_modified = true; - } - if (new_progress != progress_item.label) { - progress_item.label = new_progress; - menu_modified = true; - } - if (menu_modified) { - if (paused) progress_item.label += ' - paused'; - tray.menu = menu; - } - get_status(socket); -}, 1500); - -// Menu actions contents -menu.append(new gui.MenuItem({ - label: 'Open local folder', - icon: 'images/folder.png', - click: function () { - var dir = globals['settings']['directory']; - console.log('Open ' + dir); - gui.Shell.showItemInFolder(dir); - } -})); - -menu.append(new gui.MenuItem({ - label: 'Launch Pithos+ page', - icon: 'images/pithos.png', - click: function () { - var pithos_url = globals['settings']['pithos_url']; - console.log('Visit ' + pithos_url); - gui.Shell.openExternal(pithos_url); - } -})); - -// Settings and About -menu.append(new gui.MenuItem({type: 'separator'})); -menu.append(new gui.MenuItem({ - label: 'Settings', - icon: 'images/settings.png', - click: function () { - if (windows['settings']) windows['settings'].close(); - windows['settings'] = gui.Window.open("settings.html", { - "toolbar": false, "focus": true}); - } -})); - -menu.append(new gui.MenuItem({ - label: 'About', - icon: 'images/about.png', - click: function () { - if (windows['about']) windows['about'].close(); - windows['about'] = gui.Window.open("about.html", { - "toolbar": false, "resizable": false, "focus": true}); - } -})); - -// Quit -menu.append(new gui.MenuItem({type: 'separator'})); -menu.append(new gui.MenuItem({ - label: 'Quit Agkyra', - icon: 'images/exit.png', - click: function() {post_shutdown(socket);} -})); - -tray.menu = menu; diff --git a/agkyra/gui/settings.html b/agkyra/gui/settings.html index 7510ca5b6633fc9a45b859b57e80b41ef9d413fd..e01ac268e45107da57b5b91a5a152f249cd4fc7b 100644 --- a/agkyra/gui/settings.html +++ b/agkyra/gui/settings.html @@ -1,12 +1,33 @@ <!DOCTYPE html> +<html> <head> <title>User Settings</title> <link rel="stylesheet" href="static/stylesheets/normalize.css" /> <link rel="stylesheet" href="static/stylesheets/main.css" /> <script src="static/js/jquery.js"></script> - <script src="static/js/common.js"></script> - </head> + <script src="settings.js"></script> + <script type="text/javascript"> + var fs = require('fs'); + var exclude = null; + $(document).ready(function() { + var url = get_setting('url'); + if (url) $('#cloud-url').val(url); + var token = get_setting('token'); + if (token) $('#token').val(token); + var container = get_setting('container'); + if (container) $('#container').val(container); + var directory = get_setting('directory'); + if (directory) $('#directory').html(directory); + exclude = get_setting('exclude'); + if (exclude) $('#exclude').val( + fs.readFileSync(exclude, encoding='utf-8')); + }); + function update_exclude(new_content) { + if (exclude) fs.writeFile(exclude, new_content); + } + </script> + </head> <body> <div class="row js-main"> <header> @@ -23,18 +44,20 @@ </div> <div class="small-9 columns"> - <input type="text" id="cloud-url" placeholder="Authentication URL"> - <small class="error">Invalid entry</small> + <input type="text" id="cloud-url" placeholder="Authentication URL" + onchange="set_setting('url', $(this).val())"> + <small class="error" style="visibility: hidden">Invalid entry</small> </div> </div> <div class="row error"> <div class="small-3 columns"> - <label for="token" class="right inline">Token</label> + <label for="token" class="right inline">User token</label> </div> <div class="small-9 columns"> - <input type="text" id="token" placeholder="User token"> - <small class="error">Invalid entry</small> + <input type="text" id="token" placeholder="User token" + onchange="set_setting('token', $(this).val())"> + <small class="error" style="visibility: hidden">Invalid entry</small> </div> </div> </fieldset> @@ -45,31 +68,43 @@ <label for="container" class="right inline">Remote container</label> </div> <div class="small-9 columns"> - <input type="text" id="container" value="pithos"> + <input type="text" id="container" placeholder="Pithos+ container" + onchange="set_setting('container', $(this).val())"> </div> </div> <div class="row"> <div class="small-3 columns"> - <label for="" class="right inline">Local directory</label> + <label for="directory" class="right inline">Local directory</label> </div> + <div class="small-9 columns" id="directory"> + No dir chosen</div> + </div> + <div class="row"> + <div class="small-3 columns">Change it:</div> <div class="small-9 columns"> - <input type="file" nwdirectory /> + <!-- TODO: change the label of this field --> + <input type="file" nwdirectory + onchange=" + $('#directory').html($(this).val()); + set_setting('directory', $(this).val()); + " /> </div> - <script>var gui=require('nw.gui');</script> </div> <div class="row"> <div class="small-3 columns"> <label for="" class="right inline">Exclude these items from syncing</label> </div> <div class="small-9 columns"> - <textarea>tmp/ -skata*</textarea> + <textarea id="exclude" + onchange="update_exclude($(this).val())"> + </textarea> </div> </div> </fieldset> - <div class="clearfix"> - <a class="button right">Sync</a> - </div> + <!--<div class="clearfix"> + <a id="sync_button" class="button right" + onclick="window.close();">OK</a> + </div> --> </div> <div class="small-4 columns"> diff --git a/agkyra/gui/settings.js b/agkyra/gui/settings.js new file mode 100644 index 0000000000000000000000000000000000000000..0df6d76633f5de0bff08bf8621ef9166344800f5 --- /dev/null +++ b/agkyra/gui/settings.js @@ -0,0 +1,19 @@ +/** +* Methods for accessing settings between documents +*/ + +function export_settings(settings) { + global.settings = settings; +} + +function import_settings() { + return global.settings; +} + +function get_setting(key) { + return global.settings[key]; +} + +function set_setting(key, val) { + global.settings[key] = val; +} diff --git a/agkyra/protocol.py b/agkyra/protocol.py index 8f33210a4e0b3f56f2bace13e7725c9125fe9483..7113a41fe906a5615d2a934d2abf451349c607bf 100644 --- a/agkyra/protocol.py +++ b/agkyra/protocol.py @@ -1,6 +1,7 @@ from ws4py.websocket import WebSocket import json import logging +from os.path import abspath LOG = logging.getLogger(__name__) @@ -11,23 +12,25 @@ class WebSocketProtocol(WebSocket): -- INTERRNAL HANDSAKE -- GUI: {"method": "post", "gui_id": <GUI ID>} - HELPER: {"ACCEPTED": 202}" or "{"REJECTED": 401} + HELPER: {"ACCEPTED": 202, "method": "post"}" or + "{"REJECTED": 401, "action": "post gui_id"} -- SHUT DOWN -- GUI: {"method": "post", "path": "shutdown"} -- PAUSE -- GUI: {"method": "post", "path": "pause"} - HELPER: {"OK": 200} or error + HELPER: {"OK": 200, "action": "post pause"} or error -- start -- GUI: {"method": "post", "path": "start"} - HELPER: {"OK": 200} or error + HELPER: {"OK": 200, "action": "post start"} or error -- GET SETTINGS -- GUI: {"method": "get", "path": "settings"} HELPER: { + "action": "get settings", "token": <user token>, "url": <auth url>, "container": <container>, @@ -45,11 +48,13 @@ class WebSocketProtocol(WebSocket): "pithos_url": <pithos URL>, "exclude": <file path> } - HELPER: {"CREATED": 201} or {<ERROR>: <ERROR CODE>} + HELPER: {"CREATED": 201, "action": "put settings",} or + {<ERROR>: <ERROR CODE>, "action": "get settings",} -- GET STATUS -- GUI: {"method": "get", "path": "status"} - HELPER: ""progress": <int>, "paused": <boolean>} or {<ERROR>: <ERROR CODE>} + HELPER: {"progress": <int>, "paused": <boolean>, "action": "get status"} or + {<ERROR>: <ERROR CODE>, "action": "get status"} """ gui_id = None @@ -59,7 +64,7 @@ class WebSocketProtocol(WebSocket): url=' https://accounts.okeanos.grnet.gr/identity/v2.0', container='pithos', directory='/tmp/.', - exclude='agkyra.log', + exclude=abspath('exclude.cnf'), pithos_url='https://pithos.okeanos.grnet.gr/ui/') status = dict(progress=0, paused=False) @@ -73,6 +78,9 @@ class WebSocketProtocol(WebSocket): def get_settings(self): return self.settings + def set_settings(self, new_settings): + self.settings = new_settings + def pause_sync(self): self.status['paused'] = True @@ -103,32 +111,40 @@ class WebSocketProtocol(WebSocket): 'start': self.start_sync, 'pause': self.pause_sync }[action]() - self.send_json({'OK': 200}) + self.send_json({'OK': 200, 'action': 'post %s' % action}) elif r['gui_id'] == self.gui_id: self.accepted = True - self.send_json({'ACCEPTED': 202}) + self.send_json({'ACCEPTED': 202, 'action': 'post gui_id'}) else: - self.send_json({'REJECTED': 401}) + action = r.get('path', 'gui_id') + self.send_json({'REJECTED': 401, 'action': 'post %s' % action}) self.terminate() def _put(self, r): """Handle PUT requests""" if not self.accepted: - self.send_json({'UNAUTHORIZED': 401}) + action = r['path'] + self.send_json({'UNAUTHORIZED': 401, 'action': 'put %s' % action}) self.terminate() else: LOG.debug('put %s' % r) + action = r.pop('path') + self.set_settings(r) + r.update({'CREATED': 201, 'action': 'put %s' % action}) + self.send_json(r) def _get(self, r): """Handle GET requests""" + action = r.pop('path') if not self.accepted: - self.send_json({'UNAUTHORIZED': 401}) + self.send_json({'UNAUTHORIZED': 401, 'action': 'get %s' % action}) self.terminate() else: data = { 'settings': self.get_settings, 'status': self.get_status, - }[r.pop('path')]() + }[action]() + data['action'] = 'get %s' % action self.send_json(data) def received_message(self, message):