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