mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 01:12:52 +08:00
146 lines
5.7 KiB
JavaScript
146 lines
5.7 KiB
JavaScript
'use strict';
|
|
'require poll';
|
|
|
|
// gitbackup -- shared bounded live-log poller (ticket 23, spec "Не
|
|
// изобретать четвёртый механизм"). Before this file existed, overview.js
|
|
// (run/test), history.js (restore) and settings.js (test) each carried
|
|
// their own, separately-typed copy of the exact same state machine: fetch
|
|
// a log tail, diff it against the line count from the previous tick, feed
|
|
// the new lines to a caller-supplied terminal-line regex, and give up
|
|
// after 60s of no new output or a hard 5-minute ceiling regardless --
|
|
// interfaces.md's own record of tickets 11-14 already names this bound
|
|
// three times over ("терминальная строка лога, простой в 60 секунд,
|
|
// жёсткий потолок в 5 минут"), copied rather than shared. Ticket 23's own
|
|
// acceptance criterion ("механизм поллинга один на все три view, а не
|
|
// четыре разных") asks for exactly one implementation of that shape; this
|
|
// is it.
|
|
//
|
|
// LuCI's own require() resolves a module ONCE per page and hands back the
|
|
// same already-instantiated singleton to every caller (luci.js:
|
|
// `compileClass`'s `new _class()`, cached in `classes[name]`) -- so every
|
|
// one of the three views above sharing this same instance is safe only
|
|
// because each of them is its own full page load (classic multi-page LuCI
|
|
// tears down the whole JS context on tab switch) AND never runs two such
|
|
// pollers from itself at once (the button(s) a poller was started from
|
|
// stay disabled for its entire run). start() below resets every piece of
|
|
// this instance's state, so a second, later operation on the very same
|
|
// page (e.g. overview.js's "Backup now" followed later by its own "Test
|
|
// connection") reuses the same instance cleanly once the first one has
|
|
// actually stopped.
|
|
return L.Class.extend({
|
|
// start(opts) -- begins polling opts.fetch() every opts.interval
|
|
// seconds (default 2, matching every prior copy of this poller).
|
|
// opts.fetch() must resolve to the log's current text tail (a
|
|
// rejected fetch is treated as "no output this tick", same as an
|
|
// empty string, and repeated failures alone can still trip the idle
|
|
// bound below). Returns a promise that resolves once the FIRST fetch
|
|
// (establishing the starting line count) has completed and polling
|
|
// has actually started -- callers await this before firing the
|
|
// backgrounded CLI call itself, exactly like every prior copy of this
|
|
// poller did, so a very fast operation's own first output line is
|
|
// never missed.
|
|
//
|
|
// opts.terminalRe (RegExp, required) -- tested against each newly
|
|
// arrived line; a match stops the poller and fires opts.onFinish.
|
|
// opts.onProgress(addedText), if given, fires on every tick that saw
|
|
// new output at all, with only the lines new since the previous tick
|
|
// (already newline-joined, blank lines dropped) -- never the whole
|
|
// accumulated text, so a caller appending it to a growing <pre> never
|
|
// duplicates a line.
|
|
// opts.onFinish(matchedLine), if given, fires exactly once, after the
|
|
// poller has already stopped itself, with the one line that matched
|
|
// opts.terminalRe.
|
|
// opts.onTimeout(), if given, fires exactly once, after the poller has
|
|
// already stopped itself, when opts.idleLimit ticks (default 30 == 60s
|
|
// at the default 2s interval) passed with no new output at all, when
|
|
// opts.tickLimit ticks (default 150 == 5 minutes) is reached
|
|
// regardless, or when opts.fetch() itself keeps rejecting for that
|
|
// same idle span -- three different reasons this project's own
|
|
// interfaces.md has always treated as one bound ("простой... либо
|
|
// потолок"), not three outcomes a caller has to tell apart.
|
|
start: function(opts) {
|
|
var self = this;
|
|
|
|
self._opts = opts;
|
|
self._idle = 0;
|
|
self._ticks = 0;
|
|
self._lines = 0;
|
|
|
|
return opts.fetch().then(function(text) {
|
|
text = text || '';
|
|
self._lines = text ? text.split('\n').length : 0;
|
|
|
|
if (!self._bound)
|
|
self._bound = L.bind(self._tick, self);
|
|
|
|
poll.remove(self._bound);
|
|
poll.add(self._bound, opts.interval || 2);
|
|
});
|
|
},
|
|
|
|
// stop() -- idempotent; safe to call whether or not a poll is
|
|
// currently running (every call site here does, on the "could not
|
|
// even start the operation" path, whether or not start() got as far
|
|
// as poll.add).
|
|
stop: function() {
|
|
if (this._bound)
|
|
poll.remove(this._bound);
|
|
},
|
|
|
|
_tick: function() {
|
|
var self = this;
|
|
var opts = self._opts;
|
|
var idleLimit = opts.idleLimit || 30;
|
|
var tickLimit = opts.tickLimit || 150;
|
|
|
|
self._ticks++;
|
|
|
|
return opts.fetch().then(function(text) {
|
|
var lines, newLines, add, finished, matchedLine, i;
|
|
|
|
text = text || '';
|
|
lines = text.split('\n');
|
|
newLines = (lines.length >= self._lines) ? lines.slice(self._lines) : lines;
|
|
add = newLines.filter(function(l) { return l; }).join('\n');
|
|
finished = false;
|
|
matchedLine = null;
|
|
|
|
self._lines = lines.length;
|
|
|
|
if (add) {
|
|
self._idle = 0;
|
|
if (opts.onProgress)
|
|
opts.onProgress(add);
|
|
for (i = 0; i < newLines.length; i++) {
|
|
if (opts.terminalRe.test(newLines[i])) {
|
|
finished = true;
|
|
matchedLine = newLines[i];
|
|
}
|
|
}
|
|
} else {
|
|
self._idle++;
|
|
}
|
|
|
|
if (finished) {
|
|
self.stop();
|
|
if (opts.onFinish)
|
|
opts.onFinish(matchedLine);
|
|
} else if (self._idle >= idleLimit || self._ticks >= tickLimit) {
|
|
self.stop();
|
|
if (opts.onTimeout)
|
|
opts.onTimeout();
|
|
}
|
|
}, function() {
|
|
// A single failed poll must not spin forever either -- same
|
|
// convention every prior copy of this poller used (count it
|
|
// as a big idle jump rather than a distinct error path).
|
|
self._idle += idleLimit;
|
|
if (self._idle >= idleLimit) {
|
|
self.stop();
|
|
if (opts.onTimeout)
|
|
opts.onTimeout();
|
|
}
|
|
});
|
|
}
|
|
});
|