瀏覽代碼

feat: add sync state on options page

Gerald 9 年之前
父節點
當前提交
3c8dd91a08

+ 1 - 1
src/background/main.js

@@ -16,7 +16,7 @@ var commands = {
   },
   GetData: function (data, src) {
     return vmdb.getData().then(function (data) {
-      data.sync = sync.status();
+      data.sync = sync.states();
       return data;
     });
   },

+ 21 - 5
src/background/sync/dropbox.js

@@ -5,6 +5,7 @@ setTimeout(function () {
   };
   var events = getEventEmitter();
   var dropbox = sync.service('dropbox', {
+    displayName: 'Dropbox',
     init: init,
     authenticate: authenticate,
     on: events.on,
@@ -21,7 +22,7 @@ setTimeout(function () {
 
   function init() {
     dropbox.inst = null;
-    dropbox.status.set('initializing');
+    dropbox.authState.set('initializing');
     var token = dropbox.config.get('token');
     if (token) {
       dropbox.inst = new Dropbox(token);
@@ -30,7 +31,7 @@ setTimeout(function () {
         url: 'https://api.dropboxapi.com/2/users/get_current_account',
       })
       .then(function (text) {
-        dropbox.status.set('authorized');
+        dropbox.authState.set('authorized');
         events.fire('init');
       }, function (res) {
         if (res.status > 300) {
@@ -38,12 +39,15 @@ setTimeout(function () {
         }
         if (res.status === 401) {
           dropbox.config.clear();
-          dropbox.status.set('unauthorized');
+          dropbox.authState.set('unauthorized');
+        } else {
+          dropbox.authState.set('error');
         }
+        dropbox.syncState.set('error');
         dropbox.config.setOption('enabled', false);
       });
     } else {
-      dropbox.status.set('unauthorized');
+      dropbox.authState.set('unauthorized');
     }
   }
   function authenticate() {
@@ -104,6 +108,7 @@ setTimeout(function () {
           var v = headers[k];
           xhr.setRequestHeader(k, v);
         }
+        xhr.timeout = 10 * 1000;
         xhr.onload = function () {
           if (this.status > 300) reject(this);
           else resolve(this.responseText);
@@ -112,9 +117,20 @@ setTimeout(function () {
           if (this.status === 503) {
             // TODO Too Many Requests
           }
-          reject(this);
+          requestError();
+        };
+        xhr.ontimeout = function () {
+          requestError('Timed out.');
         };
         xhr.send(options.body);
+
+        function requestError(reason) {
+          reject({
+            url: xhr.url,
+            status: xhr.status,
+            reason: reason || xhr.responseText,
+          });
+        }
       });
     });
   };

+ 53 - 29
src/background/sync/index.js

@@ -58,28 +58,27 @@ var sync = function () {
     _.options.set(this.prefix, this.data);
   };
 
-  function serviceStatus() {
-    var validStatused = [
-      'idle',
-      'initializing',
-      'authorized',
-      'unauthorized',
-    ];
-    var status = 'idle';
+  function serviceState(validStates, initialState, onChange) {
+    var state = initialState || validStates[0];
     return {
-      get: function () {return status;},
-      set: function (_status) {
-        if (~validStatused.indexOf(_status)) {
-          status = _status;
-          _.messenger.post({
-            cmd: 'sync',
-            data: getStatuses(),
-          });
+      get: function () {return state;},
+      set: function (_state) {
+        if (~validStates.indexOf(_state)) {
+          state = _state;
+          onChange && onChange();
+        } else {
+          console.warn('Invalid state:', _state);
         }
-        return status;
+        return state;
       },
     };
   }
+  function onStateChange() {
+    _.messenger.post({
+      cmd: 'sync',
+      data: getStates(),
+    });
+  }
   function service(name, methods) {
     var service;
     if (methods) {
@@ -87,7 +86,18 @@ var sync = function () {
       service = _.assign({}, methods, {
         name: name,
         config: new ServiceConfig(name),
-        status: serviceStatus(),
+        authState: serviceState([
+          'idle',
+          'initializing',
+          'authorized',
+          'unauthorized',
+          'error',
+        ], null, onStateChange),
+        syncState: serviceState([
+          'idle',
+          'syncing',
+          'error',
+        ], null, onStateChange),
       });
       setTimeout(function () {
         services.push(service);
@@ -103,14 +113,16 @@ var sync = function () {
     }
     return service;
   }
-  function getStatuses() {
-    return services.reduce(function (res, service) {
-      res[service.name] = {
-        status: service.status.get(),
+  function getStates() {
+    return services.map(function (service) {
+      return {
+        name: service.name,
+        displayName: service.displayName,
+        authState: service.authState.get(),
+        syncState: service.syncState.get(),
         timestamp: service.config.get('meta', {}).timestamp,
       };
-      return res;
-    }, {});
+    });
   }
   function sync(service) {
     if (service) {
@@ -142,7 +154,7 @@ var sync = function () {
     var service = queue.shift();
     if (!service) return stopSync();
     syncing = true;
-    syncOne(service).then(process, stopSync);
+    syncOne(service).then(process);
   }
   function initService(service) {
     service.on('init', function () {
@@ -166,6 +178,7 @@ var sync = function () {
   }
   function syncOne(service) {
     if (!service.inst) return;
+    service.syncState.set('syncing');
     return Promise.all([
       service.inst.list(),
       service.inst.get(METAFILE)
@@ -293,10 +306,21 @@ var sync = function () {
       }));
       return Promise.all(promises.map(function (promise) {
         // ignore errors to ensure all promises are fulfilled
-        return promise.catch(function (err) {
-          console.log(err);
+        return promise.then(function () {}, function (err) {
+          return err || true;
         });
-      }));
+      }))
+      .then(function (errors) {
+        errors = errors.filter(function (err) {return err;});
+        if (errors.length) throw errors;
+      });
+    })
+    .then(function () {
+      service.syncState.set('idle');
+    }, function (err) {
+      service.syncState.set('error');
+      console.log('Failed syncing:', service.name);
+      console.log(err);
     });
   }
 
@@ -304,7 +328,7 @@ var sync = function () {
     init: init,
     sync: sync,
     service: service,
-    status: getStatuses,
+    states: getStates,
     utils: {
       getFilename: getFilename,
       isScriptFile: isScriptFile,

+ 2 - 2
src/options/app.js

@@ -27,12 +27,12 @@ BaseView.prototype.initI18n.call(window);
 var scriptList, syncData;
 function initMain() {
   scriptList = new ScriptList;
-  syncData = new Backbone.Model;
+  syncData = new Backbone.Collection;
   var port = chrome.runtime.connect({name: 'Options'});
   port.onMessage.addListener(function (res) {
     switch (res.cmd) {
       case 'sync':
-        syncData.set(res.data);
+        syncData.reset(res.data);
         break;
       case 'add':
         res.data.message = '';

+ 1 - 1
src/options/model.js

@@ -37,7 +37,7 @@ var ScriptList = Backbone.Collection.extend({
       _this.loading = false;
       _.assign(_this.cache, data.cache);
       _this.reset(data.scripts);
-      syncData.set(data.sync);
+      syncData.reset(data.sync);
     });
   },
 });

+ 17 - 0
src/options/templates/sync-service.html

@@ -0,0 +1,17 @@
+<label>
+  <input type=checkbox data-check="<%= it.name + 'Enabled' %>" data-sync="<%= it.name %>" <%=
+  it.enabled ? 'checked' : ''
+  %> <%=
+  it.authorized ? '' : 'disabled'
+  %>>
+  <span><%= _.i18n('labelSyncTo', it.displayName || it.name) %></span>
+</label>
+<button data-auth="<%= it.name %>" <%= it.unauthorized ? '' : 'disabled' %>><%=
+  it.authorized ? _.i18n('buttonAuthorized') : _.i18n('buttonAuthorize')
+%></button>
+<span><%=
+  it.initializing ? _.i18n('msgSyncInit') :
+  it.error ? _.i18n('msgSyncError') :
+  it.syncing ? _.i18n('msgSyncing') :
+  it.lastSync ? _.i18n('lastSync', it.lastSync) : ''
+%></span>

+ 1 - 15
src/options/templates/tab-settings.html

@@ -38,20 +38,6 @@
   </fieldset>
   <fieldset class=title>
     <legend data-i18n=labelSync></legend>
-    <div>
-      <label>
-        <input id=cbSyncDropbox type=checkbox data-check=dropboxEnabled data-sync=dropbox <%=
-        it.dropboxEnabled ? 'checked' : ''
-        %> <%=
-        it.sync.dropbox.authorized ? '' : 'disabled'
-        %>>
-        <span data-i18n=labelSyncDropbox></span>
-      </label>
-      <button data-auth=dropbox <%=
-        it.sync.dropbox.unauthorized ? '' : 'disabled'
-      %>><%=
-        it.sync.dropbox.authorized ? _.i18n('buttonAuthorized') : _.i18n('buttonAuthorize')
-      %></button>
-    </div>
+    <div class="sync-services"></div>
   </fieldset>
 </div>

+ 13 - 0
src/options/views/sync-service.js

@@ -0,0 +1,13 @@
+var SyncServiceView = BaseView.extend({
+  templateUrl: '/options/templates/sync-service.html',
+  _render: function () {
+    var it = this.model.toJSON();
+    it.initializing = it.authState === 'initializing';
+    it.authorized = it.authState === 'authorized';
+    it.unauthorized = it.authState === 'unauthorized';
+    it.error = it.syncState === 'error';
+    it.syncing = it.syncState === 'syncing';
+    it.lastSync = it.timestamp && new Date(it.timestamp).toLocaleString();
+    this.$el.html(this.templateFn(it));
+  },
+});

+ 6 - 8
src/options/views/tab-settings.js

@@ -31,7 +31,6 @@ var ExportList = BaseView.extend({
 
 var SettingsTab = BaseView.extend({
   el: '#tab',
-  name: 'settings',
   events: {
     'change [data-check]': 'updateCheckbox',
     'change #sInjectMode': 'updateInjectMode',
@@ -46,17 +45,16 @@ var SettingsTab = BaseView.extend({
   templateUrl: '/options/templates/tab-settings.html',
   initialize: function () {
     BaseView.prototype.initialize.call(this);
-    this.listenTo(syncData, 'change', this.render);
+    this.listenTo(syncData, 'reset', this.render);
   },
   _render: function () {
     var options = _.options.getAll();
-    var sync = options.sync = syncData.toJSON();
-    ['dropbox'].forEach(function (name) {
-      var service = sync[name] = sync[name] || {};
-      service.authorized = service.status === 'authorized';
-      service.unauthorized = service.status === 'unauthorized';
-    });
     this.$el.html(this.templateFn(options));
+    var syncServices = this.$('.sync-services');
+    syncData.each(function (service) {
+      var serviceView = new SyncServiceView({model: service});
+      syncServices.append(serviceView.$el);
+    });
     this.$('#sInjectMode').val(options.injectMode);
     this.updateInjectHint();
     this.exportList = new ExportList;