1
1
Fork 0
mirror of https://github.com/boxgaming/qbjs.git synced 2024-04-28 17:20:13 +00:00
qbjs/vfs.js

480 lines
17 KiB
JavaScript

let VFS = function() {
this.FILE = Symbol("FILE");
this.DIRECTORY = Symbol("DIRECTORY");
let _root = { toString: function() { return "/"; }};
this.rootDirectory = function() {
return _root;
};
this.reset = function() {
_root = { toString: function() { return "/"; }};
}
this.fullPath = function(node) {
if (node == _root) { return "/" };
var n = node;
var path = "";
while (n.name) {
path = "/" + n.name + path;
n = n.parent;
}
return path;
};
this.getParentPath = function(path) {
var parts = this.tokenizePath(path);
var path = "";
if (parts.length < 2) {
if (parts.isFullpath) { return "/"; }
else { return ""; }
}
for (var i=0; i < parts.length-1; i++) {
path += "/" + parts[i];
}
return path;
};
this.getFileName = function(path) {
var parts = this.tokenizePath(path);
return parts[parts.length-1];
};
this.createFile = function(path, parent) {
if (parent == undefined) { parent = _root; }
let fsnode = { type: this.FILE, name: path, data: new ArrayBuffer(0), parent: parent, toString: function() { return this.name; }};
parent[path] = fsnode;
return fsnode;
};
this.createDirectory = function(path, parent) {
if (parent == undefined) { parent = _root; }
let fsnode = { type: this.DIRECTORY, name: path, parent: parent, toString: function() { return this.name; }};
parent[path] = fsnode;
return fsnode;
};
this.getChildren = function(parent, type) {
if (parent == undefined) { parent = _root; }
let nodes = [];
for (var e in parent) {
if (type == undefined || (parent[e] && parent[e].type == type)) {
if ((parent[e].type == this.FILE || parent[e].type == this.DIRECTORY) && e != "parent") {
nodes.push(parent[e]);
}
}
}
nodes.sort();
return nodes;
};
this.writeText = function(file, text) {
file.data = appendBuffer(file.data, this.textToData(text));
};
this.textToData = function(text) {
let chars = [];
for (let i=0; i < text.length; i++) {
chars.push(text.charCodeAt(i));
}
return new Uint8Array(chars).buffer;
};
this.readLine = function(file, offset) {
if (offset == undefined) { offset = 0; }
if (offset >= file.data.byteLength) {
throw new Error("Input past end of file");
}
let view = new Uint8Array(file.data);
let c = null;
let str = "";
while (offset < file.data.byteLength && c != "\n") {
c = String.fromCharCode(view[offset]);
if (c != "\n") {
str += c;
}
offset++;
}
return str;
};
this.readText = function(file) {
let offset = 0;
let view = new Uint8Array(file.data);
let c = null;
let str = "";
while (offset < file.data.byteLength) {
c = String.fromCharCode(view[offset]);
str += c
offset++;
}
return str;
};
this.writeData = function(file, data, offset) {
if (offset == undefined) { offset = 0; }
let start = file.data.slice(0, offset);
let end = file.data.slice(offset + data.byteLength, file.data.byteLength);
file.data = start;
if (start.byteLength < offset) {
file.data = appendBuffer(file.data, new ArrayBuffer(offset - start.byteLength));
}
file.data = appendBuffer(file.data, data);
file.data = appendBuffer(file.data, end);
};
this.readData = function(file, offset, length) {
return file.data.slice(offset, offset + length);
};
this.getNode = function(path, parent) {
let parts = this.tokenizePath(path);
if (parts.isRoot) {
return _root;
}
if (parts.isFullpath) {
parent = _root;
}
let node = null;
for (let i=0; i < parts.length; i++) {
if (parts[i] == ".") {
// move along, nothing to see here
}
else if (parts[i] == "..") {
if (node.parent == undefined) {
node = _root;
}
else {
node = node.parent;
parent = node;
}
}
else {
node = parent[parts[i]];
parent = node;
if (node == undefined) {
return null;
}
}
}
return node;
};
this.getDataURL = async function(file) {
let blob = null;
let type = getTypeFromName(file.name);
if (type) {
blob = new Blob([file.data], { type: type });
}
else {
blob = new Blob([file.data]);
}
let dataUrl = await new Promise(r => {let a=new FileReader(); a.onload=r; a.readAsDataURL(blob)}).then(e => e.target.result);
return dataUrl;
};
this.dataURLToBlob = function(dataURL) {
// convert base64 to raw binary data held in a string
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
var byteString = atob(dataURL.split(',')[1]);
// separate out the mime component
var mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
// create a view into the buffer
var ia = new Uint8Array(ab);
// set the bytes of the buffer to the correct values
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
// write the ArrayBuffer to a blob, and you're done
var blob = new Blob([ab], {type: mimeString});
return blob;
};
function getTypeFromName(filename) {
var parts = filename.split(".");
if (parts.length < 2) { return null; }
var ext = parts.pop();
return types[ext];
}
this.renameNode = function(node, newName) {
// TODO: move the file if the newName includes a path
var parent = node.parent;
parent[node.name] = undefined;
node.name = newName;
parent[node.name] = node;
};
this.removeFile = function(file, parent) {
if (typeof file == "string") {
file = this.getNode(file, parent);
}
if (file && file.type == this.FILE) {
let parent = file.parent;
delete parent[file.name];
}
else {
throw new Error("File not found");
}
};
this.removeDirectory = function(directory, parent) {
if (typeof directory == "string") {
directory = this.getNode(directory, parent);
}
if (directory && directory.type == this.DIRECTORY) {
var childNodes = this.getChildren(directory);
if (childNodes.length == 0) {
let parent = directory.parent;
delete parent[directory.name];
}
else {
// TODO: probably throw an exception
}
}
else {
// TODO: probably throw an exception
}
};
this.tokenizePath = function(path) {
path = path.replaceAll("\\","/");
let parts = path.split("/");
parts.isFullpath = false;
if (path.indexOf("/") == 0) {
parts.isFullpath = true;
}
if (parts[0].match(/[A-Z|a-z]:/)) {
parts.shift();
parts.isFullpath = true;
}
if (parts[0] == "") {
while (parts.length > 0 && parts[0] == "") {
parts.shift();
}
}
if (parts.isFullpath && parts.length == 0) {
parts.isRoot = true;
}
else {
parts.isRoot = false;
}
return parts;
};
function appendBuffer(buffer1, buffer2) {
let tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
}
const types = {
// File Extension MIME Type
'abs': 'audio/x-mpeg',
'ai': 'application/postscript',
'aif': 'audio/x-aiff',
'aifc': 'audio/x-aiff',
'aiff': 'audio/x-aiff',
'aim': 'application/x-aim',
'art': 'image/x-jg',
'asf': 'video/x-ms-asf',
'asx': 'video/x-ms-asf',
'au': 'audio/basic',
'avi': 'video/x-msvideo',
'avx': 'video/x-rad-screenplay',
'bcpio': 'application/x-bcpio',
'bin': 'application/octet-stream',
'bmp': 'image/bmp',
'body': 'text/html',
'cdf': 'application/x-cdf',
'cer': 'application/pkix-cert',
'class': 'application/java',
'cpio': 'application/x-cpio',
'csh': 'application/x-csh',
'css': 'text/css',
'dib': 'image/bmp',
'doc': 'application/msword',
'dtd': 'application/xml-dtd',
'dv': 'video/x-dv',
'dvi': 'application/x-dvi',
'eot': 'application/vnd.ms-fontobject',
'eps': 'application/postscript',
'etx': 'text/x-setext',
'exe': 'application/octet-stream',
'gif': 'image/gif',
'gtar': 'application/x-gtar',
'gz': 'application/x-gzip',
'hdf': 'application/x-hdf',
'hqx': 'application/mac-binhex40',
'htc': 'text/x-component',
'htm': 'text/html',
'html': 'text/html',
'ief': 'image/ief',
'jad': 'text/vnd.sun.j2me.app-descriptor',
'jar': 'application/java-archive',
'java': 'text/x-java-source',
'jnlp': 'application/x-java-jnlp-file',
'jpe': 'image/jpeg',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'js': 'application/javascript',
'jsf': 'text/plain',
'json': 'application/json',
'jspf': 'text/plain',
'kar': 'audio/midi',
'latex': 'application/x-latex',
'm3u': 'audio/x-mpegurl',
'mac': 'image/x-macpaint',
'man': 'text/troff',
'mathml': 'application/mathml+xml',
'me': 'text/troff',
'mid': 'audio/midi',
'midi': 'audio/midi',
'mif': 'application/x-mif',
'mov': 'video/quicktime',
'movie': 'video/x-sgi-movie',
'mp1': 'audio/mpeg',
'mp2': 'audio/mpeg',
'mp3': 'audio/mpeg',
'mp4': 'video/mp4',
'mpa': 'audio/mpeg',
'mpe': 'video/mpeg',
'mpeg': 'video/mpeg',
'mpega': 'audio/x-mpeg',
'mpg': 'video/mpeg',
'mpv2': 'video/mpeg2',
'ms': 'application/x-wais-source',
'nc': 'application/x-netcdf',
'oda': 'application/oda',
'odb': 'application/vnd.oasis.opendocument.database',
'odc': 'application/vnd.oasis.opendocument.chart',
'odf': 'application/vnd.oasis.opendocument.formula',
'odg': 'application/vnd.oasis.opendocument.graphics',
'odi': 'application/vnd.oasis.opendocument.image',
'odm': 'application/vnd.oasis.opendocument.text-master',
'odp': 'application/vnd.oasis.opendocument.presentation',
'ods': 'application/vnd.oasis.opendocument.spreadsheet',
'odt': 'application/vnd.oasis.opendocument.text',
'otg': 'application/vnd.oasis.opendocument.graphics-template',
'oth': 'application/vnd.oasis.opendocument.text-web',
'otp': 'application/vnd.oasis.opendocument.presentation-template',
'ots': 'application/vnd.oasis.opendocument.spreadsheet-template',
'ott': 'application/vnd.oasis.opendocument.text-template',
'ogx': 'application/ogg',
'ogv': 'video/ogg',
'oga': 'audio/ogg',
'ogg': 'audio/ogg',
'otf': 'application/x-font-opentype',
'spx': 'audio/ogg',
'flac': 'audio/flac',
'anx': 'application/annodex',
'axa': 'audio/annodex',
'axv': 'video/annodex',
'xspf': 'application/xspf+xml',
'pbm': 'image/x-portable-bitmap',
'pct': 'image/pict',
'pdf': 'application/pdf',
'pgm': 'image/x-portable-graymap',
'pic': 'image/pict',
'pict': 'image/pict',
'pls': 'audio/x-scpls',
'png': 'image/png',
'pnm': 'image/x-portable-anymap',
'pnt': 'image/x-macpaint',
'ppm': 'image/x-portable-pixmap',
'ppt': 'application/vnd.ms-powerpoint',
'pps': 'application/vnd.ms-powerpoint',
'ps': 'application/postscript',
'psd': 'image/vnd.adobe.photoshop',
'qt': 'video/quicktime',
'qti': 'image/x-quicktime',
'qtif': 'image/x-quicktime',
'ras': 'image/x-cmu-raster',
'rdf': 'application/rdf+xml',
'rgb': 'image/x-rgb',
'rm': 'application/vnd.rn-realmedia',
'roff': 'text/troff',
'rtf': 'application/rtf',
'rtx': 'text/richtext',
'sfnt': 'application/font-sfnt',
'sh': 'application/x-sh',
'shar': 'application/x-shar',
'sit': 'application/x-stuffit',
'snd': 'audio/basic',
'src': 'application/x-wais-source',
'sv4cpio': 'application/x-sv4cpio',
'sv4crc': 'application/x-sv4crc',
'svg': 'image/svg+xml',
'svgz': 'image/svg+xml',
'swf': 'application/x-shockwave-flash',
't': 'text/troff',
'tar': 'application/x-tar',
'tcl': 'application/x-tcl',
'tex': 'application/x-tex',
'texi': 'application/x-texinfo',
'texinfo': 'application/x-texinfo',
'tif': 'image/tiff',
'tiff': 'image/tiff',
'tr': 'text/troff',
'tsv': 'text/tab-separated-values',
'ttf': 'application/x-font-ttf',
'txt': 'text/plain',
'ulw': 'audio/basic',
'ustar': 'application/x-ustar',
'vxml': 'application/voicexml+xml',
'xbm': 'image/x-xbitmap',
'xht': 'application/xhtml+xml',
'xhtml': 'application/xhtml+xml',
'xls': 'application/vnd.ms-excel',
'xml': 'application/xml',
'xpm': 'image/x-xpixmap',
'xsl': 'application/xml',
'xslt': 'application/xslt+xml',
'xul': 'application/vnd.mozilla.xul+xml',
'xwd': 'image/x-xwindowdump',
'vsd': 'application/vnd.visio',
'wav': 'audio/x-wav',
'wbmp': 'image/vnd.wap.wbmp',
'wml': 'text/vnd.wap.wml',
'wmlc': 'application/vnd.wap.wmlc',
'wmls': 'text/vnd.wap.wmlsc',
'wmlscriptc': 'application/vnd.wap.wmlscriptc',
'wmv': 'video/x-ms-wmv',
'woff': 'application/font-woff',
'woff2': 'application/font-woff2',
'wrl': 'model/vrml',
'wspolicy': 'application/wspolicy+xml',
'z': 'application/x-compress',
'zip': 'application/zip'
};
}
if (!ArrayBuffer.prototype.slice) {
ArrayBuffer.prototype.slice = function (start, end) {
let that = new Uint8Array(this);
if (end == undefined) end = that.length;
let result = new ArrayBuffer(end - start);
let resultArray = new Uint8Array(result);
for (let i = 0; i < resultArray.length; i++)
resultArray[i] = that[i + start];
return result;
}
}