serialize requests in BackupStorage

This commit is contained in:
Alex Pankratov
2021-04-25 14:37:57 +02:00
parent 9923868e56
commit 850a16acfd

View File

@@ -1696,10 +1696,13 @@
this.listWidth = null; // list-width this.listWidth = null; // list-width
this.theme = null; // default or 'dark' this.theme = null; // default or 'dark'
this.backups = [ ]; // [ { type, id, enabled, conf } ];
this.nextBackupId = 1;
this.board = null; // active board this.board = null; // active board
this.backups =
{
agents : [ ], // [ { type, id, enabled, conf } ];
nextId : 1
};
} }
function BoardMeta() function BoardMeta()
@@ -1708,7 +1711,7 @@
this.current = 1; // revision this.current = 1; // revision
this.ui_spot = 0; // 0 = not set this.ui_spot = 0; // 0 = not set
this.history = [ ]; // revision IDs this.history = [ ]; // revision IDs
this.backups = [ ]; // backup agents IDs with which this board is stored this.backups = [ ]; // agents that this board is backed up with
} }
class Storage class Storage
@@ -1720,9 +1723,11 @@
this.conf = new AppConfig(); this.conf = new AppConfig();
this.boardIndex = new Map(); this.boardIndex = new Map();
this.backups = []; // BackupStorage this.backups =
this.backupStatus = ''; // '', 'ok', 'busy', 'failed' {
this.backupCb = null; status : '', // '', 'ok', 'busy', 'failed'
agents : [ ], // BackupStorage instances
};
} }
open() open()
@@ -1740,9 +1745,12 @@
return this.conf; return this.conf;
} }
setVerLast(ver) setVerLast()
{ {
this.conf.verLast = ver || NB.codeVersion; if (this.conf.verLast == NB.codeVersion)
return true;
this.conf.verLast = NB.codeVersion;
return this.saveConfig(); return this.saveConfig();
} }
@@ -1754,6 +1762,8 @@
setActiveBoard(board_id) setActiveBoard(board_id)
{ {
console.log('setActiveBoard [' + this.conf.board + '] -> [' + board_id + ']');
var meta = board_id ? this.boardIndex.get(board_id) : true; var meta = board_id ? this.boardIndex.get(board_id) : true;
if (! meta) if (! meta)
@@ -1944,7 +1954,7 @@
this.delItem('board.' + board_id + '.meta'); this.delItem('board.' + board_id + '.meta');
this.boardIndex.delete(board_id); this.boardIndex.delete(board_id);
this.backups.forEach(function(store){ this.backups.agents.forEach(function(store){
store.nukeBoard(board_id); store.nukeBoard(board_id);
}); });
@@ -2042,22 +2052,26 @@
conf.verSeen = 20200220; // 20200429; conf.verSeen = 20200220; // 20200429;
} }
if (conf.backups.length != 2 || var agents = conf.backups.agents;
conf.backups[0].type != simp || conf.backups[0].conf.base != 'http://127.0.0.1:10001' ||
conf.backups[1].type != simp)
{
console.log('Unexpected backup config, will re-initialize.', conf.backups);
conf.backups.push({ if (agents.length != 2 ||
agents[0].type != simp || agents[0].conf.base != 'http://127.0.0.1:10001' ||
agents[1].type != simp)
{
console.log('Unexpected backup config, will re-initialize.', agents);
conf.backups.agents = [];
conf.backups.agents.push({
type: simp, type: simp,
id: simp + '-' + (conf.nextBackupId++), id: simp + '-' + (conf.backups.nextId++),
enabled: false, enabled: false,
conf: { base: 'http://127.0.0.1:10001', auth: '' } conf: { base: 'http://127.0.0.1:10001', auth: '' }
}) })
conf.backups.push({ conf.backups.agents.push({
type: simp, type: simp,
id: simp + '-' + (conf.nextBackupId++), id: simp + '-' + (conf.backups.nextId++),
enabled: false, enabled: false,
conf: { base: '', auth: '' } conf: { base: '', auth: '' }
}) })
@@ -2069,20 +2083,18 @@
/* /*
* backups * backups
*/ */
initBackups(backupStatusCb) initBackups(onBackupStatus)
{ {
var self = this; var self = this;
var pending = 0; var pending = 0;
var success = true; var success = true;
var store_id = 1; var store_id = 1;
NB.storage.backupCb = backupStatusCb; self.backups.agents = [];
this.backups = []; onBackupStatus(null);
self.setBackupStatus(''); this.conf.backups.agents.forEach(function(b){
this.conf.backups.forEach(function(b){
var T = NB.backupTypes.get(b.type); var T = NB.backupTypes.get(b.type);
if (! T) if (! T)
@@ -2094,59 +2106,40 @@
if (! b.enabled) if (! b.enabled)
return; return;
var store = new T(b.id, b.conf); var agent = new T(b.id, b.conf, onBackupStatus);
self.backups.push(store); self.backups.agents.push(agent);
console.log( `Added backup storage - type '${store.type}', id '${store.id}'` ); console.log( `Added backup agent - type '${agent.type}', id '${agent.id}'` );
self.setBackupStatus('busy'); agent.checkStatus(null); // will need just onBackupStatus() callbacks
pending++;
store.checkStatus(function(ok, xhr){
console.log( `Backup storage '${store.id}' is ${ok ? 'ready' : 'NOT ready'} ` );
store.last = { ok: ok, text: xhr.responseText, code: xhr.status };
success &= ok;
if (--pending)
return;
self.setBackupStatus(success ? 'ok' : 'failed');
});
}); });
} }
backupBoard(board_id, board, meta) backupBoard(board_id, board, meta)
{ {
var self = this; var self = this;
var pending = 0;
var success = true;
meta.backups = []; if (! this.backups.agents.length)
if (! this.backups.length)
{ {
this.setBackupStatus(''); meta.backups = [];
return; return;
} }
this.setBackupStatus('busy'); meta.backups = [];
this.backups.forEach(function(store){ console.log( `Backing up ${board_id}...` );
pending++;
store.saveBoard(board_id, board, meta, function(ok){ this.backups.agents.forEach(function(agent){
agent.saveBoard(board_id, board, meta, function(){
var what = 'Backup of ' + board_id + (board ? '' : ' (meta)'); var what = 'Backup of ' + board_id + (board ? '' : ' (meta)');
console.log( `${what} to '${store.id}' -> ${ok ? 'ok' : 'failed'}` ); console.log( `${what} to '${agent.id}' -> ${agent.status}` );
if (ok) meta.backups.push(store.id); if (agent.status == 'ready')
else success = false; meta.backups.push(agent.id);
if (--pending)
return;
self.setJson('board.' + board_id + '.meta', meta); self.setJson('board.' + board_id + '.meta', meta);
self.setBackupStatus(success ? 'ok' : 'failed');
}); });
}); });
} }
@@ -2154,34 +2147,14 @@
backupConfig() backupConfig()
{ {
var self = this; var self = this;
var pending = 0;
var success = true;
if (! this.backups.length) if (! this.backups.agents.length)
{
this.setBackupStatus('');
return; return;
}
this.setBackupStatus('busy'); this.backups.agents.forEach(function(agent){
agent.saveConfig(self.conf, function(){});
this.backups.forEach(function(store){
pending++;
store.saveConfig(self.conf, function(ok){
success &= ok;
if (--pending)
return;
self.setBackupStatus(success ? 'ok' : 'failed');
});
}); });
} }
setBackupStatus(status)
{
this.backupStatus = status;
if (this.backupCb) this.backupCb.call(this, status);
}
}; };
class Storage_Local extends Storage class Storage_Local extends Storage
@@ -2277,6 +2250,9 @@
if (! meta) if (! meta)
continue; continue;
delete meta.backingUp; // run-time var
delete meta.needsBackup; // ditto
meta = Object.assign(new BoardMeta(), meta); meta = Object.assign(new BoardMeta(), meta);
this.boardIndex.set(board_id, meta); this.boardIndex.set(board_id, meta);
} }
@@ -2382,12 +2358,17 @@
*/ */
class BackupStorage class BackupStorage
{ {
constructor(id, conf) constructor(id, conf, onStatusChange)
{ {
this.type = '?'; this.type = '?';
this.id = id; this.id = id;
this.conf = conf; this.conf = conf;
this.status = '';
this.lastOp = '';
this.lastXhr = { op: '', text: '', code: 0 };
this.onStatusChange = onStatusChange;
this.queue = [];
} }
checkStatus(cb) { return false; } checkStatus(cb) { return false; }
@@ -2398,95 +2379,144 @@
class SimpleBackup extends BackupStorage class SimpleBackup extends BackupStorage
{ {
constructor(id, conf) constructor(id, conf, onStatusChange)
{ {
super(id, null); super(id, null, onStatusChange);
this.type = 'simp'; this.type = 'simp';
this.conf = { base: '', auth: '' } this.conf = { base: '', auth: '' }
this.conf = Object.assign(this.conf, conf); this.conf = Object.assign(this.conf, conf);
} }
checkStatus(cb) checkStatus(cb)
{ {
var self = this; this.queue.push({
what : 'checkStatus',
$.ajax({ cb : cb,
url: this.conf.base + '/config', args :
type: 'put',
headers: { 'X-Access-Token': this.conf.auth },
data:
{ {
self: document.location.href, url: this.conf.base + '/config',
// conf: -- without the data -- type: 'put',
}, headers: { 'X-Access-Token': this.conf.auth },
dataType: 'json' data:
}) {
.done(function(d, s, x) { if (cb) cb.call(self, true, x, s, d); }) self: document.location.href,
.fail(function(x, s, e) { if (cb) cb.call(self, false, self.patchXhr(x), s, e); }) // conf: -- without the data --
},
dataType: 'json'
}
});
this.runQueue();
} }
saveConfig(conf, cb) saveConfig(conf, cb)
{ {
var self = this; this.queue.push({
what : 'saveConfig',
$.ajax({ cb : cb,
url: this.conf.base + '/config', args :
type: 'put',
headers: { 'X-Access-Token': this.conf.auth },
data:
{ {
self: document.location.href, url: this.conf.base + '/config',
conf: JSON.stringify(conf) type: 'put',
}, headers: { 'X-Access-Token': this.conf.auth },
dataType: 'json' data:
}) {
.done(function(d, s, x) { if (cb) cb.call(self, true, x, s, d); }) self: document.location.href,
.fail(function(x, s, e) { if (cb) cb.call(self, false, self.patchXhr(x), s, e); }) conf: JSON.stringify(conf)
},
dataType: 'json'
}
});
this.runQueue();
} }
saveBoard(id, data, meta, cb) saveBoard(id, data, meta, cb)
{ {
var self = this; this.queue.push({
what : 'saveBoard',
$.ajax({ cb : cb,
url: this.conf.base + '/board/' + id, args :
type: 'put',
headers: { 'X-Access-Token': this.conf.auth },
data:
{ {
self: document.location.href, url: this.conf.base + '/board/' + id,
data: data ? JSON.stringify(data) : null, type: 'put',
meta: meta ? JSON.stringify(meta) : null headers: { 'X-Access-Token': this.conf.auth },
}, data:
dataType: 'json' {
}) self: document.location.href,
.done(function(d, s, x) { if (cb) cb.call(self, true, x, s, d); }) data: data ? JSON.stringify(data) : null,
.fail(function(x, s, e) { if (cb) cb.call(self, false, self.patchXhr(x), s, e); }) meta: meta ? JSON.stringify(meta) : null
},
dataType: 'json'
}
});
this.runQueue();
} }
nukeBoard(id, cb) nukeBoard(id, cb)
{ {
var self = this; this.queue.push({
what : 'saveBoard',
cb : cb,
args :
{
url: this.conf.base + '/board/' + id,
type: 'delete',
headers: { 'X-Access-Token': this.conf.auth },
}
});
$.ajax({ this.runQueue();
url: this.conf.base + '/board/' + id,
type: 'delete',
headers: { 'X-Access-Token': this.conf.auth },
})
.done(function(d, s, x) { if (cb) cb.call(self, true, x, s, d); })
.fail(function(x, s, e) { if (cb) cb.call(self, false, self.patchXhr(x), s, e); })
} }
/* /*
* private * private
*/ */
patchXhr(x) runQueue()
{ {
if (! x.responseText) var self = this;
x.responseText = x.status ? `Request failed with ${x.status}` : 'Offline or CORS-blocked';
return x; if (! this.queue.length)
return;
if (this.status == 'busy')
return;
var req = this.queue.shift();
this.setStatus('busy', req.what);
$.ajax(req.args)
.done(function(d, s, x) { self.onRequestDone(req, true, x); })
.fail(function(x, s, e) { self.onRequestDone(req, false, x); })
}
onRequestDone(req, ok, xhr)
{
console.log( `Backup agent '${this.id}', ${this.lastOp}() -> ${ok ? 'ok' : 'failed'}` );
var code = xhr.status;
var text = xhr.responseText || (code ? `Response code ${code}` : 'Offline or CORS-blocked');
this.lastXhr = { text: text, code: code };
this.setStatus(ok ? 'ready' : 'error', this.lastOp);
if (req.cb) req.cb.call(this);
this.runQueue();
}
setStatus(status, op)
{
if (status == 'busy' && this.status == 'busy')
throw `Backup agent ${this.id} is already busy!`;
this.status = status;
this.lastOp = op;
this.onStatusChange(this);
} }
} }
@@ -3093,6 +3123,9 @@
function closeBoard(quick) function closeBoard(quick)
{ {
if (! NB.board)
return;
var $board = $('.wrap .board'); var $board = $('.wrap .board');
if (quick) if (quick)
@@ -3429,20 +3462,20 @@
/* /*
* *
*/ */
function findBackup(which) function findBackupAgent(which)
{ {
var s = null; var a = null;
NB.storage.backups.forEach(function(store){ NB.storage.backups.agents.forEach(function(agent){
if (store.type == which.type && if (agent.type == which.type &&
store.conf.auth == which.conf.auth && agent.conf.auth == which.conf.auth &&
store.conf.base == which.conf.base) agent.conf.base == which.conf.base)
{ {
s = store; a = agent;
} }
}); });
return s; return a;
} }
function setBackupConfigUi($div, backupConf) function setBackupConfigUi($div, backupConf)
@@ -3454,7 +3487,7 @@
} }
var $status = $div.find('.status'); var $status = $div.find('.status');
var b = findBackup(backupConf); var b = findBackupAgent(backupConf);
var text = 'OK'; var text = 'OK';
if (b && b.last && ! b.last.ok) if (b && b.last && ! b.last.ok)
@@ -3470,15 +3503,16 @@
function getBackupConfigUi() function getBackupConfigUi()
{ {
var conf = NB.storage.getConfig(); var conf = NB.storage.getConfig();
var loc = conf.backups[0]; var loc = conf.backups.agents[0];
var rem = conf.backups[1]; var rem = conf.backups.agents[1];
var $div = $('.overlay .backup-conf'); var $div = $('.overlay .backup-conf');
var $loc = $div.find('.loc'); var $loc = $div.find('.loc');
var $rem = $div.find('.rem'); var $rem = $div.find('.rem');
var ret = { var ret =
loc: jsonClone(loc), {
loc: jsonClone(loc),
rem: jsonClone(rem) rem: jsonClone(rem)
}; };
@@ -3522,16 +3556,17 @@
$div.delay(850).queue(function(){ $div.delay(850).queue(function(){
var T = NB.backupTypes.get(backupConf.type); var T = NB.backupTypes.get(backupConf.type);
var foo = new T(backupConf.id, backupConf.conf); var foo = new T(backupConf.id, backupConf.conf, function(){});
foo.checkStatus(function(ok, xhr){ foo.checkStatus(function(){
if (ok)
if (foo.status == 'ready')
{ {
$text.val('OK'); $text.val('OK');
} }
else else
{ {
$text.val(xhr.responseText); $text.val(foo.lastXhr.text);
$status.addClass('error'); $status.addClass('error');
} }
@@ -3546,8 +3581,8 @@
{ {
var conf = NB.storage.getConfig(); var conf = NB.storage.getConfig();
if (conf.backups.length != 2) if (conf.backups.agents.length != 2)
throw 'Invalid conf.backups[]'; // as per fixupConfig() throw 'Invalid conf.backups.agents[]'; // as per fixupConfig()
// //
var $div = $('tt .backup-conf').clone(); var $div = $('tt .backup-conf').clone();
@@ -3557,8 +3592,8 @@
var $rem = $div.find('.rem'); var $rem = $div.find('.rem');
var typ = (new SimpleBackup).type; var typ = (new SimpleBackup).type;
var loc = conf.backups[0]; var loc = conf.backups.agents[0];
var rem = conf.backups[1]; var rem = conf.backups.agents[1];
div.checking = 0; div.checking = 0;
@@ -3641,16 +3676,16 @@
return false; return false;
if (foo.loc.enabled && ! loc.enabled) if (foo.loc.enabled && ! loc.enabled)
foo.loc.id = typ + '-' + (conf.nextBackupId++); foo.loc.id = typ + '-' + (conf.backups.nextId++);
if (foo.rem.enabled && ! rem.enabled) if (foo.rem.enabled && ! rem.enabled)
foo.rem.id = typ + '-' + (conf.nextBackupId++); foo.rem.id = typ + '-' + (conf.backups.nextId++);
conf.backups[0] = foo.loc; conf.backups.agents[0] = foo.loc;
conf.backups[1] = foo.rem; conf.backups.agents[1] = foo.rem;
NB.storage.initBackups(onBackupStatusChange);
NB.storage.saveConfig(); NB.storage.saveConfig();
NB.storage.initBackups(onBackupStatus);
hideOverlay(); hideOverlay();
}); });
@@ -3662,14 +3697,17 @@
showOverlay($div); showOverlay($div);
} }
function onBackupStatus(_status) function onBackupStatusChange(agent)
{ {
var backups = NB.storage.backups; var agents = NB.storage.backups.agents;
var status = NB.storage.backupStatus;
var $config = $('.config'); var $config = $('.config');
var $status = $('.config .teaser u') var $status = $('.config .teaser u')
if (! backups.length) // if (agent) console.log( `onBackupStatusChange: ${agent.id}, status ${agent.status}, op ${agent.lastOp}, xhr '${agent.lastXhr.text}' / ${agent.lastXhr.code}` );
// else console.log( `onBackupStatusChange: <generic>` );
if (! agents.length)
{ {
$config.removeClass('backups-on backup-err backing-up'); $config.removeClass('backups-on backup-err backing-up');
return; return;
@@ -3677,13 +3715,51 @@
$config.addClass('backups-on'); $config.addClass('backups-on');
if (status == 'failed') $config.addClass('backup-err').removeClass('backing-up'); else var busy = 0;
if (status == 'busy') $config.addClass('backing-up').removeClass('backup-err'); else var error = 0;
if (status == 'ok') $config.removeClass('backing-up backup-err'); var ready = 0;
// if become 'ok' - process all pending backups agents.forEach(function(agent){
if (agent.status == 'busy') busy++; else
if (agent.status == 'error') error++; else
if (agent.status == 'ready') ready++; else
throw `Unknown status [${agent.status}] on backup agent ${agent.id}`;
});
// ... if (error > 0) $config.addClass('backup-err').removeClass('backing-up'); else
if (busy > 0) $config.addClass('backing-up').removeClass('backup-err'); else
$config.removeClass('backing-up backup-err');
// process all pending backups if needed
if (! error && ! busy)
runPendingBackups();
}
function runPendingBackups()
{
// var boards = NB.storage.getBoardIndex();
// var backups = NB.storage.backups;
// var backupSet = [];
//
// backups.forEach(function(b){ backupSet.push(b.id); });
// backupSet.sort();
//
// console.log('Checking for pending backups. Current backup set - ', backupSet);
//
// boards.forEach(function(meta, id){
//
// var backItUp = false;
//
// if (! meta.needsBackup && jsonMatch(meta.backups.sort(), backupSet))
// return;
//
// console.log( `Board ${id} may need a backup`, meta.backups );
//
// var board = NB.storage.loadBoard(id);
// if (board)
// NB.storage.backupBoard(id, board, meta)
// });
} }
/* /*
@@ -4860,7 +4936,7 @@
console.log( `Active: [${conf.board}]` ); console.log( `Active: [${conf.board}]` );
console.log( `Theme: [${conf.theme}]` ); console.log( `Theme: [${conf.theme}]` );
console.log( `Font: [${conf.fontName}], size [${conf.fontSize || '-'}], line-height [${conf.lineHeight || '-'}]` ); console.log( `Font: [${conf.fontName}], size [${conf.fontSize || '-'}], line-height [${conf.lineHeight || '-'}]` );
console.log( 'Backups: ' + JSON.stringify(conf.backups)); console.log( 'Backups: ', conf.backups);
/* /*
* backups * backups
@@ -4868,7 +4944,7 @@
NB.backupTypes = new Map(); NB.backupTypes = new Map();
NB.backupTypes.set( (new SimpleBackup).type, SimpleBackup ); NB.backupTypes.set( (new SimpleBackup).type, SimpleBackup );
NB.storage.initBackups(onBackupStatus); NB.storage.initBackups(onBackupStatusChange);
/* /*
* the ui * the ui