make synchronization recurent, improve synchronization status display
authorymh <ymh.work@gmail.com>
Sun, 30 Jul 2017 01:02:09 +0200
changeset 130 78246db1cbac
parent 129 d48946d164c6
child 131 adad5563603c
make synchronization recurent, improve synchronization status display
client/.env
client/package.json
client/src/actions/syncActions.js
client/src/components/Navbar.js
client/src/components/Navbar.scss
client/src/config.js
client/src/constants/actionTypes.js
client/src/reducers/authReducer.js
client/src/reducers/syncReducer.js
client/src/sagas/networkSaga.js
client/src/sagas/selectors.js
client/src/sagas/syncSaga.js
client/src/sagas/utils.js
client/yarn.lock
src/notes/api/views/auth.py
--- a/client/.env	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/.env	Sun Jul 30 01:02:09 2017 +0200
@@ -2,3 +2,4 @@
 REACT_APP_BASENAME =
 REACT_APP_NETWORK_STATUS_INTERVAL = 2000
 REACT_APP_NETWORK_STATUS_TIMEOUT = 2000
+REACT_APP_SYNC_INTERVAL = 20000
--- a/client/package.json	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/package.json	Sun Jul 30 01:02:09 2017 +0200
@@ -22,7 +22,7 @@
     "redux-persist": "^4.8.2",
     "redux-persist-immutable": "^4.3.0",
     "redux-persist-transform-immutable": "^4.3.0",
-    "redux-saga": "^0.15.3",
+    "redux-saga": "^0.15.6",
     "slate": "^0.20.1",
     "uuid": "^3.0.1"
   },
--- a/client/src/actions/syncActions.js	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/src/actions/syncActions.js	Sun Jul 30 01:02:09 2017 +0200
@@ -12,3 +12,9 @@
     type: types.SYNC_END_SYNC
   }
 }
+
+export const startSynchronize = () => {
+  return {
+    type: types.SYNC_START_SYNC
+  }
+}
--- a/client/src/components/Navbar.js	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/src/components/Navbar.js	Sun Jul 30 01:02:09 2017 +0200
@@ -8,6 +8,7 @@
 import * as authActions from '../actions/authActions';
 import { forceSync } from '../actions/networkActions';
 import { ActionEnum } from '../constants';
+import './Navbar.css';
 
 const LoginNav = ({isAuthenticated, currentUser, history, authActions, onLogout}) => {
 
@@ -42,11 +43,22 @@
   )
 }
 
-const SyncButton = ({ onSyncClick, isSynchronizing }) => {
+const SyncButton = ({ onSyncClick, isSynchronizing, isSynchronized, id }) => {
+  const classnames = "material-icons"
+    + ((!isSynchronized)?" sync-button-not-synchronized":"")
+    + ((isSynchronizing)?" sync-button-synchronizing":"");
+  let title = "Synchronize";
+  let clickCb = onSyncClick;
+  if(isSynchronizing) {
+    title = "Synchronizing...";
+    clickCb = () => {};
+  } else if (!isSynchronized) {
+    title += ": not synchronized";
+  }
+
   return (
-    <NavItem onClick={onSyncClick}>
-      Sync
-      {isSynchronizing && <span className="material-icons">&#xE627;</span>}
+    <NavItem title={title} onClick={clickCb} id={id || null}>
+      <span className={classnames}>&#xE627;</span>
     </NavItem>
   )
 }
@@ -118,7 +130,7 @@
             <NavItem onClick={this.onClickSessions} href="/sessions">Sessions</NavItem>
           </Nav>
           <Nav pullRight>
-            <SyncButton onSyncClick={this.onSyncClick} isSynchronizing={this.props.isSynchronizing}/>
+            <SyncButton id='sync-button' onSyncClick={this.onSyncClick} isSynchronizing={this.props.isSynchronizing} isSynchronized={this.props.isSynchronized} />
             <Online {...this.props} />
             <LoginNav {...this.props} onLogout={this.onClickLogout} />
           </Nav>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Navbar.scss	Sun Jul 30 01:02:09 2017 +0200
@@ -0,0 +1,7 @@
+.sync-button-synchronizing {
+    color: green;
+}
+
+.sync-button-not-synchronized {
+    color: orange;
+}
--- a/client/src/config.js	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/src/config.js	Sun Jul 30 01:02:09 2017 +0200
@@ -4,5 +4,6 @@
   apiRootUrl: process.env.REACT_APP_API_ROOT_URL || 'http://localhost:8000',
   basename: process.env.REACT_APP_BASENAME || '',
   networkStatusTimeout: parseInt(process.env.REACT_APP_NETWORK_STATUS_TIMEOUT, 10) || 2000,
-  networkStatusInterval: parseInt(process.env.REACT_APP_NETWORK_STATUS_INTERVAL, 10) || 20000,
+  networkStatusInterval: parseInt(process.env.REACT_APP_NETWORK_STATUS_INTERVAL, 10) || 2000,
+  syncInterval: parseInt(process.env.REACT_APP_SYNC_INTERVAL, 10) || 20000,
 }
--- a/client/src/constants/actionTypes.js	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/src/constants/actionTypes.js	Sun Jul 30 01:02:09 2017 +0200
@@ -50,6 +50,7 @@
 export const DATA_FETCH_SUCCESS = 'DATA_FETCH_SUCCESS';
 
 export const SYNC_DO_SYNC = 'SYNC_DO_SYNC';
+export const SYNC_START_SYNC = 'SYNC_START_SYNC';
 export const SYNC_END_SYNC = 'SYNC_END_SYNC';
 export const SYNC_SET_LAST_SYNC = 'SYNC_SET_LAST_SYNC';
 export const SYNC_RESET_ALL = 'SYNC_RESET_ALL';
--- a/client/src/reducers/authReducer.js	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/src/reducers/authReducer.js	Sun Jul 30 01:02:09 2017 +0200
@@ -103,7 +103,7 @@
 export const groups = (state = Immutable.List([]), action) => {
   switch (action.type) {
     case types.GROUP_LOAD_SUCCESS:
-      return Immutable.List(action.groups);
+      return Immutable.List(action.groups.results);
     case types.GROUP_CREATE_SUCCESS:
       return state.push(action.group);
     default:
--- a/client/src/reducers/syncReducer.js	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/src/reducers/syncReducer.js	Sun Jul 30 01:02:09 2017 +0200
@@ -13,7 +13,7 @@
 
 export const isSynchronizing = (state = false, action) => {
   switch (action.type) {
-    case types.SYNC_DO_SYNC:
+    case types.SYNC_START_SYNC:
       return true;
     case types.SYNC_END_SYNC:
       return false;
--- a/client/src/sagas/networkSaga.js	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/src/sagas/networkSaga.js	Sun Jul 30 01:02:09 2017 +0200
@@ -4,7 +4,7 @@
 import * as persistConstants  from 'redux-persist/constants';
 import jwt_decode from 'jwt-decode';
 import moment from 'moment';
-import { delay } from './utils';
+import { delay } from 'redux-saga';
 import { setOnlineStatus } from '../actions/networkActions';
 import { getToken } from './selectors';
 
@@ -94,11 +94,11 @@
   yield take(persistConstants.REHYDRATE);
 
   while (true) {
-    yield race([
-      all([call(pollData, context), call(callDelay, context)]),
-      take(types.AUTH_LOGOUT),
-      take(types.AUTH_LOGIN_SUCCESS)
-    ]);
+    yield race({
+      poll: all([call(pollData, context), call(callDelay, context)]),
+      logout: take(types.AUTH_LOGOUT),
+      login: take(types.AUTH_LOGIN_SUCCESS)
+    });
   }
 }
 
--- a/client/src/sagas/selectors.js	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/src/sagas/selectors.js	Sun Jul 30 01:02:09 2017 +0200
@@ -4,6 +4,8 @@
 
 export const getLastSync = state => state.getIn(['authStatus', 'lastSync']) || 0
 
+export const getOnline = state => state.getIn(["status", 'online'])
+
 export const getToken = state => state.getIn(['authStatus','token'])
 
 const getSessionMapSelector = actionVal => state =>
--- a/client/src/sagas/syncSaga.js	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/src/sagas/syncSaga.js	Sun Jul 30 01:02:09 2017 +0200
@@ -1,19 +1,25 @@
-import { put, take, all, select } from 'redux-saga/effects'
+import { put, take, all, select, spawn, race, call} from 'redux-saga/effects'
+import { delay } from 'redux-saga';
 import * as types from '../constants/actionTypes';
-import { getLastSync } from './selectors';
+import { getLastSync, getOnline } from './selectors';
 import moment from 'moment';
-import { endSynchronize, updateLastSync } from '../actions/syncActions';
+import { startSynchronize, endSynchronize, updateLastSync } from '../actions/syncActions';
+import { forceSync } from '../actions/networkActions';
 import SessionSynchronizer from './SessionSyncronizer';
 import NoteSynchronizer from './NoteSyncronizer';
+import config from '../config';
 
+function* doSync(context) {
 
-function* watchSync(context) {
-  while (true) {
-    yield take(types.SYNC_DO_SYNC);
+    const online = yield select(getOnline);
+    if(!online) {
+      yield put(endSynchronize());
+      return;
+    }
+
     const lastSync = yield select(getLastSync);
-
+    yield put(startSynchronize());
 
-    //const sessions = yield context.client.get('/api/notes/sessions/', {modified_since: lastSync});
     const nextLastSync = moment().unix()
 
     // TODO: manage errors
@@ -30,12 +36,39 @@
     } finally {
       yield put(endSynchronize());
     }
+
+}
+
+function* watchDoSync(context) {
+  while (true) {
+    yield take(types.SYNC_DO_SYNC);
+    yield spawn(doSync, context);
+    yield take(types.SYNC_END_SYNC);
+  }
+}
+
+function* delayPutDoSync() {
+  yield put(forceSync());
+  yield call(delay, config.syncInterval);
+}
+
+function* loopDoSync() {
+  while(true) {
+    const online = yield select(getOnline);
+    if(!online) {
+      yield take(types.STATUS_ONLINE);
+    }
+    yield race({
+      delaySync: call(delayPutDoSync),
+      offline: take(types.STATUS_OFFLINE)
+    });
   }
 }
 
 //--- The root saga
 export default function* rootSaga(context) {
   yield all([
-    watchSync(context)
+    watchDoSync(context),
+    loopDoSync()
   ]);
 }
--- a/client/src/sagas/utils.js	Fri Jul 28 19:40:35 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-// Utility function to delay effects
-export const delay = function(millis) {
-    const promise = new Promise(resolve => {
-        setTimeout(() => resolve(true), millis)
-    });
-    return promise;
-}
--- a/client/yarn.lock	Fri Jul 28 19:40:35 2017 +0200
+++ b/client/yarn.lock	Sun Jul 30 01:02:09 2017 +0200
@@ -5549,9 +5549,9 @@
     lodash "^4.17.4"
     lodash-es "^4.17.4"
 
-redux-saga@^0.15.3:
-  version "0.15.3"
-  resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.15.3.tgz#be2b86b4ad46bf0d84fcfcb0ca96cfc33db91acb"
+redux-saga@^0.15.6:
+  version "0.15.6"
+  resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.15.6.tgz#8638dc522de6c6c0a496fe8b2b5466287ac2dc4d"
 
 redux@^3.6.0:
   version "3.6.0"
--- a/src/notes/api/views/auth.py	Fri Jul 28 19:40:35 2017 +0200
+++ b/src/notes/api/views/auth.py	Sun Jul 30 01:02:09 2017 +0200
@@ -15,7 +15,7 @@
     lookup_field = 'name'
 
     def get_queryset(self):
-        return Group.objects.all()
+        return Group.objects.all().order_by('name')
 
     serializers = {
         'create': WriteGroupSerializer,