@theia/process 1.48.0 → 1.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -30
- package/lib/common/process-common-module.d.ts +3 -3
- package/lib/common/process-common-module.js +22 -22
- package/lib/common/process-manager-types.d.ts +35 -35
- package/lib/common/process-manager-types.js +23 -23
- package/lib/common/shell-command-builder.d.ts +49 -49
- package/lib/common/shell-command-builder.js +169 -169
- package/lib/common/shell-command-builder.slow-spec.d.ts +9 -9
- package/lib/common/shell-command-builder.slow-spec.js +404 -404
- package/lib/common/shell-quoting.d.ts +91 -91
- package/lib/common/shell-quoting.js +145 -145
- package/lib/common/shell-quoting.spec.d.ts +1 -1
- package/lib/common/shell-quoting.spec.js +170 -170
- package/lib/node/dev-null-stream.d.ts +17 -17
- package/lib/node/dev-null-stream.js +41 -41
- package/lib/node/index.d.ts +6 -6
- package/lib/node/index.js +24 -24
- package/lib/node/multi-ring-buffer.d.ts +68 -68
- package/lib/node/multi-ring-buffer.js +299 -299
- package/lib/node/multi-ring-buffer.spec.d.ts +1 -1
- package/lib/node/multi-ring-buffer.spec.js +422 -422
- package/lib/node/process-backend-module.d.ts +3 -3
- package/lib/node/process-backend-module.js +56 -56
- package/lib/node/process-manager.d.ts +33 -33
- package/lib/node/process-manager.js +102 -102
- package/lib/node/process.d.ts +95 -95
- package/lib/node/process.js +142 -142
- package/lib/node/pseudo-pty.d.ts +22 -22
- package/lib/node/pseudo-pty.js +38 -38
- package/lib/node/raw-process.d.ts +45 -45
- package/lib/node/raw-process.js +104 -104
- package/lib/node/raw-process.spec.d.ts +1 -1
- package/lib/node/raw-process.spec.js +164 -164
- package/lib/node/task-terminal-process.d.ts +10 -10
- package/lib/node/task-terminal-process.js +42 -42
- package/lib/node/terminal-process.d.ts +60 -60
- package/lib/node/terminal-process.js +248 -248
- package/lib/node/terminal-process.spec.d.ts +1 -1
- package/lib/node/terminal-process.spec.js +103 -103
- package/lib/node/test/process-test-container.d.ts +2 -2
- package/lib/node/test/process-test-container.js +28 -28
- package/lib/node/utils.d.ts +16 -16
- package/lib/node/utils.js +77 -77
- package/package.json +4 -4
- package/src/common/process-common-module.ts +22 -22
- package/src/common/process-manager-types.ts +58 -58
- package/src/common/shell-command-builder.slow-spec.ts +486 -486
- package/src/common/shell-command-builder.ts +187 -187
- package/src/common/shell-quoting.spec.ts +176 -176
- package/src/common/shell-quoting.ts +236 -236
- package/src/common/tests/$weird(),file=name.js +1 -1
- package/src/common/tests/white space.js +1 -1
- package/src/node/dev-null-stream.ts +47 -47
- package/src/node/index.ts +22 -22
- package/src/node/multi-ring-buffer.spec.ts +486 -486
- package/src/node/multi-ring-buffer.ts +348 -348
- package/src/node/process-backend-module.ts +67 -67
- package/src/node/process-manager.ts +107 -107
- package/src/node/process.ts +207 -207
- package/src/node/pseudo-pty.ts +54 -54
- package/src/node/raw-process.spec.ts +199 -199
- package/src/node/raw-process.ts +156 -156
- package/src/node/string-argv.d.ts +21 -21
- package/src/node/task-terminal-process.ts +41 -41
- package/src/node/terminal-process.spec.ts +121 -121
- package/src/node/terminal-process.ts +290 -290
- package/src/node/test/process-fork-test.js +22 -22
- package/src/node/test/process-test-container.ts +27 -27
- package/src/node/utils.ts +79 -79
|
@@ -1,348 +1,348 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2017 Ericsson and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import * as stream from 'stream';
|
|
18
|
-
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
19
|
-
import { Disposable } from '@theia/core/lib/common';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The MultiRingBuffer is a ring buffer implementation that allows
|
|
23
|
-
* multiple independent readers.
|
|
24
|
-
*
|
|
25
|
-
* These readers are created using the getReader or getStream functions
|
|
26
|
-
* to create a reader that can be read using deq() or one that is a readable stream.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
export class MultiRingBufferReadableStream extends stream.Readable implements Disposable {
|
|
30
|
-
|
|
31
|
-
protected more = false;
|
|
32
|
-
protected disposed = false;
|
|
33
|
-
|
|
34
|
-
constructor(protected readonly ringBuffer: MultiRingBuffer,
|
|
35
|
-
protected readonly reader: number,
|
|
36
|
-
protected readonly encoding: BufferEncoding = 'utf8'
|
|
37
|
-
) {
|
|
38
|
-
super();
|
|
39
|
-
this.setEncoding(encoding);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
override _read(size: number): void {
|
|
43
|
-
this.more = true;
|
|
44
|
-
this.deq(size);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
override _destroy(err: Error | null, callback: (err: Error | null) => void): void {
|
|
48
|
-
this.ringBuffer.closeStream(this);
|
|
49
|
-
this.ringBuffer.closeReader(this.reader);
|
|
50
|
-
this.disposed = true;
|
|
51
|
-
this.removeAllListeners();
|
|
52
|
-
callback(err);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
onData(): void {
|
|
56
|
-
if (this.more === true) {
|
|
57
|
-
this.deq(-1);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
deq(size: number): void {
|
|
62
|
-
if (this.disposed === true) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let buffer = undefined;
|
|
67
|
-
do {
|
|
68
|
-
buffer = this.ringBuffer.deq(this.reader, size, this.encoding);
|
|
69
|
-
if (buffer !== undefined) {
|
|
70
|
-
this.more = this.push(buffer, this.encoding);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
while (buffer !== undefined && this.more === true && this.disposed === false);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
dispose(): void {
|
|
77
|
-
this.destroy();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export const MultiRingBufferOptions = Symbol('MultiRingBufferOptions');
|
|
82
|
-
export interface MultiRingBufferOptions {
|
|
83
|
-
readonly size: number,
|
|
84
|
-
readonly encoding?: BufferEncoding,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface WrappedPosition { newPos: number, wrap: boolean }
|
|
88
|
-
|
|
89
|
-
@injectable()
|
|
90
|
-
export class MultiRingBuffer implements Disposable {
|
|
91
|
-
|
|
92
|
-
protected readonly buffer: Buffer;
|
|
93
|
-
protected head: number = -1;
|
|
94
|
-
protected tail: number = -1;
|
|
95
|
-
protected readonly maxSize: number;
|
|
96
|
-
protected readonly encoding: BufferEncoding;
|
|
97
|
-
|
|
98
|
-
/* <id, position> */
|
|
99
|
-
protected readonly readers: Map<number, number>;
|
|
100
|
-
/* <stream : id> */
|
|
101
|
-
protected readonly streams: Map<MultiRingBufferReadableStream, number>;
|
|
102
|
-
protected readerId = 0;
|
|
103
|
-
|
|
104
|
-
constructor(
|
|
105
|
-
@inject(MultiRingBufferOptions) protected readonly options: MultiRingBufferOptions
|
|
106
|
-
) {
|
|
107
|
-
this.maxSize = options.size;
|
|
108
|
-
if (options.encoding !== undefined) {
|
|
109
|
-
this.encoding = options.encoding;
|
|
110
|
-
} else {
|
|
111
|
-
this.encoding = 'utf8';
|
|
112
|
-
}
|
|
113
|
-
this.buffer = Buffer.alloc(this.maxSize);
|
|
114
|
-
this.readers = new Map();
|
|
115
|
-
this.streams = new Map();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
enq(str: string, encoding = 'utf8'): void {
|
|
119
|
-
let buffer: Buffer = Buffer.from(str, encoding as BufferEncoding);
|
|
120
|
-
|
|
121
|
-
// Take the last elements of string if it's too big, drop the rest
|
|
122
|
-
if (buffer.length > this.maxSize) {
|
|
123
|
-
buffer = buffer.slice(buffer.length - this.maxSize);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (buffer.length === 0) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// empty
|
|
131
|
-
if (this.head === -1 && this.tail === -1) {
|
|
132
|
-
this.head = 0;
|
|
133
|
-
this.tail = 0;
|
|
134
|
-
buffer.copy(this.buffer, this.head, 0, buffer.length);
|
|
135
|
-
this.head = buffer.length - 1;
|
|
136
|
-
this.onData(0);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const startHead = this.inc(this.head, 1).newPos;
|
|
141
|
-
|
|
142
|
-
if (this.inc(startHead, buffer.length).wrap === true) {
|
|
143
|
-
buffer.copy(this.buffer, startHead, 0, this.maxSize - startHead);
|
|
144
|
-
buffer.copy(this.buffer, 0, this.maxSize - startHead);
|
|
145
|
-
} else {
|
|
146
|
-
buffer.copy(this.buffer, startHead);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
this.incTails(buffer.length);
|
|
150
|
-
this.head = this.inc(this.head, buffer.length).newPos;
|
|
151
|
-
this.onData(startHead);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
getReader(): number {
|
|
155
|
-
this.readers.set(this.readerId, this.tail);
|
|
156
|
-
return this.readerId++;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
closeReader(id: number): void {
|
|
160
|
-
this.readers.delete(id);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
getStream(encoding?: BufferEncoding): MultiRingBufferReadableStream {
|
|
164
|
-
const reader = this.getReader();
|
|
165
|
-
const readableStream = new MultiRingBufferReadableStream(this, reader, encoding);
|
|
166
|
-
this.streams.set(readableStream, reader);
|
|
167
|
-
return readableStream;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
closeStream(readableStream: MultiRingBufferReadableStream): void {
|
|
171
|
-
this.streams.delete(<MultiRingBufferReadableStream>readableStream);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
protected onData(start: number): void {
|
|
175
|
-
/* Any stream that has read everything already
|
|
176
|
-
* Should go back to the last buffer in start offset */
|
|
177
|
-
for (const [id, pos] of this.readers) {
|
|
178
|
-
if (pos === -1) {
|
|
179
|
-
this.readers.set(id, start);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
/* Notify the streams there's new data. */
|
|
183
|
-
for (const [readableStream] of this.streams) {
|
|
184
|
-
readableStream.onData();
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
deq(id: number, size = -1, encoding: BufferEncoding = 'utf8'): string | undefined {
|
|
189
|
-
const pos = this.readers.get(id);
|
|
190
|
-
if (pos === undefined || pos === -1) {
|
|
191
|
-
return undefined;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (size === 0) {
|
|
195
|
-
return undefined;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
let buffer = '';
|
|
199
|
-
const maxDeqSize = this.sizeForReader(id);
|
|
200
|
-
const wrapped = this.isWrapped(pos, this.head);
|
|
201
|
-
|
|
202
|
-
let deqSize;
|
|
203
|
-
if (size === -1) {
|
|
204
|
-
deqSize = maxDeqSize;
|
|
205
|
-
} else {
|
|
206
|
-
deqSize = Math.min(size, maxDeqSize);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (wrapped === false) { // no wrap
|
|
210
|
-
buffer = this.buffer.toString(encoding, pos, pos + deqSize);
|
|
211
|
-
} else { // wrap
|
|
212
|
-
buffer = buffer.concat(this.buffer.toString(encoding, pos, this.maxSize),
|
|
213
|
-
this.buffer.toString(encoding, 0, deqSize - (this.maxSize - pos)));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const lastIndex = this.inc(pos, deqSize - 1).newPos;
|
|
217
|
-
// everything is read
|
|
218
|
-
if (lastIndex === this.head) {
|
|
219
|
-
this.readers.set(id, -1);
|
|
220
|
-
} else {
|
|
221
|
-
this.readers.set(id, this.inc(pos, deqSize).newPos);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return buffer;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
sizeForReader(id: number): number {
|
|
228
|
-
const pos = this.readers.get(id);
|
|
229
|
-
if (pos === undefined) {
|
|
230
|
-
return 0;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return this.sizeFrom(pos, this.head, this.isWrapped(pos, this.head));
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
size(): number {
|
|
237
|
-
return this.sizeFrom(this.tail, this.head, this.isWrapped(this.tail, this.head));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
protected isWrapped(from: number, to: number): boolean {
|
|
241
|
-
if (to < from) {
|
|
242
|
-
return true;
|
|
243
|
-
} else {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
protected sizeFrom(from: number, to: number, wrap: boolean): number {
|
|
248
|
-
if (from === -1 || to === -1) {
|
|
249
|
-
return 0;
|
|
250
|
-
} else {
|
|
251
|
-
if (wrap === false) {
|
|
252
|
-
return to - from + 1;
|
|
253
|
-
} else {
|
|
254
|
-
return to + 1 + this.maxSize - from;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
emptyForReader(id: number): boolean {
|
|
260
|
-
const pos = this.readers.get(id);
|
|
261
|
-
if (pos === undefined || pos === -1) {
|
|
262
|
-
return true;
|
|
263
|
-
} else {
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
empty(): boolean {
|
|
269
|
-
if (this.head === -1 && this.tail === -1) {
|
|
270
|
-
return true;
|
|
271
|
-
} else {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
streamsSize(): number {
|
|
277
|
-
return this.streams.size;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
readersSize(): number {
|
|
281
|
-
return this.readers.size;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Dispose all the attached readers/streams.
|
|
286
|
-
*/
|
|
287
|
-
dispose(): void {
|
|
288
|
-
for (const readableStream of this.streams.keys()) {
|
|
289
|
-
readableStream.dispose();
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/* Position should be incremented if it goes pass end. */
|
|
294
|
-
protected shouldIncPos(pos: number, end: number, size: number): boolean {
|
|
295
|
-
const { newPos: newHead, wrap } = this.inc(end, size);
|
|
296
|
-
|
|
297
|
-
/* Tail Head */
|
|
298
|
-
if (this.isWrapped(pos, end) === false) {
|
|
299
|
-
// Head needs to wrap to push the tail
|
|
300
|
-
if (wrap === true && newHead >= pos) {
|
|
301
|
-
return true;
|
|
302
|
-
}
|
|
303
|
-
} else { /* Head Tail */
|
|
304
|
-
// If we wrap head is pushing tail, or if it goes over pos
|
|
305
|
-
if (wrap === true || newHead >= pos) {
|
|
306
|
-
return true;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
protected incTailSize(pos: number, head: number, size: number): WrappedPosition {
|
|
313
|
-
const { newPos: newHead } = this.inc(head, size);
|
|
314
|
-
/* New tail is 1 past newHead. */
|
|
315
|
-
return this.inc(newHead, 1);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
protected incTail(pos: number, size: number): WrappedPosition {
|
|
319
|
-
|
|
320
|
-
if (this.shouldIncPos(pos, this.head, size) === false) {
|
|
321
|
-
return { newPos: pos, wrap: false };
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return this.incTailSize(pos, this.head, size);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/* Increment the main tail and all the reader positions. */
|
|
328
|
-
protected incTails(size: number): void {
|
|
329
|
-
this.tail = this.incTail(this.tail, size).newPos;
|
|
330
|
-
|
|
331
|
-
for (const [id, pos] of this.readers) {
|
|
332
|
-
if (pos !== -1) {
|
|
333
|
-
if (this.shouldIncPos(pos, this.tail, size) === true) {
|
|
334
|
-
this.readers.set(id, this.tail);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
protected inc(pos: number, size: number): WrappedPosition {
|
|
341
|
-
if (size === 0) {
|
|
342
|
-
return { newPos: pos, wrap: false };
|
|
343
|
-
}
|
|
344
|
-
const newPos = (pos + size) % this.maxSize;
|
|
345
|
-
const wrap = newPos <= pos;
|
|
346
|
-
return { newPos, wrap };
|
|
347
|
-
}
|
|
348
|
-
}
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2017 Ericsson and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import * as stream from 'stream';
|
|
18
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
import { Disposable } from '@theia/core/lib/common';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The MultiRingBuffer is a ring buffer implementation that allows
|
|
23
|
+
* multiple independent readers.
|
|
24
|
+
*
|
|
25
|
+
* These readers are created using the getReader or getStream functions
|
|
26
|
+
* to create a reader that can be read using deq() or one that is a readable stream.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export class MultiRingBufferReadableStream extends stream.Readable implements Disposable {
|
|
30
|
+
|
|
31
|
+
protected more = false;
|
|
32
|
+
protected disposed = false;
|
|
33
|
+
|
|
34
|
+
constructor(protected readonly ringBuffer: MultiRingBuffer,
|
|
35
|
+
protected readonly reader: number,
|
|
36
|
+
protected readonly encoding: BufferEncoding = 'utf8'
|
|
37
|
+
) {
|
|
38
|
+
super();
|
|
39
|
+
this.setEncoding(encoding);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override _read(size: number): void {
|
|
43
|
+
this.more = true;
|
|
44
|
+
this.deq(size);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override _destroy(err: Error | null, callback: (err: Error | null) => void): void {
|
|
48
|
+
this.ringBuffer.closeStream(this);
|
|
49
|
+
this.ringBuffer.closeReader(this.reader);
|
|
50
|
+
this.disposed = true;
|
|
51
|
+
this.removeAllListeners();
|
|
52
|
+
callback(err);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onData(): void {
|
|
56
|
+
if (this.more === true) {
|
|
57
|
+
this.deq(-1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
deq(size: number): void {
|
|
62
|
+
if (this.disposed === true) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let buffer = undefined;
|
|
67
|
+
do {
|
|
68
|
+
buffer = this.ringBuffer.deq(this.reader, size, this.encoding);
|
|
69
|
+
if (buffer !== undefined) {
|
|
70
|
+
this.more = this.push(buffer, this.encoding);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
while (buffer !== undefined && this.more === true && this.disposed === false);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
dispose(): void {
|
|
77
|
+
this.destroy();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const MultiRingBufferOptions = Symbol('MultiRingBufferOptions');
|
|
82
|
+
export interface MultiRingBufferOptions {
|
|
83
|
+
readonly size: number,
|
|
84
|
+
readonly encoding?: BufferEncoding,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface WrappedPosition { newPos: number, wrap: boolean }
|
|
88
|
+
|
|
89
|
+
@injectable()
|
|
90
|
+
export class MultiRingBuffer implements Disposable {
|
|
91
|
+
|
|
92
|
+
protected readonly buffer: Buffer;
|
|
93
|
+
protected head: number = -1;
|
|
94
|
+
protected tail: number = -1;
|
|
95
|
+
protected readonly maxSize: number;
|
|
96
|
+
protected readonly encoding: BufferEncoding;
|
|
97
|
+
|
|
98
|
+
/* <id, position> */
|
|
99
|
+
protected readonly readers: Map<number, number>;
|
|
100
|
+
/* <stream : id> */
|
|
101
|
+
protected readonly streams: Map<MultiRingBufferReadableStream, number>;
|
|
102
|
+
protected readerId = 0;
|
|
103
|
+
|
|
104
|
+
constructor(
|
|
105
|
+
@inject(MultiRingBufferOptions) protected readonly options: MultiRingBufferOptions
|
|
106
|
+
) {
|
|
107
|
+
this.maxSize = options.size;
|
|
108
|
+
if (options.encoding !== undefined) {
|
|
109
|
+
this.encoding = options.encoding;
|
|
110
|
+
} else {
|
|
111
|
+
this.encoding = 'utf8';
|
|
112
|
+
}
|
|
113
|
+
this.buffer = Buffer.alloc(this.maxSize);
|
|
114
|
+
this.readers = new Map();
|
|
115
|
+
this.streams = new Map();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
enq(str: string, encoding = 'utf8'): void {
|
|
119
|
+
let buffer: Buffer = Buffer.from(str, encoding as BufferEncoding);
|
|
120
|
+
|
|
121
|
+
// Take the last elements of string if it's too big, drop the rest
|
|
122
|
+
if (buffer.length > this.maxSize) {
|
|
123
|
+
buffer = buffer.slice(buffer.length - this.maxSize);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (buffer.length === 0) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// empty
|
|
131
|
+
if (this.head === -1 && this.tail === -1) {
|
|
132
|
+
this.head = 0;
|
|
133
|
+
this.tail = 0;
|
|
134
|
+
buffer.copy(this.buffer, this.head, 0, buffer.length);
|
|
135
|
+
this.head = buffer.length - 1;
|
|
136
|
+
this.onData(0);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const startHead = this.inc(this.head, 1).newPos;
|
|
141
|
+
|
|
142
|
+
if (this.inc(startHead, buffer.length).wrap === true) {
|
|
143
|
+
buffer.copy(this.buffer, startHead, 0, this.maxSize - startHead);
|
|
144
|
+
buffer.copy(this.buffer, 0, this.maxSize - startHead);
|
|
145
|
+
} else {
|
|
146
|
+
buffer.copy(this.buffer, startHead);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.incTails(buffer.length);
|
|
150
|
+
this.head = this.inc(this.head, buffer.length).newPos;
|
|
151
|
+
this.onData(startHead);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getReader(): number {
|
|
155
|
+
this.readers.set(this.readerId, this.tail);
|
|
156
|
+
return this.readerId++;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
closeReader(id: number): void {
|
|
160
|
+
this.readers.delete(id);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getStream(encoding?: BufferEncoding): MultiRingBufferReadableStream {
|
|
164
|
+
const reader = this.getReader();
|
|
165
|
+
const readableStream = new MultiRingBufferReadableStream(this, reader, encoding);
|
|
166
|
+
this.streams.set(readableStream, reader);
|
|
167
|
+
return readableStream;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
closeStream(readableStream: MultiRingBufferReadableStream): void {
|
|
171
|
+
this.streams.delete(<MultiRingBufferReadableStream>readableStream);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
protected onData(start: number): void {
|
|
175
|
+
/* Any stream that has read everything already
|
|
176
|
+
* Should go back to the last buffer in start offset */
|
|
177
|
+
for (const [id, pos] of this.readers) {
|
|
178
|
+
if (pos === -1) {
|
|
179
|
+
this.readers.set(id, start);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/* Notify the streams there's new data. */
|
|
183
|
+
for (const [readableStream] of this.streams) {
|
|
184
|
+
readableStream.onData();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
deq(id: number, size = -1, encoding: BufferEncoding = 'utf8'): string | undefined {
|
|
189
|
+
const pos = this.readers.get(id);
|
|
190
|
+
if (pos === undefined || pos === -1) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (size === 0) {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let buffer = '';
|
|
199
|
+
const maxDeqSize = this.sizeForReader(id);
|
|
200
|
+
const wrapped = this.isWrapped(pos, this.head);
|
|
201
|
+
|
|
202
|
+
let deqSize;
|
|
203
|
+
if (size === -1) {
|
|
204
|
+
deqSize = maxDeqSize;
|
|
205
|
+
} else {
|
|
206
|
+
deqSize = Math.min(size, maxDeqSize);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (wrapped === false) { // no wrap
|
|
210
|
+
buffer = this.buffer.toString(encoding, pos, pos + deqSize);
|
|
211
|
+
} else { // wrap
|
|
212
|
+
buffer = buffer.concat(this.buffer.toString(encoding, pos, this.maxSize),
|
|
213
|
+
this.buffer.toString(encoding, 0, deqSize - (this.maxSize - pos)));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const lastIndex = this.inc(pos, deqSize - 1).newPos;
|
|
217
|
+
// everything is read
|
|
218
|
+
if (lastIndex === this.head) {
|
|
219
|
+
this.readers.set(id, -1);
|
|
220
|
+
} else {
|
|
221
|
+
this.readers.set(id, this.inc(pos, deqSize).newPos);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return buffer;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
sizeForReader(id: number): number {
|
|
228
|
+
const pos = this.readers.get(id);
|
|
229
|
+
if (pos === undefined) {
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return this.sizeFrom(pos, this.head, this.isWrapped(pos, this.head));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
size(): number {
|
|
237
|
+
return this.sizeFrom(this.tail, this.head, this.isWrapped(this.tail, this.head));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
protected isWrapped(from: number, to: number): boolean {
|
|
241
|
+
if (to < from) {
|
|
242
|
+
return true;
|
|
243
|
+
} else {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
protected sizeFrom(from: number, to: number, wrap: boolean): number {
|
|
248
|
+
if (from === -1 || to === -1) {
|
|
249
|
+
return 0;
|
|
250
|
+
} else {
|
|
251
|
+
if (wrap === false) {
|
|
252
|
+
return to - from + 1;
|
|
253
|
+
} else {
|
|
254
|
+
return to + 1 + this.maxSize - from;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
emptyForReader(id: number): boolean {
|
|
260
|
+
const pos = this.readers.get(id);
|
|
261
|
+
if (pos === undefined || pos === -1) {
|
|
262
|
+
return true;
|
|
263
|
+
} else {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
empty(): boolean {
|
|
269
|
+
if (this.head === -1 && this.tail === -1) {
|
|
270
|
+
return true;
|
|
271
|
+
} else {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
streamsSize(): number {
|
|
277
|
+
return this.streams.size;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
readersSize(): number {
|
|
281
|
+
return this.readers.size;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Dispose all the attached readers/streams.
|
|
286
|
+
*/
|
|
287
|
+
dispose(): void {
|
|
288
|
+
for (const readableStream of this.streams.keys()) {
|
|
289
|
+
readableStream.dispose();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* Position should be incremented if it goes pass end. */
|
|
294
|
+
protected shouldIncPos(pos: number, end: number, size: number): boolean {
|
|
295
|
+
const { newPos: newHead, wrap } = this.inc(end, size);
|
|
296
|
+
|
|
297
|
+
/* Tail Head */
|
|
298
|
+
if (this.isWrapped(pos, end) === false) {
|
|
299
|
+
// Head needs to wrap to push the tail
|
|
300
|
+
if (wrap === true && newHead >= pos) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
} else { /* Head Tail */
|
|
304
|
+
// If we wrap head is pushing tail, or if it goes over pos
|
|
305
|
+
if (wrap === true || newHead >= pos) {
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
protected incTailSize(pos: number, head: number, size: number): WrappedPosition {
|
|
313
|
+
const { newPos: newHead } = this.inc(head, size);
|
|
314
|
+
/* New tail is 1 past newHead. */
|
|
315
|
+
return this.inc(newHead, 1);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
protected incTail(pos: number, size: number): WrappedPosition {
|
|
319
|
+
|
|
320
|
+
if (this.shouldIncPos(pos, this.head, size) === false) {
|
|
321
|
+
return { newPos: pos, wrap: false };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return this.incTailSize(pos, this.head, size);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* Increment the main tail and all the reader positions. */
|
|
328
|
+
protected incTails(size: number): void {
|
|
329
|
+
this.tail = this.incTail(this.tail, size).newPos;
|
|
330
|
+
|
|
331
|
+
for (const [id, pos] of this.readers) {
|
|
332
|
+
if (pos !== -1) {
|
|
333
|
+
if (this.shouldIncPos(pos, this.tail, size) === true) {
|
|
334
|
+
this.readers.set(id, this.tail);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
protected inc(pos: number, size: number): WrappedPosition {
|
|
341
|
+
if (size === 0) {
|
|
342
|
+
return { newPos: pos, wrap: false };
|
|
343
|
+
}
|
|
344
|
+
const newPos = (pos + size) % this.maxSize;
|
|
345
|
+
const wrap = newPos <= pos;
|
|
346
|
+
return { newPos, wrap };
|
|
347
|
+
}
|
|
348
|
+
}
|