diff --git a/nullboard.html b/nullboard.html index 41b3f43..9730e8c 100644 --- a/nullboard.html +++ b/nullboard.html @@ -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,15 +3503,16 @@ 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 = { - loc: jsonClone(loc), + 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: ` ); + + 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