backups working...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2022-08-01 20:31:26 +03:00
parent 62a6d3de07
commit 8adedd5724

244
pwiki2.js
View File

@ -778,12 +778,19 @@ var FILESTORE_OPTIONS = {
verbose: true, verbose: true,
} }
var getOpts = function(opts){
return {
...FILESTORE_OPTIONS,
...(opts ?? {}),
} }
// func(base[, options]) // func(base[, options])
// -> true/false // -> true/false
// //
// func(base, path[, options]) // func(base, path[, options])
// -> true/false // -> true/false
// //
// XXX not yet sure how w should handle dot-files....
// XXX should these be store methods??? // XXX should these be store methods???
// XXX do we need error checking in these??? // XXX do we need error checking in these???
var exists = var exists =
@ -793,9 +800,7 @@ async function(base, sub, options){
options = sub ?? options options = sub ?? options
sub = base sub = base
base = null } base = null }
var {index} = Object.assign({}, var {index} = getOpts(options)
FILESTORE_OPTIONS,
options ?? {})
var target = base ? var target = base ?
module.path.join(base, sub) module.path.join(base, sub)
@ -813,9 +818,7 @@ async function(base, sub, options){
options = sub ?? options options = sub ?? options
sub = base sub = base
base = null } base = null }
var {index} = Object.assign({}, var {index} = getOpts(options)
FILESTORE_OPTIONS,
options ?? {})
var target = base ? var target = base ?
module.path.join(base, sub) module.path.join(base, sub)
@ -838,9 +841,7 @@ async function(base, sub, options){
options = sub ?? options options = sub ?? options
sub = base sub = base
base = null } base = null }
var {index} = Object.assign({}, var {index} = getOpts(options)
FILESTORE_OPTIONS,
options ?? {})
var levels = module.path.split(sub) var levels = module.path.split(sub)
for(var level of levels){ for(var level of levels){
@ -873,9 +874,7 @@ async function(base, sub, data, options){
data = sub data = sub
sub = base sub = base
base = null } base = null }
var {index} = Object.assign({}, var {index} = getOpts(options)
FILESTORE_OPTIONS,
options ?? {})
var target = base ? var target = base ?
module.path.join(base, sub) module.path.join(base, sub)
@ -910,9 +909,7 @@ async function(base, sub, options){
options = sub ?? options options = sub ?? options
sub = base sub = base
base = '' } base = '' }
var {index} = Object.assign({}, var {index} = getOpts(options)
FILESTORE_OPTIONS,
options ?? {})
// remove leaf... // remove leaf...
var target = base == '' ? var target = base == '' ?
@ -953,10 +950,7 @@ async function(base, sub, options){
var cleanup = var cleanup =
module.cleanup = module.cleanup =
async function(base, options){ async function(base, options){
var {index, clearEmptyDir, dirToFile, verbose} = var {index, clearEmptyDir, dirToFile, verbose} = getOpts(options)
Object.assign({},
FILESTORE_OPTIONS,
options ?? {})
glob(module.path.join(base, '**/*')) glob(module.path.join(base, '**/*'))
.on('end', async function(paths){ .on('end', async function(paths){
@ -986,25 +980,32 @@ async function(base, options){
continue } continue }
} } }) } } } }) }
var backup =
module.backup = {
// XXX backup config???
// //
// backing(<base>[, <options>]) // .create(<base>[, <options>])
// backing(<base>, '**'[, <options>]) // .create(<base>, '**'[, <options>])
// backing(<base>, '**', Date.timeStamp()[, <options>]) // .create(<base>, '**', Date.timeStamp()[, <options>])
// -> <list> // -> <list>
// //
// backing(<base>, <path>[, <version>][, <options>]) // .create(<base>, <path>[, <version>][, <options>])
// -> <list> // -> <list>
// //
// backing(<base>, <path>, false[, <options>]) // .create(<base>, <path>, false[, <options>])
// -> <list> // -> <list>
// //
// .create(..) and .restore(..) are completely symmetrical.
//
// NOTE: backing up ** will include nested backups but will skip the // NOTE: backing up ** will include nested backups but will skip the
// root backup... // root backup but will ignore the root backup dir...
// NOTE: currently this ignores only the first element of the options.backup //
// path. // XXX since these are *almost* identical in structure, can we reuse one
var backup = // to implement the other???
module.backup = // ..or can we implement these in a manner similar to "cp A B" vs. "cp B A"???
async function(base, sub='**', version=Date.timeStamp(), options){ create: async function(base, sub='**', version=Date.timeStamp(), options){
var that = this
if(typeof(sub) == 'object'){ if(typeof(sub) == 'object'){
options = sub options = sub
sub = '**' } sub = '**' }
@ -1012,10 +1013,7 @@ async function(base, sub='**', version=Date.timeStamp(), options){
options = version options = version
version = Date.timeStamp() } version = Date.timeStamp() }
// options... // options...
var {index, backup, verbose, recursive, cleanBackup} = var {index, backup, verbose, recursive, cleanBackup, __batch} = options = getOpts(options)
Object.assign({},
FILESTORE_OPTIONS,
options ?? {})
recursive = recursive ?? false recursive = recursive ?? false
var _backup = backup = var _backup = backup =
@ -1025,22 +1023,20 @@ async function(base, sub='**', version=Date.timeStamp(), options){
backup = backup =
module.path.join( module.path.join(
base, base,
module.path.relative(sub, backup)) module.path.relative(module.path.dirname(sub), backup))
// ** or * -- backup each file in path... // ** or * -- backup each file in path...
if(/[\\\/]*\*\*?$/.test(sub)){ if(/[\\\/]*\*\*?$/.test(sub)){
if(sub.endsWith('**')){
options.recursive = true }
options.__batch = true
if(cleanBackup if(cleanBackup
&& fs.existsSync(backup)){ && fs.existsSync(backup)){
verbose verbose
&& console.log('backup(..): cleaning:', backup) && console.log('.create(..): cleaning:', backup)
await fs.promises.rm(backup, {recursive: true}) } await fs.promises.rm(backup, {recursive: true}) }
if(sub.endsWith('**')){
options = {
...(options ?? {}),
recursive: true,
} }
sub = sub.replace(/[\\\/]*\*\*?$/, '') sub = sub.replace(/[\\\/]*\*\*?$/, '')
// XXX should we ignore only the first element (current) or the sub-path???
var b = module.path.split(_backup) var b = module.path.split(_backup)
.filter(function(p){ .filter(function(p){
return p != '' }) return p != '' })
@ -1051,7 +1047,7 @@ async function(base, sub='**', version=Date.timeStamp(), options){
.filter(function(file){ .filter(function(file){
return !file.includes(b) }) return !file.includes(b) })
.map(async function(file){ .map(async function(file){
return await module.backup(base, sub +'/'+ file, version, options) }) return await that.create(base, sub +'/'+ file, version, options) })
// keep only the paths we backed up... // keep only the paths we backed up...
.filter(function(e){ .filter(function(e){
return !!e }) return !!e })
@ -1064,38 +1060,162 @@ async function(base, sub='**', version=Date.timeStamp(), options){
// nothing to backup... // nothing to backup...
if(!fs.existsSync(target)){ if(!fs.existsSync(target)){
verbose verbose
&& console.log('backup(..): target does not exist:', target) && console.log('.create(..): target does not exist:', target)
return } return }
if(!recursive){
var stat = await fs.promises.stat(target)
if(stat.isDirectory()){
sub += '/'+index
target += '/'+index
// nothing to backup...
if(!fs.existsSync(target)){
verbose
&& console.log('backup(..): nothing to backup:', target)
return } } }
var to = full ? var to = full ?
backup +'/'+ sub backup +'/'+ sub
: backup +'/'+ module.path.basename(sub) : backup +'/'+ module.path.basename(sub)
var todir = module.path.dirname(to) var todir = module.path.dirname(to)
if(!recursive){
var stat = await fs.promises.stat(target)
if(stat.isDirectory()){
target += '/'+index
to += '/'+index
// nothing to backup...
if(!fs.existsSync(target)){
verbose verbose
&& console.log('backup(..):', sub, '->', to) && !__batch
&& console.log('.create(..): nothing to backup:', target)
return } } }
verbose
&& console.log('.create(..):', sub, '->', to)
await fs.promises.mkdir(todir, {recursive: true}) await fs.promises.mkdir(todir, {recursive: true})
await fs.promises.cp(target, to, {force: true, recursive}) await fs.promises.cp(target, to, {force: true, recursive})
return to } } return to } },
restore: async function(base, sub, version, options){
var that = this
// XXX // XXX
var restore = var {index, backup, verbose, recursive, preBackup, __batch} = options = getOpts(options)
module.restore = recursive = recursive ?? false
async function(base, sub, version, options){
var _backup = backup =
version ?
module.path.join(backup, version)
: backup
backup =
module.path.join(
base,
module.path.relative(
module.path.dirname(sub),
backup))
// check if we can restore...
if(!fs.existsSync(backup)){
verbose
&& console.log('restore(..): no backup version:', version)
return }
// XXX should we use the same options...
preBackup
&& await this.create(base, sub, options ?? {})
// ** or * -- backup each file in path...
// NOTE: when restoring there is no difference between ** and *...
if(/[\\\/]*\*\*?$/.test(sub)){
if(sub.endsWith('**')){
options.recursive = true }
// restore...
// NOTE: we have already made a full backup so no need to
// redo it down the line...
options.preBackup = false
options.__batch = true
sub = sub.replace(/[\\\/]*\*\*?$/, '')
var to = module.path.join(base, sub)
var b = module.path.split(_backup)
.filter(function(p){
return p != '' })
.shift()
// cleanup...
// NOTE: we need this stage as the file list we are backing up
// and the one in the target dir can differ, and a single-page
// .restore(..) will only remove collisions...
await fs.promises.readdir(base +'/'+ sub)
.iter()
// skip backups...
.filter(function(file){
return !file.includes(b) })
.map(async function(file){
var p = module.path.join(base, sub, file)
verbose
&& console.log('restore(..): removing:', p)
await fs.promises.rm(p, {recursive: true})
return p })
return fs.promises.readdir(backup)
.iter()
.map(async function(file){
return await that.restore(base, sub+'/'+file, version, options) })
// keep only the paths we backed up...
.filter(function(e){
return !!e })
// single page...
} else {
var index_file = ''
var full = _backup[0] == '/'
var source = full ?
module.path.join(backup, sub)
: module.path.join(backup, module.path.basename(sub))
if(!fs.existsSync(source)){
verbose
&& console.log('restore(..): source not present in backup:', source)
return }
var to = module.path.join(base, sub)
if(fs.existsSync(to)){
var stat = await fs.promises.stat(to)
if(stat.isDirectory()){
var f = module.path.join(to, index)
if(fs.existsSync(f)){
verbose
&& console.log('restore(..): removing:', f)
await fs.promises.rm(f) }
} else {
verbose
&& console.log('restore(..): removing:', to)
await fs.promises.rm(to) } }
if(!recursive){
// handle dir text...
var stat = await fs.promises.stat(source)
if(stat.isDirectory()){
source += '/'+index
to += '/'+index
if(!fs.existsSync(source)){
verbose
&& !__batch
&& console.log('restore(..): source not present in backup:', source)
return } } }
verbose
&& console.log('restore(..): restoring:', to)
await fs.promises.cp(source, to, {recursive: true})
return source } },
//
// Get backup versions...
// listbackups(<base>[, <options>])
// listbackups(<base>, '*'[, <options>])
// -> <list>
//
// Get backup versions containing <path>...
// listbackups(<base>, <path>[, <options>])
// -> <list>
//
list: async function(base, sub, options){
var that = this
if(typeof(sub) == 'object'){
options = sub
sub = '*' }
var {index, backup} = getOpts(options)
// XXX // XXX
},
} }
// - - - - - - - - - - - - - - - - - - - - - - -
// XXX might be a good idea to support ro mode on top level explicitly... // XXX might be a good idea to support ro mode on top level explicitly...
// XXX add monitor API + cache + live mode (auto on when lock detected)... // XXX add monitor API + cache + live mode (auto on when lock detected)...
var FileStoreRO = var FileStoreRO =
@ -1175,7 +1295,7 @@ module.FileStore = {
// XXX should these be generic??? // XXX should these be generic???
// XXX add versioning... // XXX add versioning...
backup: async function(path='**', options={}){ backup: async function(path='**', options={}){
return module.backup( return backup.create(
this.__path__, path, this.__path__, path,
{ {
index: this.__directory_text__, index: this.__directory_text__,
@ -1183,7 +1303,7 @@ module.FileStore = {
...options, ...options,
}) }, }) },
restore: async function(path='**', options={}){ restore: async function(path='**', options={}){
return module.restore( return backup.restore(
this.__path__, path, this.__path__, path,
{ {
index: this.__directory_text__, index: this.__directory_text__,