Initial commit

This commit is contained in:
NI
2019-08-07 15:56:51 +08:00
commit 02f14eb14f
206 changed files with 38863 additions and 0 deletions

570
ui/stream/reader.js Normal file
View File

@@ -0,0 +1,570 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import Exception from "./exception.js";
import * as subscribe from "./subscribe.js";
export class Buffer {
/**
* constructor
*
* @param {Uint8Array} buffer Array buffer
* @param {function} depleted Callback that will be called when the buffer
* is depleted
*/
constructor(buffer, depleted) {
this.buffer = buffer;
this.used = 0;
this.onDepleted = depleted;
}
/**
* Return the index of given byte inside current available (unused) read
* buffer
*
* @param {number} byteData Target data
* @param {number} maxLen Max search length
*
* @returns {number} Return number >= 0 when found, -1 when not
*/
searchBuffer(byteData, maxLen) {
let searchLen = this.remains();
if (searchLen > maxLen) {
searchLen = maxLen;
}
for (let i = 0; i < searchLen; i++) {
if (this.buffer[i + this.used] !== byteData) {
continue;
}
return i;
}
return -1;
}
/**
* Return the index of given byte inside current available (unused) read
* buffer
*
* @param {number} byteData Target data
*
* @returns {number} Return number >= 0 when found, -1 when not
*/
indexOf(byteData) {
return this.searchBuffer(byteData, this.remains());
}
/**
* Return how many bytes in the source + buffer is still available to be
* read, return 0 when reader is depleted and thus can be ditched
*
* @returns {number} Remaining size
*
*/
remains() {
return this.buffer.length - this.used;
}
/**
* Return how many bytes is still availale in the buffer.
*
* Note: This reader don't have renewable data source, so when buffer
* depletes, the reader is done
*
* @returns {number} Remaining size
*
*/
buffered() {
return this.remains();
}
/**
* Export max n bytes from current buffer
*
* @param {number} n suggested max byte length, set to 0 to refresh buffer
* if current buffer is deplated
*
* @returns {Uint8Array} Exported data
*
* @throws {Exception} When reader has been depleted
*
*/
export(n) {
let remain = this.remains();
if (remain <= 0) {
throw new Exception("Reader has been depleted", false);
}
if (remain > n) {
remain = n;
}
let exported = this.buffer.slice(this.used, this.used + remain);
this.used += exported.length;
if (this.remains() <= 0) {
this.onDepleted();
}
return exported;
}
}
export class Multiple {
/**
* Constructor
*
* @param {function} depleted Callback will be called when all reader is
* depleted
*
*/
constructor(depleted) {
this.reader = null;
this.depleted = depleted;
this.subscribe = new subscribe.Subscribe();
this.closed = false;
}
/**
* Add new reader as sub reader
*
* @param {Buffer} reader
* @param {function} depleted Callback that will be called when given reader
* is depleted
*
* @throws {Exception} When the reader is closed
*
*/
feed(reader, depleted) {
if (this.closed) {
throw new Exception("Reader is closed", false);
}
if (this.reader === null && this.subscribe.pendings() <= 0) {
this.reader = {
reader: reader,
depleted: depleted
};
return;
}
this.subscribe.resolve({
reader: reader,
depleted: depleted
});
}
/**
* Return the index of given byte inside current available (unused) read
* buffer
*
* @param {number} byteData Target data
* @param {number} maxLen Max search length
*
* @returns {number} Return number >= 0 when found, -1 when not
*
*/
searchBuffer(byteData, maxLen) {
if (this.reader === null) {
return -1;
}
return this.reader.reader.searchBuffer(byteData, maxLen);
}
/**
* Return the index of given byte inside current available (unused) read
* buffer
*
* @param {number} byteData Target data
*
* @returns {number} Return number >= 0 when found, -1 when not
*/
indexOf(byteData) {
return this.searchBuffer(byteData, this.buffered());
}
/**
* Return how many bytes still available in the buffer (How many bytes of
* buffer is left for read before reloading from data source)
*
* @returns {number} How many bytes left in the current buffer
*/
buffered() {
if (this.reader == null) {
return 0;
}
return this.reader.reader.buffered();
}
/**
* close current reading
*
*/
close() {
if (this.closed) {
return;
}
this.closed = true;
this.subscribe.reject(new Exception("Reader is closed", false));
}
/**
* Export max n bytes from current buffer
*
* @param {number} n suggested max byte length, set to 0 to refresh buffer
* if current buffer is deplated
*
* @returns {Uint8Array} Exported data
*
*/
async export(n) {
for (;;) {
if (this.reader !== null) {
let exported = await this.reader.reader.export(n);
if (this.reader.reader.remains() <= 0) {
this.reader.depleted();
this.reader = null;
}
return exported;
}
this.depleted(this);
this.reader = await this.subscribe.subscribe();
}
}
}
export class Reader {
/**
* constructor
*
* @param {Multiple} multiple Source reader
* @param {function} bufferConverter Function convert
*
*/
constructor(multiple, bufferConverter) {
this.multiple = multiple;
this.buffers = new subscribe.Subscribe();
this.bufferConverter =
bufferConverter ||
(d => {
return d;
});
this.closed = false;
}
/**
* Add buffer into current reader
*
* @param {Uint8Array} buffer buffer to add
*
* @throws {Exception} When the reader is closed
*
*/
feed(buffer) {
if (this.closed) {
throw new Exception("Reader is closed, new data has been deined", false);
}
this.buffers.resolve(buffer);
}
async reader() {
if (this.closed) {
throw new Exception("Reader is closed, unable to read", false);
}
if (this.multiple.buffered() > 0) {
return this.multiple;
}
let self = this,
converted = await this.bufferConverter(await self.buffers.subscribe());
this.multiple.feed(new Buffer(converted, () => {}), () => {});
return this.multiple;
}
/**
* close current reading
*
*/
close() {
if (this.closed) {
return;
}
this.closed = true;
this.buffers.reject(
new Exception(
"Reader is closed, and thus " + "cannot be operated on",
false
)
);
return this.multiple.close();
}
/**
* Return the index of given byte inside current available (unused) read
* buffer
*
* @param {number} byteData Target data
* @param {number} maxLen Max search length
*
* @returns {number} Return number >= 0 when found, -1 when not
*/
async searchBuffer(byteData, maxLen) {
return (await this.reader()).searchBuffer(byteData, maxLen);
}
/**
* Return the index of given byte inside current available (unused) read
* buffer
*
* @param {number} byteData Target data
*
* @returns {number} Return number >= 0 when found, -1 when not
*/
async indexOf(byteData) {
return (await this.reader()).indexOf(byteData);
}
/**
* Return how many bytes still available in the buffer (How many bytes of
* buffer is left for read before reloading from data source)
*
* @returns {number} How many bytes left in the current buffer
*/
async buffered() {
return (await this.reader()).buffered();
}
/**
* Export max n bytes from current buffer
*
* @param {number} n suggested max byte length, set to 0 to refresh buffer
* if current buffer is deplated
*
* @returns {Uint8Array} Exported data
*
*/
async export(n) {
return (await this.reader()).export(n);
}
}
/**
* Read exactly one bytes from the reader
*
* @param {Reader} reader the source reader
*
* @returns {Uint8Array} Exported data
*
*/
export async function readOne(reader) {
for (;;) {
let d = await reader.export(1);
if (d.length <= 0) {
continue;
}
return d;
}
}
/**
* Read exactly n bytes from the reader
*
* @param {Reader} reader the source reader
* @param {number} n length to read
*
* @returns {Uint8Array} Exported data
*
*/
export async function readN(reader, n) {
let readed = 0,
result = new Uint8Array(n);
while (readed < n) {
let exported = await reader.export(n - readed);
result.set(exported, readed);
readed += exported.length;
}
return result;
}
export class Limited {
/**
* Constructor
*
* @param {Reader} reader the source reader
* @param {number} maxN max bytes to read
*
* @returns {boolean} true when the reader is completed, false otherwise
*
*/
constructor(reader, maxN) {
this.reader = reader;
this.remain = maxN;
}
/**
* Indicate whether or not the current reader is completed
*
* @returns {boolean} true when the reader is completed, false otherwise
*
*/
completed() {
return this.remain <= 0;
}
/**
* Return the index of given byte inside current available (unused) read
* buffer
*
* @param {number} byteData Target data
* @param {number} maxLen Max search length
*
* @returns {number} Return number >= 0 when found, -1 when not
*
*/
searchBuffer(byteData, maxLen) {
return this.reader.searchBuffer(
byteData,
maxLen > this.remain ? this.remain : maxLen
);
}
/**
* Return the index of given byte inside current read buffer
*
* @param {number} byteData Target data
*
* @returns {number} Return number >= 0 when found, -1 when not
*/
indexOf(byteData) {
return this.reader.searchBuffer(byteData, this.remain);
}
/**
* Return how many bytes still available to be read
*
* @returns {number} Remaining size
*
*/
remains() {
return this.remain;
}
/**
* Return how many bytes still available in the buffer (How many bytes of
* buffer is left for read before reloading from data source)
*
* @returns {number} Remaining size
*
*/
buffered() {
let buf = this.reader.buffered();
return buf > this.remain ? this.remain : buf;
}
/**
* Export max n bytes from current buffer
*
* @param {number} n suggested max length
*
* @throws {Exception} when reading already completed
*
* @returns {Uint8Array} Exported data
*
*/
async export(n) {
if (this.completed()) {
throw new Exception("Reader already completed", false);
}
let toRead = n > this.remain ? this.remain : n,
exported = await this.reader.export(toRead);
this.remain -= exported.length;
return exported;
}
}
/**
* Read the whole Limited reader and return the result
*
* @param {Limited} limited the Limited reader
*
* @returns {Uint8Array} Exported data
*
*/
export async function readCompletely(limited) {
return await readN(limited, limited.remains());
}
/**
* Read until given byteData is reached. This function is guaranteed to spit
* out at least one byte
*
* @param {Reader} indexOfReader
* @param {number} byteData
*/
export async function readUntil(indexOfReader, byteData) {
let pos = await indexOfReader.indexOf(byteData),
buffered = await indexOfReader.buffered();
if (pos >= 0) {
return {
data: await readN(indexOfReader, pos + 1),
found: true
};
}
if (buffered <= 0) {
let d = await readOne(indexOfReader);
return {
data: d,
found: d[0] === byteData
};
}
return {
data: await readN(indexOfReader, buffered),
found: false
};
}