Browse Source

buffer files on a character basis

pull/217/head
Ben Letchford 5 years ago
parent
commit
e81966cd5c
  1. 149
      src/client/download.ts
  2. 77
      src/client/index.ts

149
src/client/download.ts

@ -1,55 +1,100 @@
import * as fileType from 'file-type'; const DEFAULT_FILE_BEGIN = '\u001b[5i';
import Toastify from 'toastify-js'; const DEFAULT_FILE_END = '\u001b[4i';
export const FILE_BEGIN = '\u001b[5i'; export class FileDownloader {
export const FILE_END = '\u001b[4i'; constructor(
export let fileBuffer = []; onCompleteFileCallback: (file: string) => any,
fileBegin: string = DEFAULT_FILE_BEGIN,
export function onCompleteFile() { fileEnd: string = DEFAULT_FILE_END
let bufferCharacters = fileBuffer.join(''); ) {
bufferCharacters = bufferCharacters.substring( this.fileBuffer = [];
bufferCharacters.lastIndexOf(FILE_BEGIN) + FILE_BEGIN.length, this.onCompleteFileCallback = onCompleteFileCallback;
bufferCharacters.lastIndexOf(FILE_END) this.fileBegin = fileBegin;
this.fileEnd = fileEnd;
this.partialFileBegin = '';
}
bufferCharacter(character: string): string {
// If we are not currently buffering a file.
if (this.fileBuffer.length === 0) {
// If we are not currently expecting the rest of the fileBegin sequences.
if (this.partialFileBegin.length === 0) {
// If the character is the first character of fileBegin we know to start
// expecting the rest of the fileBegin sequence.
if (character === this.fileBegin[0]) {
this.partialFileBegin = character;
return '';
}
// Otherwise, we just return the character for printing to the terminal.
return character;
}
// We're currently in the state of buffering a beginner marker...
const nextExpectedCharacter = this.fileBegin[
this.partialFileBegin.length
];
// If the next character *is* the next character in the fileBegin sequence.
if (character === nextExpectedCharacter) {
this.partialFileBegin += character;
// Do we now have the complete fileBegin sequence.
if (this.partialFileBegin === this.fileBegin) {
this.partialFileBegin = '';
this.fileBuffer = this.fileBuffer.concat(this.fileBegin.split(''));
return '';
}
// Otherwise, we just wait until the next character.
return '';
}
// If the next expected character wasn't found for the fileBegin sequence,
// we need to return all the data that was bufferd in the partialFileBegin
// back to the terminal.
const dataToReturn = this.partialFileBegin + character;
this.partialFileBegin = '';
return dataToReturn;
}
// If we are currently in the state of buffering a file.
this.fileBuffer.push(character);
// If we now have an entire fileEnd marker, we have a complete file!
if (
this.fileBuffer.length >= this.fileBegin.length + this.fileEnd.length &&
this.fileBuffer.slice(-this.fileEnd.length).join('') === this.fileEnd
) {
this.onCompleteFile(
this.fileBuffer
.slice(
this.fileBegin.length,
this.fileBuffer.length - this.fileEnd.length
)
.join('')
); );
this.fileBuffer = [];
}
return '';
}
// Try to decode it as base64, if it fails we assume it's not base64 buffer(data: string): string {
try { // This is a optimization to quickly return if we know for
bufferCharacters = window.atob(bufferCharacters); // sure we don't need to loop over each character.
} catch (err) { if (
// Assuming it's not base64... this.fileBuffer.length === 0 &&
} this.partialFileBegin.length === 0 &&
data.indexOf(this.fileBegin[0]) === -1
const bytes = new Uint8Array(bufferCharacters.length); ) {
for (let i = 0; i < bufferCharacters.length; i += 1) { return data;
bytes[i] = bufferCharacters.charCodeAt(i); }
} let newData = '';
for (let i = 0; i < data.length; i += 1) {
let mimeType = 'application/octet-stream'; newData += this.bufferCharacter(data[i]);
let fileExt = ''; }
const typeData = fileType(bytes); return newData;
if (typeData) { }
mimeType = typeData.mime;
fileExt = typeData.ext; onCompleteFile(bufferCharacters: string) {
} this.onCompleteFileCallback(bufferCharacters);
const fileName = `file-${new Date() }
.toISOString()
.split('.')[0]
.replace(/-/g, '')
.replace('T', '')
.replace(/:/g, '')}${fileExt ? `.${fileExt}` : ''}`;
const blob = new Blob([new Uint8Array(bytes.buffer)], { type: mimeType });
const blobUrl = URL.createObjectURL(blob);
fileBuffer = [];
Toastify({
text: `Download ready: <a href="${blobUrl}" target="_blank" download="${fileName}">${fileName}</a>`,
duration: 10000,
newWindow: true,
gravity: 'bottom',
position: 'right',
backgroundColor: '#fff',
stopOnFocus: true,
}).showToast();
} }

77
src/client/index.ts

@ -3,9 +3,11 @@ import { isNull } from 'lodash';
import { dom, library } from "@fortawesome/fontawesome-svg-core"; import { dom, library } from "@fortawesome/fontawesome-svg-core";
import { faCogs } from "@fortawesome/free-solid-svg-icons/faCogs"; import { faCogs } from "@fortawesome/free-solid-svg-icons/faCogs";
import Toastify from 'toastify-js';
import * as fileType from 'file-type';
import { socket } from './socket'; import { socket } from './socket';
import { overlay, terminal } from './elements'; import { overlay, terminal } from './elements';
import { FILE_BEGIN, FILE_END, fileBuffer, onCompleteFile } from './download'; import { FileDownloader } from './download';
import verifyPrompt from './verify'; import verifyPrompt from './verify';
import disconnect from './disconnect'; import disconnect from './disconnect';
import mobileKeyboard from './mobile'; import mobileKeyboard from './mobile';
@ -76,6 +78,52 @@ socket.on('connect', () => {
term.focus(); term.focus();
mobileKeyboard(); mobileKeyboard();
const fileDownloader = new FileDownloader(function (
bufferCharacters: string
) {
let fileCharacters = bufferCharacters;
// Try to decode it as base64, if it fails we assume it's not base64
try {
fileCharacters = window.atob(fileCharacters);
} catch (err) {
// Assuming it's not base64...
}
const bytes = new Uint8Array(fileCharacters.length);
for (let i = 0; i < fileCharacters.length; i += 1) {
bytes[i] = fileCharacters.charCodeAt(i);
}
let mimeType = 'application/octet-stream';
let fileExt = '';
const typeData = fileType(bytes);
if (typeData) {
mimeType = typeData.mime;
fileExt = typeData.ext;
}
const fileName = `file-${new Date()
.toISOString()
.split('.')[0]
.replace(/-/g, '')
.replace('T', '')
.replace(/:/g, '')}${fileExt ? `.${fileExt}` : ''}`;
const blob = new Blob([new Uint8Array(bytes.buffer)], {
type: mimeType,
});
const blobUrl = URL.createObjectURL(blob);
Toastify({
text: `Download ready: <a href="${blobUrl}" target="_blank" download="${fileName}">${fileName}</a>`,
duration: 10000,
newWindow: true,
gravity: 'bottom',
position: 'right',
backgroundColor: '#fff',
stopOnFocus: true,
}).showToast();
});
term.on('data', data => { term.on('data', data => {
socket.emit('input', data); socket.emit('input', data);
}); });
@ -84,30 +132,9 @@ socket.on('connect', () => {
}); });
socket socket
.on('data', (data: string) => { .on('data', (data: string) => {
const indexOfFileBegin = data.indexOf(FILE_BEGIN); const remainingData = fileDownloader.buffer(data);
const indexOfFileEnd = data.indexOf(FILE_END); if (remainingData) {
term.write(remainingData);
// If we've got the entire file in one chunk
if (indexOfFileBegin !== -1 && indexOfFileEnd !== -1) {
fileBuffer.push(data);
onCompleteFile();
}
// If we've found a beginning marker
else if (indexOfFileBegin !== -1) {
fileBuffer.push(data);
}
// If we've found an ending marker
else if (indexOfFileEnd !== -1) {
fileBuffer.push(data);
onCompleteFile();
}
// If we've found the continuation of a file
else if (fileBuffer.length > 0) {
fileBuffer.push(data);
}
// Just treat it as normal data
else {
term.write(data);
} }
}) })
.on('login', () => { .on('login', () => {

Loading…
Cancel
Save