Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ indent_size = 2
[*.ts]
indent_style = space
indent_size = 2

[Burner.ts]
indent_style = space
indent_size = 4
61 changes: 55 additions & 6 deletions src/Burner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,22 @@ export interface Entry {
interface LiteEntry {
entry: Entry;

/**
* Lesser side of {@link Entry.name}
*/
left: number;

/**
* Greater side of {@link Entry.name}
*/
right: number;

child: number;

firstSector: number;
isMini?: boolean;

isRed: boolean;
}

function RoundUpto4096(num: number) {
Expand Down Expand Up @@ -144,6 +154,7 @@ class LiteBurner {
child: -1,
firstSector: 0,
isMini: it.length < 4096,
isRed: false,
})
);

Expand Down Expand Up @@ -275,7 +286,7 @@ class LiteBurner {
ds.seek(pos + 0x40);
ds.writeUint16(Math.min(64, numBytesName + 2));
ds.writeUint8(liteEnt.entry.type);
ds.writeUint8((x === 0) ? 0 : 1);
ds.writeUint8(liteEnt.isRed ? 0 : 1);
ds.writeInt32(liteEnt.left);
ds.writeInt32(liteEnt.right);
ds.writeInt32(liteEnt.child);
Expand Down Expand Up @@ -362,6 +373,11 @@ class LiteBurner {
return t;
}

/**
* Build the directory tree.
*
* @param dirIndex The index of the directory entry to be built.
*/
private buildTree(dirIndex: number) {
const { liteEnts } = this;
const liteEntry = liteEnts[dirIndex];
Expand All @@ -370,8 +386,9 @@ class LiteBurner {
throw new Error("It must be a storage!");
}

// Array.sort is destructive, so copy it by concat() before changing
const children = liteEntry.entry.children.concat();
if (children.length >= 1) {
if (1 <= children.length) {
children.sort(
(a, b) => {
return this.compareName(
Expand All @@ -381,12 +398,44 @@ class LiteBurner {
}
);

liteEntry.child = children[0];

for (let x = 0; x < children.length - 1; x++) {
liteEnts[children[x]].right = children[x + 1];
// ( | 0 )
// ( 0 | 1 )
// ( 0 | 1 2 )

// (left, right), returns first right node
const split2 = (start: number, end: number, isRed: boolean): number => {
if (start < end) {
const midNum = Math.floor((start + end) / 2);
const entryIndex = children[midNum];
const entry = liteEnts[entryIndex];
entry.isRed = isRed;
entry.left = split2(start, midNum, !isRed);
entry.right = split2(midNum + 1, end, !isRed);
return entryIndex;
} else {
return -1;
}
}

// ( | 0 | )
// ( | 0 | 1 )
// ( 0 | 1 | 2 )
// ( 0 | 1 | 2 3 )
// ( 0 1 | 2 | 3 4 )

// (left, root, right), returns root node
const split3 = (): number => {
const midNum = Math.floor(children.length / 2);
const entryIndex = children[midNum];
const entry = liteEnts[entryIndex];
entry.isRed = false;
entry.left = split2(0, midNum, true);
entry.right = split2(midNum + 1, children.length, true);
return entryIndex;
};

liteEntry.child = split3();

for (let subIndex of children
.filter(it => liteEnts[it].entry.type === TypeEnum.DIRECTORY)
) {
Expand Down
217 changes: 180 additions & 37 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,65 @@ describe('Burner', function () {
const burn = require('../lib/Burner').burn;
const Reader = require('../lib/Reader').Reader;

/**
*
* path: `file`, `dir/file`, `dir1/dir2/file`
*
* @param { path: string, binary?: ArrayLike<number> }[] entries
* @returns { array: Uint8Array }
*/
const burnByFsEntries = (entries) => {
const fsEntries = [
{
name: "Root Entry",
type: TypeEnum.ROOT,
length: 0,
children: [],
},
];

for (const entry of entries) {
const { path, binary } = entry;
const elements = path.split('/');

let parentIndex = 0;
for (let index = 0; index < elements.length - 1; index++) {
const name = elements[index];
const hitIndex = fsEntries[parentIndex].children.find(it => fsEntries[it].name === name);
if (hitIndex === undefined) {
const newIndex = fsEntries.length;
fsEntries.push({
name,
type: TypeEnum.DIRECTORY,
length: 0,
children: [],
});
fsEntries[parentIndex].children.push(newIndex);
parentIndex = newIndex;
} else {
parentIndex = hitIndex;
}
}

{
const newIndex = fsEntries.length;

fsEntries.push({
name: elements[elements.length - 1],
type: TypeEnum.DOCUMENT,
length: binary ? binary.length : 0,
binaryProvider: binary ? () => new Uint8Array(binary) : undefined,
children: [],
});

fsEntries[parentIndex].children.push(newIndex);
}
}

const array = burn(fsEntries);
return { array: array };
};

const burnAFileHavingLengthBy = (x) => {
const writeData = new Uint8Array(x);
for (let t = 0; t < writeData.length; t++) {
Expand All @@ -653,50 +712,134 @@ describe('Burner', function () {
return { writeData, array };
};

const runReaderWith = ({ writeData, array }) => {
const reader = new Reader(array);
reader.parse();

const readData = reader.rootFolder().readFile("file");
assert.deepStrictEqual(readData, writeData);
};
const files10 = [
{ path: "file1", binary: new Uint8Array(Buffer.from("data1")) },
{ path: "file2", binary: new Uint8Array(Buffer.from("data2")) },
{ path: "file3", binary: new Uint8Array(Buffer.from("data3")) },
{ path: "file4", binary: new Uint8Array(Buffer.from("data4")) },
{ path: "file5", binary: new Uint8Array(Buffer.from("data5")) },
{ path: "file6", binary: new Uint8Array(Buffer.from("data6")) },
{ path: "file7", binary: new Uint8Array(Buffer.from("data7")) },
{ path: "file8", binary: new Uint8Array(Buffer.from("data8")) },
{ path: "file9", binary: new Uint8Array(Buffer.from("data9")) },
{ path: "file10", binary: new Uint8Array(Buffer.from("data10")) },
];

const dirs3_10 = [
{ path: "d1/e1/file1", binary: new Uint8Array(Buffer.from("data1")) },
{ path: "d1/e2/file2", binary: new Uint8Array(Buffer.from("data2")) },
{ path: "d2/e1/file3", binary: new Uint8Array(Buffer.from("data3")) },
{ path: "d2/e2/file4", binary: new Uint8Array(Buffer.from("data4")) },
{ path: "d3/e1/file5", binary: new Uint8Array(Buffer.from("data5")) },
{ path: "d3/e2/file6", binary: new Uint8Array(Buffer.from("data6")) },
{ path: "d1/e1/file7", binary: new Uint8Array(Buffer.from("data7")) },
{ path: "d2/e1/file8", binary: new Uint8Array(Buffer.from("data8")) },
{ path: "d3/e1/file9", binary: new Uint8Array(Buffer.from("data9")) },
{ path: "d1/e1/file10", binary: new Uint8Array(Buffer.from("data10")) },
];

describe('Compare file contents among Burner/Reader', function () {
const testIt = function (length) {
return runReaderWith(
burnAFileHavingLengthBy(length)
);
}
describe('file size', function () {
const runReaderWith = ({ writeData, array }) => {
const reader = new Reader(array);
reader.parse();

it('file size 0', function () { testIt(0); });
it('file size 1', function () { testIt(1); });
it('file size 63', function () { testIt(63); });
it('file size 64 (minifat sector size)', function () { testIt(64); });
it('file size 65', function () { testIt(65); });
it('file size 511', function () { testIt(511); });
it('file size 512 (fat sector size)', function () { testIt(512); });
it('file size 513', function () { testIt(513); });
it('file size 65537', function () { testIt(65537); });
const readData = reader.rootFolder().readFile("file");
assert.deepStrictEqual(readData, writeData);
};

const testIt = function (length) {
return runReaderWith(
burnAFileHavingLengthBy(length)
);
};

it('file size 0', function () { testIt(0); });
it('file size 1', function () { testIt(1); });
it('file size 63', function () { testIt(63); });
it('file size 64 (minifat sector size)', function () { testIt(64); });
it('file size 65', function () { testIt(65); });
it('file size 511', function () { testIt(511); });
it('file size 512 (fat sector size)', function () { testIt(512); });
it('file size 513', function () { testIt(513); });
it('file size 65537', function () { testIt(65537); });
});

describe('tree builder', function () {
const runReaderWith = (array, entries) => {
const reader = new Reader(array);
reader.parse();

const loadedEntries = [];

function walk(folder, prefix) {
for (const subFolder of folder.subFolders()) {
walk(subFolder, prefix + subFolder.name + "/");
}

for (const fileSet of folder.fileNameSets()) {
const path = `${prefix}${fileSet.name}`;
loadedEntries.push({
path: path,
binary: fileSet.provider(),
});
}
}

walk(reader.rootFolder(), "");

function sort(array) {
return [...array].sort((a, b) => a.path.localeCompare(b.path));
}

assert.deepStrictEqual(sort(loadedEntries), sort(entries));
};

const testIt = function (entries) {
return runReaderWith(
burnByFsEntries(entries).array,
entries
);
};

it('files10', function () { testIt(files10); });
it('dirs3_10', function () { testIt(dirs3_10); });
});
});

(useValidateCompoundFile ? describe : describe.skip)('validateCompoundFile', function () {
const testIt = async function (length) {
await runValidateCompoundFileAsync(
{
binary: burnAFileHavingLengthBy(length).array,
}
);
}
describe("file size", function () {
const testIt = async function (length) {
await runValidateCompoundFileAsync(
{
binary: burnAFileHavingLengthBy(length).array,
}
);
}

it('file size 0', function () { return testIt(0); });
it('file size 1', function () { return testIt(1); });
it('file size 63', function () { return testIt(63); });
it('file size 64 (minifat sector size)', function () { return testIt(64); });
it('file size 65', function () { return testIt(65); });
it('file size 511', function () { return testIt(511); });
it('file size 512 (fat sector size)', function () { return testIt(512); });
it('file size 513', function () { return testIt(513); });
it('file size 65537', function () { return testIt(65537); });
it('file size 0', function () { return testIt(0); });
it('file size 1', function () { return testIt(1); });
it('file size 63', function () { return testIt(63); });
it('file size 64 (minifat sector size)', function () { return testIt(64); });
it('file size 65', function () { return testIt(65); });
it('file size 511', function () { return testIt(511); });
it('file size 512 (fat sector size)', function () { return testIt(512); });
it('file size 513', function () { return testIt(513); });
it('file size 65537', function () { return testIt(65537); });
});

describe("tree builder", function () {
const testIt = async function (entries) {
await runValidateCompoundFileAsync(
{
binary: burnByFsEntries(entries).array,
}
);
}

it('files10', function () { testIt(files10); });
it('dirs3_10', function () { testIt(dirs3_10); });
});
});
});

Expand Down
Loading