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