2018-10-09 20:30:59 +08:00
|
|
|
/*
|
|
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
|
|
*/
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const fs = require("fs");
|
|
|
|
const mkdirp = require("mkdirp");
|
|
|
|
const path = require("path");
|
|
|
|
const SerializerMiddleware = require("./SerializerMiddleware");
|
|
|
|
|
|
|
|
class Section {
|
|
|
|
constructor(items) {
|
|
|
|
this.items = items;
|
|
|
|
this.parts = undefined;
|
|
|
|
this.length = NaN;
|
|
|
|
this.offset = NaN;
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve() {
|
|
|
|
let hasPromise = false;
|
|
|
|
let lastPart = undefined;
|
|
|
|
const parts = [];
|
|
|
|
let length = 0;
|
|
|
|
for (const item of this.items) {
|
|
|
|
if (typeof item === "function") {
|
|
|
|
const r = item();
|
|
|
|
if (r instanceof Promise) {
|
|
|
|
parts.push(r.then(items => new Section(items).resolve()));
|
|
|
|
hasPromise = true;
|
2018-10-18 21:52:22 +08:00
|
|
|
} else if (r) {
|
2018-10-09 20:30:59 +08:00
|
|
|
parts.push(new Section(r).resolve());
|
2018-10-18 21:52:22 +08:00
|
|
|
} else {
|
|
|
|
return null;
|
2018-10-09 20:30:59 +08:00
|
|
|
}
|
|
|
|
length += 12; // 0, offset, size
|
|
|
|
lastPart = undefined;
|
|
|
|
} else if (lastPart) {
|
|
|
|
lastPart.push(item);
|
|
|
|
length += item.length;
|
|
|
|
} else {
|
|
|
|
length += 4; // size
|
|
|
|
length += item.length;
|
|
|
|
lastPart = [item];
|
|
|
|
parts.push(lastPart);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.length = length;
|
|
|
|
if (hasPromise) {
|
|
|
|
return Promise.all(parts).then(parts => {
|
|
|
|
this.parts = parts;
|
2018-10-18 21:52:22 +08:00
|
|
|
if (!parts.every(Boolean)) return null;
|
2018-10-09 20:30:59 +08:00
|
|
|
return this;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.parts = parts;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getSections() {
|
|
|
|
return this.parts.filter(p => p instanceof Section);
|
|
|
|
}
|
|
|
|
|
|
|
|
emit(out) {
|
|
|
|
for (const part of this.parts) {
|
|
|
|
if (part instanceof Section) {
|
|
|
|
const pointerBuf = Buffer.alloc(12);
|
|
|
|
pointerBuf.writeUInt32LE(0, 0);
|
|
|
|
pointerBuf.writeUInt32LE(part.offset, 4);
|
|
|
|
pointerBuf.writeUInt32LE(part.length, 8);
|
|
|
|
out.push(pointerBuf);
|
|
|
|
} else {
|
|
|
|
const sizeBuf = Buffer.alloc(4);
|
|
|
|
out.push(sizeBuf);
|
|
|
|
let len = 0;
|
|
|
|
for (const buf of part) {
|
|
|
|
len += buf.length;
|
|
|
|
out.push(buf);
|
|
|
|
}
|
|
|
|
sizeBuf.writeUInt32LE(len, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const createPointer = (filename, offset, size) => {
|
|
|
|
return () => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
// TODO handle concurrent access to file
|
|
|
|
fs.open(filename, "r", (err, file) => {
|
|
|
|
if (err) return reject(err);
|
|
|
|
|
|
|
|
readSection(filename, file, offset, size, (readErr, parts) => {
|
|
|
|
fs.close(file, err => {
|
|
|
|
if (err) return reject(err);
|
|
|
|
if (readErr) return reject(readErr);
|
|
|
|
|
|
|
|
resolve(parts);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const readSection = (filename, file, offset, size, callback) => {
|
|
|
|
const buffer = Buffer.alloc(size);
|
|
|
|
fs.read(file, buffer, 0, size, offset, err => {
|
|
|
|
if (err) return callback(err);
|
|
|
|
|
|
|
|
const result = [];
|
|
|
|
let pos = 0;
|
|
|
|
while (pos < buffer.length) {
|
|
|
|
const len = buffer.readUInt32LE(pos);
|
|
|
|
pos += 4;
|
|
|
|
if (len === 0) {
|
|
|
|
const pOffset = buffer.readUInt32LE(pos);
|
|
|
|
pos += 4;
|
|
|
|
const pSize = buffer.readUInt32LE(pos);
|
|
|
|
pos += 4;
|
|
|
|
result.push(createPointer(filename, pOffset, pSize));
|
|
|
|
} else {
|
|
|
|
const buf = buffer.slice(pos, pos + len);
|
|
|
|
pos += len;
|
|
|
|
result.push(buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
callback(null, result);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
class FileMiddleware extends SerializerMiddleware {
|
|
|
|
/**
|
|
|
|
* @param {any[]} data data items
|
|
|
|
* @param {TODO} context TODO
|
|
|
|
* @returns {any[]|Promise<any[]>} serialized data
|
|
|
|
*/
|
|
|
|
serialize(data, { filename }) {
|
|
|
|
const root = new Section(data);
|
|
|
|
|
|
|
|
const r = root.resolve();
|
|
|
|
|
2018-10-18 21:52:22 +08:00
|
|
|
return Promise.resolve(r).then(root => {
|
|
|
|
if (!root) return null;
|
2018-10-09 20:30:59 +08:00
|
|
|
// calc positions in file
|
|
|
|
let currentOffset = 4;
|
|
|
|
const processOffsets = section => {
|
|
|
|
section.offset = currentOffset;
|
|
|
|
currentOffset += section.length;
|
|
|
|
for (const child of section.getSections()) {
|
|
|
|
processOffsets(child);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
processOffsets(root);
|
|
|
|
|
|
|
|
// get buffers to write
|
|
|
|
const sizeBuf = Buffer.alloc(4);
|
|
|
|
sizeBuf.writeUInt32LE(root.length, 0);
|
|
|
|
const buffers = [sizeBuf];
|
|
|
|
const emit = (section, out) => {
|
|
|
|
section.emit(out);
|
|
|
|
for (const child of section.getSections()) {
|
|
|
|
emit(child, out);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
emit(root, buffers);
|
|
|
|
|
|
|
|
// write to file
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
mkdirp(path.dirname(filename), err => {
|
|
|
|
if (err) return reject(err);
|
|
|
|
fs.writeFile(filename, Buffer.concat(buffers), err => {
|
|
|
|
if (err) return reject(err);
|
2018-10-23 17:31:20 +08:00
|
|
|
resolve(true);
|
2018-10-09 20:30:59 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {any[]} data data items
|
|
|
|
* @param {TODO} context TODO
|
|
|
|
* @returns {any[]|Promise<any[]>} deserialized data
|
|
|
|
*/
|
|
|
|
deserialize(data, { filename }) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
fs.open(filename, "r", (err, file) => {
|
|
|
|
if (err) return reject(err);
|
|
|
|
|
|
|
|
const sizeBuf = Buffer.alloc(4);
|
|
|
|
fs.read(file, sizeBuf, 0, 4, 0, err => {
|
|
|
|
if (err) return reject(err);
|
|
|
|
|
|
|
|
const rootSize = sizeBuf.readUInt32LE(0);
|
|
|
|
|
|
|
|
readSection(filename, file, 4, rootSize, (readErr, parts) => {
|
|
|
|
fs.close(file, err => {
|
|
|
|
if (err) return reject(err);
|
|
|
|
if (readErr) return reject(readErr);
|
|
|
|
|
|
|
|
resolve(parts);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = FileMiddleware;
|