Skip to content

Commit 2efc8f3

Browse files
committed
Adds implementation of Stream r/w operations (for both Regular-length and Mini streams)
1 parent 116d55b commit 2efc8f3

File tree

7 files changed

+538
-0
lines changed

7 files changed

+538
-0
lines changed

src/stream/MiniStreamRW.ts

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import {FAT} from "../alloc/FAT";
2+
import {MiniFAT} from "../alloc/MiniFAT";
3+
import {Header} from "../Header";
4+
import {Sectors} from "../Sectors";
5+
import {ENDOFCHAIN_MARK_INT, FREESECT_MARK_OR_NOSTREAM_INT, initializedWith} from "../utils";
6+
import {VariableSizeChunkedDataView} from "../dataview/VarSizeChunkedDataview";
7+
import {CFDataview} from "../dataview/СFDataview";
8+
import {Sector} from "../dataview/Sector";
9+
import {StreamRW} from "./StreamRW";
10+
11+
export class MiniStreamRW implements StreamRW {
12+
13+
public static readonly MINI_STREAM_CHUNK_SIZE = 64;
14+
private readonly miniFAT: MiniFAT;
15+
private readonly header: Header;
16+
private miniStreamLength: number;
17+
private readonly fat: FAT;
18+
private readonly miniStreamSectorChain: number[];
19+
private readonly sectors: Sectors;
20+
21+
constructor(miniFAT: MiniFAT, fat: FAT, firstMiniStreamSector: number, miniStreamLength: number, sectors: Sectors, header: Header) {
22+
this.miniFAT = miniFAT;
23+
this.fat = fat;
24+
this.miniStreamLength = miniStreamLength;
25+
if(firstMiniStreamSector >= 0) {
26+
this.miniStreamSectorChain = fat.buildChain(firstMiniStreamSector);
27+
} else {
28+
this.miniStreamSectorChain = [];
29+
}
30+
this.sectors = sectors;
31+
this.header = header;
32+
}
33+
34+
read(startingSector: number, lengthOrFromIncl: number, toExcl?: number): number[] {
35+
if(toExcl == null) {
36+
const result = initializedWith(lengthOrFromIncl, 0);
37+
let position = 0;
38+
for (const sectorNumber of this.miniFAT.buildChain(startingSector)) {
39+
if (lengthOrFromIncl > 0) {
40+
const data = this.getMiniSectorData(sectorNumber);
41+
const bytesToRead = Math.min(data.getSize(), lengthOrFromIncl);
42+
result.splice(position, bytesToRead, ...data.subView(0, bytesToRead).getData());
43+
position += bytesToRead;
44+
lengthOrFromIncl -= bytesToRead;
45+
} else {
46+
break;
47+
}
48+
}
49+
return result;
50+
} else {
51+
return new VariableSizeChunkedDataView(this.miniFAT.buildChain(startingSector).map((position) => this.getMiniSectorData(position)))
52+
.subView(lengthOrFromIncl, toExcl).getData();
53+
}
54+
}
55+
56+
getMiniSectorData(position: number): CFDataview {
57+
const sectorPosition = position * Math.floor(this.header.getMiniSectorShift() / this.header.getSectorShift());
58+
const shiftInsideSector = position * this.header.getMiniSectorShift() % this.header.getSectorShift();
59+
return this.sectors.sector(this.miniStreamSectorChain[sectorPosition]).subView(shiftInsideSector, shiftInsideSector + this.header.getMiniSectorShift());
60+
}
61+
62+
write(data: number[]): number {
63+
if(data.length <= 0) {
64+
throw new Error();
65+
}
66+
const numberOfChunks = this.howManyChunksNeeded(data.length);
67+
let firstMiniSectorPosition = ENDOFCHAIN_MARK_INT;
68+
for (let i = 0; i < numberOfChunks; i++) {
69+
const bytesFromPosition = i * this.header.getMiniSectorShift();
70+
const bytesUpToPosition = Math.min((i + 1) * this.header.getMiniSectorShift(), data.length);
71+
const bytesToWrite = data.slice(bytesFromPosition, bytesUpToPosition);
72+
this.getDataHolderForNextChunk().writeAt(0, bytesToWrite);
73+
const miniSectorPosition = this.miniStreamLength / this.header.getMiniSectorShift();
74+
if(firstMiniSectorPosition === ENDOFCHAIN_MARK_INT) {
75+
firstMiniSectorPosition = miniSectorPosition;
76+
}
77+
if(i === 0) {
78+
this.miniFAT.registerSector(miniSectorPosition, null);
79+
} else {
80+
this.miniFAT.registerSector(miniSectorPosition, miniSectorPosition - 1);
81+
}
82+
this.miniStreamLength += this.header.getMiniSectorShift();
83+
}
84+
return firstMiniSectorPosition;
85+
}
86+
87+
howManyChunksNeeded(dataLength: number): number {
88+
let numberOfChunks;
89+
if(dataLength % this.header.getMiniSectorShift() === 0) {
90+
numberOfChunks = Math.floor(dataLength / this.header.getMiniSectorShift());
91+
} else {
92+
numberOfChunks = Math.floor(dataLength / this.header.getMiniSectorShift()) + 1;
93+
}
94+
return numberOfChunks;
95+
}
96+
97+
writeAt(startingSector: number, position: number, data: number[]): void {
98+
new VariableSizeChunkedDataView(this.miniFAT.buildChain(startingSector).map((pos) => this.getMiniSectorData(pos)))
99+
.writeAt(position, data);
100+
}
101+
102+
append(startingSector: number, currentSize: number, data: number[]): number {
103+
const sectorChain = this.miniFAT.buildChain(startingSector);
104+
if(sectorChain.length === 0) {
105+
return this.write(data);
106+
}
107+
const lastSectorPosition = sectorChain[sectorChain.length - 1];
108+
const lastSector = this.getMiniSectorData(lastSectorPosition);
109+
let freeBytesInLastSector = 0;
110+
let remainingBytes = data.length;
111+
if(currentSize % this.header.getMiniSectorShift() !== 0) {
112+
freeBytesInLastSector = lastSector.getSize() - currentSize % this.header.getMiniSectorShift();
113+
if(freeBytesInLastSector > 0) {
114+
const byteToWrite = Math.min(freeBytesInLastSector, data.length);
115+
lastSector.writeAt(lastSector.getSize() - freeBytesInLastSector, data.slice(0, byteToWrite));
116+
freeBytesInLastSector -= byteToWrite;
117+
remainingBytes -= byteToWrite;
118+
}
119+
}
120+
if(freeBytesInLastSector > 0 || remainingBytes === 0) {
121+
return startingSector;
122+
}
123+
const numberOfChunks = this.howManyChunksNeeded(remainingBytes);
124+
for (let i = 0; i < numberOfChunks; i++) {
125+
const bytesFromPosition = i * this.header.getMiniSectorShift();
126+
const bytesUpToPosition = Math.min((i + 1) * this.header.getMiniSectorShift(), data.length);
127+
const bytesToWrite = data.slice(bytesFromPosition, bytesUpToPosition);
128+
this.getDataHolderForNextChunk().writeAt(0, bytesToWrite);
129+
const miniSectorPosition = this.miniStreamLength / this.header.getMiniSectorShift();
130+
if(i === 0) {
131+
this.miniFAT.registerSector(miniSectorPosition, lastSectorPosition);
132+
} else {
133+
this.miniFAT.registerSector(miniSectorPosition, miniSectorPosition - 1);
134+
}
135+
this.miniStreamLength += this.header.getMiniSectorShift();
136+
}
137+
return startingSector;
138+
}
139+
140+
getDataHolderForNextChunk(): CFDataview {
141+
const currentSector = this.getSectorForNextChunk();
142+
const positionInCurrentSector = this.miniStreamLength % this.header.getSectorShift();
143+
return currentSector.subView(positionInCurrentSector, positionInCurrentSector + this.header.getMiniSectorShift());
144+
}
145+
146+
getSectorForNextChunk(): Sector {
147+
if(this.miniStreamSectorChain.length === 0) {
148+
const sector = this.sectors.allocate();
149+
this.fat.registerSector(sector.getPosition(), null);
150+
this.miniStreamSectorChain.push(sector.getPosition());
151+
return sector;
152+
} else if(this.miniStreamLength % this.header.getSectorShift() === 0) {
153+
const sector = this.sectors.allocate();
154+
this.fat.registerSector(sector.getPosition(), this.sectors.sector(this.miniStreamSectorChain[this.miniStreamSectorChain.length - 1]).getPosition());
155+
this.miniStreamSectorChain.push(sector.getPosition());
156+
return sector;
157+
} else {
158+
return this.sectors.sector(this.miniStreamSectorChain[this.miniStreamSectorChain.length - 1]);
159+
}
160+
}
161+
162+
getMiniStreamLength(): number {
163+
return this.miniStreamLength;
164+
}
165+
166+
getMiniStreamFirstSectorPosition(): number {
167+
return this.miniStreamLength <= 0 ? FREESECT_MARK_OR_NOSTREAM_INT : this.miniStreamSectorChain[0];
168+
}
169+
}

src/stream/RegularStream.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import {FAT} from "../alloc/FAT";
2+
import {Header} from "../Header";
3+
import {Sectors} from "../Sectors";
4+
import {initializedWith} from "../utils";
5+
import {VariableSizeChunkedDataView} from "../dataview/VarSizeChunkedDataview";
6+
import {StreamRW} from "./StreamRW";
7+
8+
export class RegularStreamRW implements StreamRW {
9+
10+
private readonly fat: FAT;
11+
private readonly sectors: Sectors;
12+
private readonly header: Header;
13+
14+
constructor(fat: FAT, sectors: Sectors, header: Header) {
15+
this.fat = fat;
16+
this.sectors = sectors;
17+
this.header = header;
18+
}
19+
20+
read(startingSector: number, lengthOrFromIncl: number, toExcl?: number): number[] {
21+
if(toExcl == null) {
22+
const result = initializedWith(lengthOrFromIncl, 0);
23+
let positionInResult = 0;
24+
for (const sectorPosition of this.fat.buildChain(startingSector)) {
25+
if (lengthOrFromIncl > 0) {
26+
const sector = this.sectors.sector(sectorPosition);
27+
const bytesToRead = Math.min(sector.getSize(), lengthOrFromIncl);
28+
result.splice(positionInResult, bytesToRead, ...sector.subView(0, bytesToRead).getData());
29+
positionInResult += bytesToRead;
30+
lengthOrFromIncl -= bytesToRead;
31+
} else {
32+
break;
33+
}
34+
}
35+
return result;
36+
} else {
37+
return new VariableSizeChunkedDataView(this.fat.buildChain(startingSector).map((sectorPosition) => this.sectors.sector(sectorPosition)))
38+
.subView(lengthOrFromIncl, toExcl).getData();
39+
}
40+
}
41+
42+
write(data: number[]): number {
43+
let firstSectorPosition = null;
44+
let previousSectorPosition = null;
45+
for (let i = 0; i < data.length; i+=this.header.getSectorShift()) {
46+
const sector = this.sectors.allocate();
47+
const writeBytes = Math.min(this.header.getSectorShift(), data.length - i);
48+
sector.writeAt(0, data.slice(i, i + writeBytes));
49+
const sectorPosition = sector.getPosition();
50+
this.fat.registerSector(sectorPosition, previousSectorPosition);
51+
if(firstSectorPosition == null) {
52+
firstSectorPosition = sectorPosition;
53+
}
54+
previousSectorPosition = sectorPosition;
55+
}
56+
return firstSectorPosition;
57+
}
58+
59+
writeAt(startingSector: number, position: number, data: number[]): void {
60+
new VariableSizeChunkedDataView(this.fat.buildChain(startingSector).map((pos) => this.sectors.sector(pos)))
61+
.writeAt(position, data);
62+
}
63+
64+
append(startingSector: number, currentSize: number, data: number[]): number {
65+
const sectorChain = this.fat.buildChain(startingSector);
66+
if(sectorChain.length === 0) {
67+
return this.write(data);
68+
}
69+
const lastSectorPosition = sectorChain[sectorChain.length - 1];
70+
const lastSector = this.sectors.sector(lastSectorPosition);
71+
let freeBytesInLastSector = 0;
72+
let remainingBytes = data.length;
73+
if(currentSize % this.header.getSectorShift() !== 0) {
74+
freeBytesInLastSector = lastSector.getSize() - currentSize % this.header.getSectorShift();
75+
if(freeBytesInLastSector > 0) {
76+
const byteToWrite = Math.min(freeBytesInLastSector, data.length);
77+
lastSector.writeAt(lastSector.getSize() - freeBytesInLastSector, data.slice(0, byteToWrite));
78+
freeBytesInLastSector -= byteToWrite;
79+
remainingBytes -= byteToWrite;
80+
}
81+
}
82+
if(freeBytesInLastSector > 0 || remainingBytes === 0) {
83+
return startingSector;
84+
}
85+
const numberOfChunks = this.howManyChunksNeeded(remainingBytes);
86+
let previousSectorPosition = lastSectorPosition;
87+
for (let i = 0; i < numberOfChunks; i+=this.header.getSectorShift()) {
88+
const sector = this.sectors.allocate();
89+
const writeBytes = Math.min(this.header.getSectorShift(), data.length - i);
90+
sector.writeAt(0, data.slice(i, i + writeBytes));
91+
const sectorPosition = sector.getPosition();
92+
this.fat.registerSector(sectorPosition, previousSectorPosition);
93+
previousSectorPosition = sectorPosition;
94+
}
95+
return startingSector;
96+
}
97+
98+
private howManyChunksNeeded(dataLength: number): number {
99+
let numberOfChunks;
100+
if(dataLength % this.header.getSectorShift() === 0) {
101+
numberOfChunks = Math.floor(dataLength / this.header.getSectorShift());
102+
} else {
103+
numberOfChunks = Math.floor(dataLength / this.header.getSectorShift()) + 1;
104+
}
105+
return numberOfChunks;
106+
}
107+
}

src/stream/StreamHolder.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {StreamRW} from "./StreamRW";
2+
3+
export class StreamHolder {
4+
5+
private readonly regularStreamRW: StreamRW;
6+
private readonly miniStreamRW: StreamRW;
7+
private readonly sizeThreshold: number;
8+
9+
constructor(regularStreamRW: StreamRW, miniStreamRW: StreamRW, sizeThreshold: number) {
10+
this.regularStreamRW = regularStreamRW;
11+
this.miniStreamRW = miniStreamRW;
12+
this.sizeThreshold = sizeThreshold;
13+
}
14+
15+
private forSize(size: number): StreamRW {
16+
if(size >= this.sizeThreshold) {
17+
return this.regularStreamRW;
18+
} else {
19+
return this.miniStreamRW;
20+
}
21+
}
22+
23+
24+
getStreamData(startingLocation: number, size: number): number[] {
25+
return this.forSize(size).read(startingLocation, size);
26+
}
27+
28+
setStreamData(data: number[]): number {
29+
return this.forSize(data.length).write(data);
30+
}
31+
32+
read(startingLocation: number, size: number, fromIncl: number, toExcl: number): number[] {
33+
return this.forSize(size).read(startingLocation, fromIncl, toExcl);
34+
}
35+
36+
writeAt(startingLocation: number, size: number, position: number, data: number[]): void {
37+
this.forSize(size).writeAt(startingLocation, position, data);
38+
}
39+
40+
append(startingLocation: number, size: number, data: number[]): number {
41+
if(size < this.sizeThreshold && size + data.length >= this.sizeThreshold) {
42+
const result = this.forSize(size).read(startingLocation, size);
43+
result.push(...data);
44+
return this.forSize(size + data.length).write(result);
45+
} else {
46+
return this.forSize(size).append(startingLocation, size, data);
47+
}
48+
}
49+
}

src/stream/StreamRW.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {StreamReader} from "./StreamReader";
2+
import {StreamWriter} from "./StreamWriter";
3+
4+
export interface StreamRW extends StreamReader, StreamWriter{
5+
6+
}

src/stream/StreamReader.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface StreamReader {
2+
read(startingSector: number, lengthOrFromIncl: number, toExcl?: number): number[];
3+
}

src/stream/StreamWriter.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface StreamWriter {
2+
write(data: number[]): number;
3+
writeAt(startingSector: number, position: number, data: number[]): void;
4+
append(startingSector: number, currentSize: number, data: number[]): number;
5+
}

0 commit comments

Comments
 (0)