luci-base: luci.js: introduce generic LuCI.Poll
authorJo-Philipp Wich <jo@mein.io>
Fri, 5 Apr 2019 05:55:54 +0000 (07:55 +0200)
committerJo-Philipp Wich <jo@mein.io>
Sun, 7 Jul 2019 13:36:24 +0000 (15:36 +0200)
Introduce a new LuCI.Poll class which is able to repeat any
promise based function and not strictly tied to HTTP request
semantics.

Also rework LuCI.Request.Poll and XHR.Poll as well as
LuCI.start(), LuCI.stop(), LuCI.halt() etc. to redirect to
the new api.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
modules/luci-base/htdocs/luci-static/resources/luci.js

index 19f4a54c76d641ead17bc5d322668ec97481f025..fbbb230544d1b40dcbdfc0faa0c5483f95d1d778 100644 (file)
                        return (this.interceptors.length < oldlen);
                },
 
-               poll: Class.singleton({
-                       __name__: 'LuCI.Request.Poll',
-
-                       queue: [],
-
+               poll: {
                        add: function(interval, url, options, callback) {
                                if (isNaN(interval) || interval <= 0)
                                        throw new TypeError('Invalid poll interval');
 
-                               var e = {
-                                       interval: interval,
-                                       url: url,
-                                       options: options,
-                                       callback: callback
-                               };
+                               var ival = interval >>> 0,
+                                   opts = Object.assign({}, options, { timeout: ival * 1000 - 5 });
+
+                               return Poll.add(function() {
+                                       return Request.request(url, options).then(function(res) {
+                                               if (!Poll.active())
+                                                       return;
 
-                               this.queue.push(e);
-                               return e;
+                                               try {
+                                                       callback(res, res.json(), res.duration);
+                                               }
+                                               catch (err) {
+                                                       callback(res, null, res.duration);
+                                               }
+                                       });
+                               }, ival);
                        },
 
-                       remove: function(entry) {
-                               var oldlen = this.queue.length, i = oldlen;
+                       remove: function(entry) { return Poll.remove(entry) },
+                       start: function() { return Poll.start() },
+                       stop: function() { return Poll.stop() },
+                       active: function() { return Poll.active() }
+               }
+       });
 
-                               while (i--)
-                                       if (this.queue[i] === entry) {
-                                               delete this.queue[i].running;
-                                               this.queue.splice(i, 1);
-                                       }
+       var Poll = Class.singleton({
+               __name__: 'LuCI.Poll',
 
-                               if (!this.queue.length)
-                                       this.stop();
+               queue: [],
 
-                               return (this.queue.length < oldlen);
-                       },
+               add: function(fn, interval) {
+                       if (interval == null || interval <= 0)
+                               interval = window.L ? window.L.env.pollinterval : null;
+
+                       if (isNaN(interval) || typeof(fn) != 'function')
+                               throw new TypeError('Invalid argument to LuCI.Poll.add()');
 
-                       start: function() {
-                               if (!this.queue.length || this.active())
+                       for (var i = 0; i < this.queue.length; i++)
+                               if (this.queue[i].fn === fn)
                                        return false;
 
+                       var e = {
+                               r: true,
+                               i: interval >>> 0,
+                               fn: fn
+                       };
+
+                       this.queue.push(e);
+
+                       if (this.tick != null && !this.active())
+                               this.start();
+
+                       return true;
+               },
+
+               remove: function(entry) {
+                       if (typeof(fn) != 'function')
+                               throw new TypeError('Invalid argument to LuCI.Poll.remove()');
+
+                       var len = this.queue.length;
+
+                       for (var i = len; i > 0; i--)
+                               if (this.queue[i-1].fn === fn)
+                                       this.queue.splice(i-1, 1);
+
+                       if (!this.queue.length && this.stop())
                                this.tick = 0;
+
+                       return (this.queue.length != len);
+               },
+
+               start: function() {
+                       if (this.active())
+                               return false;
+
+                       this.tick = 0;
+
+                       if (this.queue.length) {
                                this.timer = window.setInterval(this.step, 1000);
                                this.step();
                                document.dispatchEvent(new CustomEvent('poll-start'));
-                               return true;
-                       },
+                       }
 
-                       stop: function() {
-                               if (!this.active())
-                                       return false;
+                       return true;
+               },
 
-                               document.dispatchEvent(new CustomEvent('poll-stop'));
-                               window.clearInterval(this.timer);
-                               delete this.timer;
-                               delete this.tick;
-                               return true;
-                       },
+               stop: function() {
+                       if (!this.active())
+                               return false;
 
-                       step: function() {
-                               Request.poll.queue.forEach(function(e) {
-                                       if ((Request.poll.tick % e.interval) != 0)
-                                               return;
+                       document.dispatchEvent(new CustomEvent('poll-stop'));
+                       window.clearInterval(this.timer);
+                       delete this.timer;
+                       delete this.tick;
+                       return true;
+               },
 
-                                       if (e.running)
-                                               return;
+               step: function() {
+                       for (var i = 0, e = null; (e = Poll.queue[i]) != null; i++) {
+                               if ((Poll.tick % e.i) != 0)
+                                       continue;
 
-                                       var opts = Object.assign({}, e.options,
-                                               { timeout: e.interval * 1000 - 5 });
+                               if (!e.r)
+                                       continue;
 
-                                       e.running = true;
-                                       Request.request(e.url, opts)
-                                               .then(function(res) {
-                                                       if (!e.running || !Request.poll.active())
-                                                               return;
+                               e.r = false;
 
-                                                       try {
-                                                               e.callback(res, res.json(), res.duration);
-                                                       }
-                                                       catch (err) {
-                                                               e.callback(res, null, res.duration);
-                                                       }
-                                               })
-                                               .finally(function() { delete e.running });
-                               });
+                               Promise.resolve(e.fn()).finally((function() { this.r = true }).bind(e));
+                       }
 
-                               Request.poll.tick = (Request.poll.tick + 1) % Math.pow(2, 32);
-                       },
+                       Poll.tick = (Poll.tick + 1) % Math.pow(2, 32);
+               },
 
-                       active: function() {
-                               return (this.timer != null);
-                       }
-               })
+               active: function() {
+                       return (this.timer != null);
+               }
        });
 
 
                                if (res.status != 403 || res.headers.get('X-LuCI-Login-Required') != 'yes')
                                        return;
 
-                               Request.poll.stop();
+                               Poll.stop();
 
                                L.ui.showModal(_('Session expired'), [
                                        E('div', { class: 'alert-message warning' },
                        });
 
                        originalCBIInit();
-                       Request.poll.start();
+
+                       Poll.start();
 
                        document.dispatchEvent(new CustomEvent('luci-loaded'));
                },
                                        });
                },
 
-               stop: function(entry) { return Request.poll.remove(entry) },
-               halt: function() { return Request.poll.stop() },
-               run: function() { return Request.poll.start() },
+               stop: function(entry) { return Poll.remove(entry) },
+               halt: function() { return Poll.stop() },
+               run: function() { return Poll.start() },
 
                /* DOM manipulation */
                dom: Class.singleton({
                        }
                }),
 
+               Poll: Poll,
                Class: Class,
                Request: Request,