luci-base: luci.js: introduce generic LuCI.Poll

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>
This commit is contained in:
Jo-Philipp Wich 2019-04-05 07:55:54 +02:00
parent 8a8e8e0aac
commit eee323cb66

View file

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