2 changed files with 146 additions and 74 deletions
@ -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 = ''; |
||||
// Try to decode it as base64, if it fails we assume it's not base64
|
} |
||||
try { |
|
||||
bufferCharacters = window.atob(bufferCharacters); |
bufferCharacter(character: string): string { |
||||
} catch (err) { |
// If we are not currently buffering a file.
|
||||
// Assuming it's not base64...
|
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 ''; |
||||
} |
} |
||||
|
|
||||
const bytes = new Uint8Array(bufferCharacters.length); |
buffer(data: string): string { |
||||
for (let i = 0; i < bufferCharacters.length; i += 1) { |
// This is a optimization to quickly return if we know for
|
||||
bytes[i] = bufferCharacters.charCodeAt(i); |
// sure we don't need to loop over each character.
|
||||
|
if ( |
||||
|
this.fileBuffer.length === 0 && |
||||
|
this.partialFileBegin.length === 0 && |
||||
|
data.indexOf(this.fileBegin[0]) === -1 |
||||
|
) { |
||||
|
return data; |
||||
|
} |
||||
|
let newData = ''; |
||||
|
for (let i = 0; i < data.length; i += 1) { |
||||
|
newData += this.bufferCharacter(data[i]); |
||||
|
} |
||||
|
return newData; |
||||
} |
} |
||||
|
|
||||
let mimeType = 'application/octet-stream'; |
onCompleteFile(bufferCharacters: string) { |
||||
let fileExt = ''; |
this.onCompleteFileCallback(bufferCharacters); |
||||
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); |
|
||||
|
|
||||
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(); |
|
||||
} |
} |
||||
|
Loading…
Reference in new issue