// // Copyright (C) Stanislaw Adaszewski, 2020 // Contact: s.adaszewski@gmail.com // Website: https://adared.ch/wba // License: GNU Affero General Public License, Version 3 // // // Directory: Hash[string, [Directory, File]] // File = [blockRefs, size] // blockRefs: Array[blockRef] // blockRef: [locator, position, size] // locator: String // position: Number // size: Number // class WBManifestReader { constructor(manifest_text) { this.rootDir = {}; if (!manifest_text) return; this.parse(manifest_text); } makeDir(parent, name) { if (!(name in parent)) parent[name] = {}; if (parent[name] instanceof Array) throw Error('Conflict trying to create a directory - a file with the same name already exists: ' + name); return parent[name]; } makePath(path) { if (typeof(path) === 'string') path = path.split('/'); let dir = this.rootDir; for (let i = 1; i < path.length; i++) dir = this.makeDir(dir, path[i]); return dir; } appendFile(streamName, locators, position, size, fileName) { let path = streamName + '/' + fileName; path = path.split('/'); let dir = this.makePath(path.slice(0, path.length - 1)); if (!(fileName in dir)) dir[fileName] = [[], 0]; if (!(dir[fileName] instanceof Array)) throw Error('Conflict trying to create a file - a directory with the same name already exists: ' + fileName); //this.appendReferences(dir[fileName], locators, position, size); } appendReferences(file, locators, position, size) { if (size === 0) return; let cum = 0; let locHashes = locators.map(loc => loc[0]); let locSizes = locators.map(loc => loc[1]); let locPositions = locators.map(loc => { let res = cum; cum += loc[1]; return res; }); let used = locators.map((_, i) => (locPositions[i] + locSizes[i] > position && locPositions[i] < position + size)); let startBlock = used.indexOf(true); let endBlock = used.lastIndexOf(true) + 1; // console.log('startBlock: ' + startBlock + ', endBlock: ' + endBlock); if (startBlock === -1) return; let blockRefs = []; let runPos = position; let runSize = size; for (let i = startBlock; i < endBlock; i++) { let blockPos = runPos - locPositions[i]; let blockSize = Math.min(runSize, locSizes[i] - blockPos); blockRefs.push([ locHashes[i], blockPos, blockSize ]); runPos += blockSize; runSize -= blockSize; } file[0] = file[0].concat(blockRefs); file[1] += size; } parse(manifest_text) { let rx = /^[a-f0-9]{32}\+[0-9]+/; let streams = manifest_text.split('\n'); if (!streams[streams.length - 1]) streams = streams.slice(0, streams.length - 1); streams.map(s => { let tokens = s.split(' '); let streamName = this.unescapeName(tokens[0]); let n = tokens.map(t => rx.exec(t)); n = n.indexOf(null, 1); let locators = tokens.slice(1, n); locators = locators.map(loc => [ loc, Number(loc.split('+')[1]) ]); let fileTokens = tokens.slice(n); fileTokens.map(t => { let [ position, size, ...fileName ] = t.split(':'); fileName = fileName.join(':'); fileName = this.unescapeName(fileName); this.appendFile(streamName, locators, Number(position), Number(size), fileName); }); }); } findDir(path) { if (typeof(path) === 'string') path = path.split('/'); if (path[0] !== '.') throw Error('Path must begin with a dot component'); let dir = this.rootDir; for (let i = 1; i < path.length; i++) { if (!(path[i] in dir)) throw Error('Directory not found'); if (dir[path[i]] instanceof Array) throw Error('Path is a file not directory'); dir = dir[path[i]]; } return dir; } listDirectory(path) { let dir = this.findDir(path); let keys = Object.keys(dir); keys.sort(); let subdirs = keys.filter(k => !(dir[k] instanceof Array)); let files = keys.filter(k => (dir[k] instanceof Array)); let res = subdirs.map(k => [ 'd', k, null ]); res = res.concat(files.map(k => [ 'f', k, dir[k][1] ])); return res; } unescapeName(name) { return name.replace(/(\\\\|\\[0-9]{3})/g, (_, $1) => ($1 === '\\\\' ? '\\' : String.fromCharCode(parseInt($1.substr(1), 8)))); } escapeName(name) { return name.replace(/ /g, '\\040'); } /* let ids = { '\\': 1, '0': 2, '4': 3 }; let transitions = [ [ [0, 0], [1, ''], [0, 0], [0, 0] ], [ [0, 0], [0, '\\'], [2, ''], [0, 0] ], ]; let mode = 0; for (let i = 0; i < name.length; i++) { let b = name[i]; let tokenId = Number(ids[b]); [ mode, out ] = transitions[mode][tokenId]; if (out === 0) out = b; } }*/ getFile(path) { if (typeof(path) === 'string') path = path.split('/'); if (path.length < 2) throw Error('Invalid file path'); let name = path[path.length - 1]; let dir = this.findDir(path.slice(0, path.length - 1)); if (!(name in dir)) throw Error('File not found'); if (!(dir[name] instanceof Array)) throw Error('Path points to a directory not a file'); return dir[name]; } } export { WBManifestReader };