From f121ea35b23aee90af2cb3b33b0b545f61c1391c Mon Sep 17 00:00:00 2001 From: Stavros Sachtouris <saxtouri@admin.grnet.gr> Date: Thu, 25 Jun 2015 16:24:54 +0300 Subject: [PATCH] Use common status codes across UI components All status codes are defined in an external JSON file named "ui_common.json". This file is loaded by the UI components if they must exchange statuses. Also, the protocol for propagating the status of the server has been modified. Instead of using separate flags to denote if the server is in some status or not, we now use a status code which must be defined in the common JSON file. In the present commit, the WebSocket server is managing status codes in that fashion, and the GUI is able to understand the responses and adjust its behavior accordingly. --- agkyra/nwgui/menu.html | 154 +++++++----------- agkyra/nwgui/notify.js | 21 ++- agkyra/nwgui/protocol.js | 23 +-- agkyra/nwgui/static/images/tray_off.png | Bin 0 -> 1395 bytes agkyra/nwgui/static/images/tray_warning.png | Bin 0 -> 1347 bytes agkyra/protocol.py | 164 +++++++++++--------- agkyra/scripts/cli.py | 4 +- agkyra/scripts/gui.py | 5 +- agkyra/scripts/server.py | 3 +- agkyra/ui_common.json | 16 ++ 10 files changed, 202 insertions(+), 188 deletions(-) create mode 100644 agkyra/nwgui/static/images/tray_off.png create mode 100644 agkyra/nwgui/static/images/tray_warning.png create mode 100644 agkyra/ui_common.json diff --git a/agkyra/nwgui/menu.html b/agkyra/nwgui/menu.html index cfc1f4a..2be192d 100644 --- a/agkyra/nwgui/menu.html +++ b/agkyra/nwgui/menu.html @@ -66,7 +66,9 @@ pause_item = new gui.MenuItem({ label: 'NOT READY', type: 'normal', click: function() { - if (paused) {post_start(socket);} else {post_pause(socket);} + if (globals.status.code == STATUS['PAUSED']) post_start(socket); + else if (globals.status.code == STATUS['SYNCING']) post_pause(socket); + else log_debug('Illegal click - status code is ' + globals.status.code); } }); pause_item.enabled = false; @@ -159,12 +161,28 @@ menu.append(new gui.MenuItem({ })); +function activate_menu() { + if (!pause_item.enabled) pause_item.enabled = true; + if (!settings_menu.enabled) { + if (globals.settings.url) refresh_endpoints(globals.settings.url); + settings_menu.enabled = true; + tray.menu = menu; + } + if ((!pithos_page_menu.enabled) && get_pithos_ui() != null){ + pithos_page_menu.enabled = true; + tray.menu = menu; + } + if ((!local_folder_menu.enabled) && globals.settings.directory) { + local_folder_menu.enabled = true; + tray.menu = menu; + } +} + function deactivate_menu() { if ( pause_item.enabled || local_folder_menu.enabled || pithos_page_menu.enabled) { - progress_item.label = 'Settings window is open'; pause_item.enabled = false; local_folder_menu.enabled = false; pithos_page_menu.enabled = false; @@ -172,109 +190,57 @@ function deactivate_menu() { } } - // Update progress -var client_ready = false; window.setInterval(function() { - if (globals.settings_are_open) { - deactivate_menu(); - return; + var new_progress = notification[globals.status.code]; + var new_pause = ''; + switch(globals.status.code) { + case STATUS['UNINITIALIZED']: + case STATUS['INITIALIZING']: + case STATUS['SHUTING DOWN']: + deactivate_menu(); + new_pause = 'inactive'; + break; + case STATUS['SYNCING']: + activate_menu(); + new_progress += ', ' + remaining(globals.status) + ' remaining'; + new_pause = 'Pause' + break; + case STATUS['PAUSING']: + new_progress += ', ' + remaining(globals.status) + ' remaining'; + new_pause = 'waiting...' + pause_item.enabled = false; + break; + case STATUS['PAUSED']: + activate_menu(); + new_pause = 'Start syncing'; + if (remaining(globals.status) > 0) + new_progress += ', ' + remaining(globals.status) + ' remaining'; + break; + case STATUS['SETTINGS MISSING']: + case STATUS['AUTH URL ERROR']: + case STATUS['TOKEN ERROR']: + case STATUS['DIRECTORY ERROR']: + case STATUS['CONTAINER ERROR']: + deactivate_menu(); + new_pause = 'inactive'; + settings_menu.enabled = true; + break; } if (globals.open_settings) { + new_progress = 'Settings window is open'; globals.open_settings = false; settings_menu.click(); deactivate_menu(); - return; - } + } else if (globals.settings_are_open) deactivate_menu(); - var menu_modified = false; - if (!client_ready) { - if (!globals.authenticated) return; - client_ready = true; - } - - if (client_ready) { - pause_item.enabled = (pause_item.label !== 'inactive'); - 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) { - local_folder_menu.enabled = true; - tray.menu = menu; - } - } - } - - var status = globals['status']; - var new_progress = progress_item.label; - var new_pause = pause_item.label; - if (status.notification !== 0) { - new_progress = notifications[status.notification]; - new_pause = 'inactive'; - if (progress_item.label !== new_progress) { - notify_user(new_progress, 'critical'); - } - } - else if (!status.can_sync) { - if (globals.just_opened) new_progress = 'Connecting...' - else new_progress = 'Not able to sync' - new_pause = 'inactive' - pause_item.enabled = false; - } else { - 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 - break; - case start_syncing: if (!status.paused) { - //update to "Syncing - pause syncing" - paused = false; - new_pause = pause_syncing; - menu_modified = true; - } - break; - default: - if (status.paused) {new_pause = start_syncing; paused=true;} - else {new_pause = pause_syncing; paused=false;} - pause_item.enabled = true; - menu_modified = true; - } - } - - var remaining = status.unsynced - status.synced; - if (status.paused){ - if (remaining) - new_progress = 'Pausing, ' + remaining + ' remain'; - else new_progress = 'Paused'; - } else { - if (remaining) - new_progress = 'Syncing, ' + remaining + ' remain'; - else new_progress = 'Running, all synced'; - } - } - if (new_pause !== pause_item.label.slice(0, new_pause.length)) { - pause_item.label = new_pause; - menu_modified = true; - } - if (new_progress !== progress_item.label) { + if (new_progress !== progress_item.label + || new_pause !== pause_item.label) { progress_item.label = new_progress; - menu_modified = true; + pause_item.label = new_pause; + tray.menu = menu; } - if (menu_modified) tray.menu = menu; get_status(socket); }, 1500); diff --git a/agkyra/nwgui/notify.js b/agkyra/nwgui/notify.js index 59735b1..93adbc2 100644 --- a/agkyra/nwgui/notify.js +++ b/agkyra/nwgui/notify.js @@ -1,5 +1,24 @@ var gui = require('nw.gui'); +var notification = { + 0: 'Not initialized', + 1: 'Initializing ...', + 2: 'Shutting down', + 100: 'Syncing', + 101: 'Pausing', + 102: 'Paused', + 200: 'Settings are incomplete', + 201: 'Cloud URL error', + 202: 'Authentication error', + 203: 'Local directory error', + 204: 'Remote container error', + 1000: 'Critical error' +} + +function is_up(code) { return (code / 100 >> 0) === 1; } +function has_settings_error(code) { return (code / 200 >> 0) === 2; } +function remaining(status) { return status.unsynced - status.synced; } + var ntf_title = { 'info': 'Notification', 'warning': 'Warning', @@ -16,7 +35,7 @@ var notify_menu = new gui.MenuItem({ icon: 'static/images/play_pause.png', iconIsTemplate: false, click: function() { - console.log('Notification is clecked'); + console.log('Notification is clicked'); } }); diff --git a/agkyra/nwgui/protocol.js b/agkyra/nwgui/protocol.js index f760d2a..bf4a61a 100644 --- a/agkyra/nwgui/protocol.js +++ b/agkyra/nwgui/protocol.js @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. -var DEBUG = false; +var DEBUG = true; var gui = require('nw.gui'); var path = require('path'); @@ -21,6 +21,7 @@ var fs = require('fs'); // Read config file var cnf = JSON.parse(fs.readFileSync(gui.App.argv[0], encoding='utf-8')); +var UI_COMMON = JSON.parse(fs.readFileSync(path.join('..', 'ui_common.json'))); function log_debug(msg) { if (DEBUG) console.log(msg); } @@ -28,12 +29,7 @@ function send_json(socket, msg) { socket.send(JSON.stringify(msg)); } -var notifications = { - 0: 'Syncer is consistent', - 1: 'Local directory is not accessible', - 2: 'Remote container is not accessible', - 100: 'Unknown error' -} +var STATUS = UI_COMMON['STATUS']; var globals = { settings: { @@ -43,10 +39,8 @@ var globals = { directory: null, exclude: null }, - status: { - synced: 0, unsynced: 0, paused: null, can_sync: false, notification: 0}, + status: {synced: 0, unsynced: 0, code: STATUS['UNINITIALIZED']}, authenticated: false, - just_opened: false, open_settings: false, settings_are_open: false } @@ -87,7 +81,7 @@ function put_settings(socket, new_settings) { function get_status(socket) { send_json(socket, {'method': 'get', 'path': 'status'}); -} // expected response {"synced":.., "unsynced":.., "paused":.., "can_sync":..} +} // expected response {"synced":.., "unsynced":.., "code":..} // Connect to helper @@ -111,7 +105,6 @@ socket.onmessage = function(e) { get_settings(this); get_status(this); globals.authenticated = true; - globals.just_opened = true; } else { log_debug('Helper: ' + JSON.stringify(r)); closeWindows(); @@ -139,10 +132,8 @@ socket.onmessage = function(e) { break; case 'get status': globals['status'] = r; - if (globals.just_opened) { - globals.just_opened = false; - globals.open_settings = !r.can_sync; - } + if (!globals.open_settings) + globals.open_settings = has_settings_error(r.code); break; default: console.log('Incomprehensible response ' + JSON.stringify(r)); diff --git a/agkyra/nwgui/static/images/tray_off.png b/agkyra/nwgui/static/images/tray_off.png new file mode 100644 index 0000000000000000000000000000000000000000..e42697a5658dab856c82189545755a0343c71d00 GIT binary patch literal 1395 zcmV-(1&sQMP)<h;3K|Lk000e1NJLTq000yK000yS1^@s7`i?D|00004XF*Lt006O% z3;baP00006VoOIv0Pg@^001@R=*s{A010qNS#tmY4c7nw4c7reD4Tcy000McNliru z-v$v32p<HJznK641n@~jK~y-)ZIgRUm1P{qzt8)g^PZa=Km|c;icmN&Tc{vr(-s@E z(4oOdQ-jw{Q#eeqn3`GH%Cuu`g@|N&bf%@1msCt^HLyg`w1k|Xg4_a>t8i}b`&{}% zXyEw%_5AVuexC2M-|qnce6mKJ3IMXxp#~l63*VSFG3Y!2fW>N#xYQ#6XSTWs03?a( z{$<Now@XY`>}@as0N~73<(+FX)6p*j00?TNzj>|L`Nqm?pLPZRLYxkjKwyK%s~vdn zz>zKhkn(oB1b~@La54~=o!RR758FbLW*8pD#70JTR@|<slOSN9X64qR-qC4Ri*h-7 z<Dn1&0J-b(YAg@$KkcFu&~n^K{^tOIGh00f4!Ax3QcctE^m=`r`KQkO0{{l6LnR=P z!Qm#HT?pl#y-k+Di|-#gN(VzE09!yogdiy%m=?P9sB>RG3f59H9Ab6)lF9)i>A$i? z5~84lNBlTLZwO<QEh^|8xpp}kJ~$CHi<+&$`yLxtsst9aGb;E#J!*7k_d`Ds%qutz z7%}RpQlrJ5NCrKm5CT6_onQ57{<dw0zSRbIlm`w;39%&*$Y+cP>y*iH&qPO;-MW2m z$Z!Av9P0u6!Gt(!3o+m8t#$LkM1#|znjlaJkXd6S?Px9M*!tOFh9@KeC<M?T=De1C z(muEQ7@1VQWog=l)TOK3LpmnK#Wq03Nz_&D?{<4P)cT3}1{pIxO!Ao$Bge3-)itF9 z#Oa{u{7DN<Dfe5EVB^%M*p5ePtr6u@UOgDLWRrMoB(Q_vVxqY_eTBUvG|Jcbu8n$L zthYoI&#fyCGFk%uowV)yaT0+1yaLx^EAzbccc1j2Qi<4}?!OuKYY&_Jon`7e&NQ2+ z@$SRNYYI#ULq)T0Y(B@vg|>IKdI^M$5MmrZZ^ek~>u0+OhLO{u#(-lXfboJOiZN!? zC}WgTx2Dq;ow8b;va0-3XD;Tvl0x!NoEAftXIN;qN+2*_aJ;TjeU{Ishx;|1YLvP> zKK-qI#pku*a)v>z0s+hhM|s{JS68nv+f7^>Nd)ia9Cr!99T2n<kSeoLHs+)((DP59 z6;A@?ewYLSEw8|JCoyjNl;oMy4mQcbMQ7bXi|a)|TsZMZDNK*zLIe;(6hROwxX>%A zYg%$r7LbbS8Vvm}S!x&vwgXTh09lf8*c}cp<!AlWF9q3bGghG{>!ZL0-EB?PyCmw4 z)t2s>fHYD+Wy4;hP%P}$jtmX}#OYAWAdmn65a<JNSr^hJA5pF}!)Ctbwd`-~Y7bS+ zc4Urdzr=dlb;{Anx%g{-eevhnFBE1Pj7nwA&Ww_nw~r9wbf|{B-7X3MyVhpL7>uTQ z-}Hr~-tN<rGC~-h@-=}{fl}iHTzT*a&j%}*<ZYxnWgU0z6TR1|J&<c|*%BPl95iQs zya52~pK6u>w&_$Gm1=4xLIDKjx}O-!G)eqE)!T4|hfO4Z_V`YhiAaMzDmFP`&9W-~ z&gLH^#rkqxQSRiiy&VeaABvo}+r^v?b)68Tiu-yiH_bclQ!ad5B6rq48!$4W`8f+c zIm&Ffai4^Q+-$Ag%5;wvTo4cm_Oa1B?iaqfIXbsU9)7X2)a8`%WX-R~*PB8O=DrSJ zb3pW0*EJ=c`TF98V8mdyb}9iOU*7v-?ViEe{{VuvXr7j`y_o<2002ovPDHLkV1k5L Bi=F@g literal 0 HcmV?d00001 diff --git a/agkyra/nwgui/static/images/tray_warning.png b/agkyra/nwgui/static/images/tray_warning.png new file mode 100644 index 0000000000000000000000000000000000000000..6812410287ad0ebffe7acaa23ac7f9014a819ee4 GIT binary patch literal 1347 zcmV-J1-$x+P)<h;3K|Lk000e1NJLTq000yK000yS1^@s7`i?D|00004XF*Lt006O% z3;baP00006VoOIv0Pg@^001@R=*s{A010qNS#tmY4c7nw4c7reD4Tcy000McNliru z-v$v3F)*RW4|)Iq1i(o|K~y-)g_CViRAm&$|L5MlyIfe7MS0D4snHL_Ov)_Qgf>~a z;#eSQqc*AKOol_Fy`UzJX{j(eqb0%&Q@)m&QAz?SmLw`lVrq*es5Fv<^0F+;UM{<L z?>&7m2+f)PpPrfLf6jA$=R5}hzyV4G0Jxd#sVm>uSzAE_0NAj9GSiGV86Ku|01zU; z{^Em49fD?vwz}qF008{LBnDNQi@^#QGQU;iWW7TkE55Lz32r9RnBMWUBC_8k3?XJ6 zNDlZw1EC7j_n5Lqx#=<=s;!`@0052Y?Cgzm8Pl_j#*2`n-uo&V0C1{dLgOtLSk8Ex z0Sw9mz{j*FEC{$;x!Tu->vvn=cBJ8IKQMJLnN%h}8~BbHX&v4$6CPAIQ*Jwx<TpfT zhsVYXw#?(CtR+U?-c&wTI@EQaSz&5rMsCjMnMs|k;<VQ5Vj-MaTvv(p^8Ea@(MyZt z2mqLg0zikTz!;vrF6vH;+Ybt;U@hDlGq&0sW}iA3q)0btw%>yhZ4EF#SmFTn9VTlO zP!Z_jlb&|exbK1zC*yek{QzAUu$&B>4Fqr?ek5+T{Rv}mMw6GxLO_uPlp+Ymfk03I z$%g*m(}DpMi9qvT_+aL{rE5J;Z(db<+lAx##mUNj#t>kK3Tz%8T(^C^rK-T|3)&O& za-UT>B=qzVA$PzS1SZ(Q?$-+EyqH&-f|VKdL5~`DD1Cg*u<YM<@3Ca$aQ_Hb^fDdp z>b1@3=!uPPZm_J5el@rCqg_)jU#hYthA-II61To=IC+^&HsT;NcI6*@YH?2>28+fW z!DG2gwea|ndv(n}N3?{+Qa2L;m=JEz&$X^cT3#+#6w}<?u7Zp(B9anjWm|77dt4zr zL>{Jbp};JWF-iy`!LP8Ss?dAAP*fjURRv9|f_g_9D(jAclAU1upRTJ!L8cjopT1Pu z(A3fZ_1Zb~)K%h>6Z`-B6$ZN4e`sfl=w@>EGDQM_7KpO-D3&3r-{jGBx&cNt0pALQ z`fxj>+HyRU&IABmOo9aTSc#8yYtlq!ZByCh(a+7@^CU(^DMDW6QN)<qAcdkyHeeP6 z$o@W@0dTRl9F#KiUu61cx#A>XlK?C>fgwUfO3pHPeN^V_)r1l8=7}|%w`bAo6&_su zeJkL30(~1s;|kqM$SIOE$)-1NTwnkI^)Q_ifl0!>a(%?8QB}+7PM_`knTCXr?MIV8 ztNXPtMhc09D}VQCxZAZp4Q{QEE>E1sAKSC{Eeeq(*WE4iPQ|V+A@VSp?8L!)3yX1p zdCK$+3o|RLW2aSZE5|blm!K=a@{*TOaCit-Xa0eONhgAeLY-eXI@N7eDG?m-iq@#n zEjH(D2dPXX6SKmUB>?Nqz)<K!Mv#DjuK5FW&gV6q>Fg5E7u06Zz8_*rRlf*%Olora z*7W+4`K!JWOy)H8(1(JYxyEdgiMfkO3?nY|Fi8dr^%hNcNiz3gQt>Gm7bYWiWWxIy zS8S8U^j>nOMy@J3=~=5ZmS6Z{RvD2XAYw#fo(jM^>{0!pqc;qCMfdScZ#OxPUvrqE zjaz*`-hTemoj)Zyf@-(J&~=hRt!|4wzPNMelET5Ue*>8dKuLgG+$#V8002ovPDHLk FV1ggFZK41G literal 0 HcmV?d00001 diff --git a/agkyra/protocol.py b/agkyra/protocol.py index e651f73..91b37c5 100644 --- a/agkyra/protocol.py +++ b/agkyra/protocol.py @@ -34,6 +34,10 @@ CURPATH = os.path.dirname(os.path.abspath(__file__)) LOG = logging.getLogger(__name__) SYNCERS = utils.ThreadSafeDict() +with open(os.path.join(CURPATH, 'ui_common.json')) as f: + UI_COMMON = json.load(f) +STATUS = UI_COMMON['STATUS'] + def retry_on_locked_db(method, *args, **kwargs): """If DB is locked, wait and try again""" @@ -66,8 +70,6 @@ class SessionHelper(object): self.db = sqlite3.connect(self.session_db) self._init_db_relation() - # self.session = self._load_active_session() or self._create_session() - # self.db.close() def _init_db_relation(self): """Create the session relation""" @@ -152,7 +154,7 @@ class SessionHelper(object): return not bool(self.load_active_session()) def heartbeat(self): - """General session heartbeat - when heart stops, WSGI server dies""" + """Periodically update the session database timestamp""" db, alive = sqlite3.connect(self.session_db), True while alive: time.sleep(2) @@ -249,19 +251,16 @@ class WebSocketProtocol(WebSocket): -- GET STATUS -- GUI: {"method": "get", "path": "status"} - HELPER: { - "can_sync": <boolean>, - "notification": <int>, - "paused": <boolean>, - "action": "get status"} or - {<ERROR>: <ERROR CODE>, "action": "get status"} + HELPER: {"code": <int>, + "synced": <int>, + "unsynced": <int>, + "action": "get status" + } or {<ERROR>: <ERROR CODE>, "action": "get status"} """ - notification = { - 0: 'Syncer is consistent', - 1: 'Local directory is not accessible', - 2: 'Remote container is not accessible', - 100: 'unknown error' - } + status = utils.ThreadSafeDict() + with status.lock() as d: + d.update(code=STATUS['UNINITIALIZED'], synced=0, unsynced=0) + ui_id = None session_db, session_relation = None, None accepted = False @@ -269,21 +268,37 @@ class WebSocketProtocol(WebSocket): token=None, url=None, container=None, directory=None, exclude=None) - status = dict( - notification=0, synced=0, unsynced=0, paused=True, can_sync=False) cnf = AgkyraConfig() essentials = ('url', 'token', 'container', 'directory') + def get_status(self, key=None): + """:return: updated status dict or value of specified key""" + if self.syncer and self.can_sync(): + self._consume_messages() + with self.status.lock() as d: + if self.syncer.paused: + d['code'] = STATUS['PAUSED'] + elif d['code'] != STATUS['PAUSING'] or ( + d['unsynced'] == d['synced']): + d['code'] = STATUS['SYNCING'] + with self.status.lock() as d: + return d.get(key, None) if key else dict(d) + + def set_status(self, **kwargs): + with self.status.lock() as d: + d.update(kwargs) + @property def syncer(self): + """:returns: the first syncer object or None""" with SYNCERS.lock() as d: for sync_key, sync_obj in d.items(): return sync_obj return None def clean_db(self): - """Clean DB from session traces""" - LOG.debug('Remove session traces') + """Clean DB from current session trace""" + LOG.debug('Remove current session trace') db = sqlite3.connect(self.session_db) db.execute('BEGIN') db.execute('DELETE FROM %s WHERE ui_id="%s"' % ( @@ -292,7 +307,7 @@ class WebSocketProtocol(WebSocket): db.close() def shutdown_syncer(self, syncer_key=0): - """Shutdown the service""" + """Shutdown the syncer backend object""" LOG.debug('Shutdown syncer') with SYNCERS.lock() as d: syncer = d.pop(syncer_key, None) @@ -302,7 +317,7 @@ class WebSocketProtocol(WebSocket): syncer.wait_sync_threads() def heartbeat(self): - """Check if socket should be alive""" + """Update session DB timestamp as long as session is alive""" db, alive = sqlite3.connect(self.session_db), True while alive: time.sleep(1) @@ -320,6 +335,7 @@ class WebSocketProtocol(WebSocket): alive = True db.close() self.shutdown_syncer() + self.set_status(code=STATUS['UNINITIALIZED']) self.close() def _get_default_sync(self): @@ -358,10 +374,12 @@ class WebSocketProtocol(WebSocket): self.settings['url'] = self.cnf.get_cloud(cloud, 'url') except Exception: self.settings['url'] = None + self.set_status(code=STATUS['SETTINGS MISSING']) try: self.settings['token'] = self.cnf.get_cloud(cloud, 'token') except Exception: self.settings['url'] = None + self.set_status(code=STATUS['SETTINGS MISSING']) # for option in ('container', 'directory', 'exclude'): for option in ('container', 'directory'): @@ -369,6 +387,7 @@ class WebSocketProtocol(WebSocket): self.settings[option] = self.cnf.get_sync(sync, option) except KeyError: LOG.debug('No %s is set' % option) + self.set_status(code=STATUS['SETTINGS MISSING']) LOG.debug('Finished loading settings') @@ -418,35 +437,31 @@ class WebSocketProtocol(WebSocket): return all([ self.settings[e] == self.settings[e] for e in self.essentials]) - def _consume_messages(self): + def _consume_messages(self, max_consumption=10): """Update status by consuming and understanding syncer messages""" if self.can_sync(): msg = self.syncer.get_next_message() if not msg: - if self.status['unsynced'] == self.status['synced']: - self.status['unsynced'] = 0 - self.status['synced'] = 0 - while (msg): + with self.status.lock() as d: + if d['unsynced'] == d['synced']: + d.update(unsynced=0, synced=0) + while msg: if isinstance(msg, messaging.SyncMessage): - # LOG.info('Start syncing "%s"' % msg.objname) - self.status['unsynced'] += 1 + self.set_status(unsynced=self.get_status('unsynced') + 1) elif isinstance(msg, messaging.AckSyncMessage): - # LOG.info('Finished syncing "%s"' % msg.objname) - self.status['synced'] += 1 - # elif isinstance(msg, messaging.CollisionMessage): - # LOG.info('Collision for "%s"' % msg.objname) - # elif isinstance(msg, messaging.ConflictStashMessage): - # LOG.info('Conflict for "%s"' % msg.objname) + self.set_status(synced=self.get_status('synced') + 1) elif isinstance(msg, messaging.LocalfsSyncDisabled): - # LOG.debug('Local FS is dissabled, noooo!') - self.status['notification'] = 1 + self.set_status(code=STATUS['DIRECTORY ERROR']) self.syncer.stop_all_daemons() elif isinstance(msg, messaging.PithosSyncDisabled): - # LOG.debug('Pithos sync is disabled, noooooo!') - self.status['notification'] = 2 + self.set_status(code=STATUS['CONTAINER ERROR']) self.syncer.stop_all_daemons() LOG.debug('Backend message: %s' % msg.name) msg = self.syncer.get_next_message() + # Limit the amount of messages consumed each time + max_consumption -= 1 + if not max_consumption: + break def can_sync(self): """Check if settings are enough to setup a syncing proccess""" @@ -454,6 +469,7 @@ class WebSocketProtocol(WebSocket): def init_sync(self): """Initialize syncer""" + self.set_status(code=STATUS['INITIALIZING']) sync = self._get_default_sync() kwargs = dict(agkyra_path=AGKYRA_DIR) @@ -480,46 +496,41 @@ class WebSocketProtocol(WebSocket): slave = localfs_client.LocalfsFileClient(syncer_settings) syncer_ = syncer.FileSyncer(syncer_settings, master, slave) self.syncer_settings = syncer_settings - # Check if syncer is ready, by consumming messages - msg = syncer_.get_next_message() - # while not msg: - # time.sleep(0.2) - # msg = syncer_.get_next_message() - - # This should be activated only on accepting a positive message - self.status['notification'] = 0 - self.status['unsynced'] = 0 - self.status['synced'] = 0 - - if msg: + # Check if syncer is ready, by consuming messages + local_ok, remote_ok = False, False + for i in range(2): + + LOG.debug('Get message %s' % (i + 1)) + msg = syncer_.get_next_message(block=True) + LOG.debug('Got message: %s' % msg) + if isinstance(msg, messaging.LocalfsSyncDisabled): - LOG.debug('Local FS is disabled') - self.status['notification'] = 1 + self.set_status(code=STATUS['DIRECTORY ERROR']) + local_ok = False + break elif isinstance(msg, messaging.PithosSyncDisabled): - LOG.debug('Pithos sync is disabled') - self.status['notification'] = 2 + self.set_status(code=STATUS['CONTAINER ERRIR']) + remote_ok = False + break + elif isinstance(msg, messaging.LocalfsSyncEnabled): + local_ok = True + elif isinstance(msg, messaging.PithosSyncEnabled): + remote_ok = True else: - LOG.debug("Unexpected message: %s" % msg) - msg = None - if msg: - syncer_ = None - else: + LOG.error('Unexpected message %s' % msg) + self.set_status(code=STATUS['CRITICAL ERROR']) + break + if local_ok and remote_ok: syncer_.initiate_probe() + self.set_status(code=STATUS['SYNCING']) + else: + syncer_ = None finally: + self.set_status(synced=0, unsynced=0) with SYNCERS.lock() as d: d[0] = syncer_ # Syncer-related methods - def get_status(self): - if self.syncer and self.can_sync(): - self._consume_messages() - self.status['paused'] = self.syncer.paused - self.status['can_sync'] = self.can_sync() - else: - self.status.update(dict( - synced=0, unsynced=0, paused=True, can_sync=False)) - return self.status - def get_settings(self): return self.settings @@ -544,10 +555,17 @@ class WebSocketProtocol(WebSocket): if was_active: self.start_sync() - def pause_sync(self): - self.syncer.stop_decide() + def _pause_syncer(self): + syncer_ = self.syncer + syncer_.stop_decide() LOG.debug('Wait open syncs to complete') - self.syncer.wait_sync_threads() + syncer_.wait_sync_threads() + + def pause_sync(self): + syncer_ = self.syncer + if syncer_ and not syncer_.paused: + Thread(target=self._pause_syncer).start() + self.set_status(code=STATUS['PAUSING']) def start_sync(self): self.syncer.start_decide() @@ -562,6 +580,8 @@ class WebSocketProtocol(WebSocket): if self.accepted: action = r['path'] if action == 'shutdown': + # Clean db to cause syncer backend to shut down + self.set_status(code=STATUS['SHUTTING DOWN']) retry_on_locked_db(self.clean_db) # self._shutdown() # self.terminate() diff --git a/agkyra/scripts/cli.py b/agkyra/scripts/cli.py index 09298b8..8f59589 100755 --- a/agkyra/scripts/cli.py +++ b/agkyra/scripts/cli.py @@ -31,10 +31,10 @@ LOGFILE = os.path.join(AGKYRA_DIR, 'agkyra.log') LOGGER = logging.getLogger('agkyra') HANDLER = logging.FileHandler(LOGFILE) FORMATTER = logging.Formatter( - "[CLI]%(name)s %(levelname)s:%(asctime)s:%(message)s") + "%(name)s:%(lineno)s %(levelname)s:%(asctime)s:%(message)s") HANDLER.setFormatter(FORMATTER) LOGGER.addHandler(HANDLER) -LOGGER.setLevel(logging.INFO) +LOGGER.setLevel(logging.DEBUG) def main(): diff --git a/agkyra/scripts/gui.py b/agkyra/scripts/gui.py index a10ed07..f826d36 100755 --- a/agkyra/scripts/gui.py +++ b/agkyra/scripts/gui.py @@ -30,10 +30,11 @@ import logging LOGFILE = os.path.join(AGKYRA_DIR, 'agkyra.log') LOGGER = logging.getLogger('agkyra') HANDLER = logging.FileHandler(LOGFILE) -FORMATTER = logging.Formatter("%(name)s %(levelname)s:%(asctime)s:%(message)s") +FORMATTER = logging.Formatter( + "%(name)s:%(lineno)s %(levelname)s:%(asctime)s:%(message)s") HANDLER.setFormatter(FORMATTER) LOGGER.addHandler(HANDLER) -LOGGER.setLevel(logging.INFO) +LOGGER.setLevel(logging.DEBUG) def main(): diff --git a/agkyra/scripts/server.py b/agkyra/scripts/server.py index d13c78a..6096a46 100644 --- a/agkyra/scripts/server.py +++ b/agkyra/scripts/server.py @@ -22,7 +22,8 @@ import logging LOGFILE = os.path.join(AGKYRA_DIR, 'agkyra.log') LOGGER = logging.getLogger('agkyra') HANDLER = logging.FileHandler(LOGFILE) -FORMATTER = logging.Formatter("%(name)s %(levelname)s:%(asctime)s:%(message)s") +FORMATTER = logging.Formatter( + "%(name)s:%(lineno)s %(levelname)s:%(asctime)s:%(message)s") HANDLER.setFormatter(FORMATTER) LOGGER.addHandler(HANDLER) LOGGER.setLevel(logging.DEBUG) diff --git a/agkyra/ui_common.json b/agkyra/ui_common.json new file mode 100644 index 0000000..a17fa39 --- /dev/null +++ b/agkyra/ui_common.json @@ -0,0 +1,16 @@ +{ + "STATUS": { + "UNINITIALIZED": 0, + "INITIALIZING": 1, + "SHUTTING DOWN": 2, + "SYNCING": 100, + "PAUSING": 101, + "PAUSED": 102, + "SETTINGS MISSING": 200, + "AUTH URL ERROR": 201, + "TOKEN ERROR": 202, + "DIRECTORY ERROR": 203, + "CONTAINER ERROR": 204, + "CRITICAL ERROR": 1000 + } +} \ No newline at end of file -- GitLab