mirror of
https://github.com/ducbao414/win32.run.git
synced 2025-12-18 02:02:50 +09:00
init the awkward code
This commit is contained in:
35
static/js/libarchive.js/src/compressed-file.js
Normal file
35
static/js/libarchive.js/src/compressed-file.js
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
/**
|
||||
* Represents compressed file before extraction
|
||||
*/
|
||||
export class CompressedFile{
|
||||
|
||||
constructor(name,size,path,archiveRef){
|
||||
this._name = name;
|
||||
this._size = size;
|
||||
this._path = path;
|
||||
this._archiveRef = archiveRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* file name
|
||||
*/
|
||||
get name(){
|
||||
return this._name;
|
||||
}
|
||||
/**
|
||||
* file size
|
||||
*/
|
||||
get size(){
|
||||
return this._size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract file from archive
|
||||
* @returns {Promise<File>} extracted file
|
||||
*/
|
||||
extract(){
|
||||
return this._archiveRef.extractSingleFile(this._path);
|
||||
}
|
||||
|
||||
}
|
||||
225
static/js/libarchive.js/src/libarchive.js
Normal file
225
static/js/libarchive.js/src/libarchive.js
Normal file
@@ -0,0 +1,225 @@
|
||||
import { CompressedFile } from "./compressed-file.js";
|
||||
|
||||
|
||||
export class Archive{
|
||||
|
||||
/**
|
||||
* Initialize libarchivejs
|
||||
* @param {Object} options
|
||||
*/
|
||||
static init(options = {}){
|
||||
Archive._options = {
|
||||
workerUrl: '../dist/worker-bundle.js',
|
||||
...options
|
||||
};
|
||||
return Archive._options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new archive instance from browser native File object
|
||||
* @param {File} file
|
||||
* @param {object} options
|
||||
* @returns {Archive}
|
||||
*/
|
||||
static open(file, options = null){
|
||||
options = options ||
|
||||
Archive._options ||
|
||||
Archive.init() && console.warn('Automatically initializing using options: ', Archive._options);
|
||||
const arch = new Archive(file,options);
|
||||
return arch.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new archive
|
||||
* @param {File} file
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(file,options){
|
||||
this._worker = new Worker(options.workerUrl);
|
||||
this._worker.addEventListener('message', this._workerMsg.bind(this));
|
||||
this._callbacks = [];
|
||||
this._content = {};
|
||||
this._processed = 0;
|
||||
this._file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares file for reading
|
||||
* @returns {Promise<Archive>} archive instance
|
||||
*/
|
||||
async open(){
|
||||
await this._postMessage({type: 'HELLO'},(resolve,reject,msg) => {
|
||||
if( msg.type === 'READY' ){
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
return await this._postMessage({type: 'OPEN', file: this._file}, (resolve,reject,msg) => {
|
||||
if(msg.type === 'OPENED'){
|
||||
resolve(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* detect if archive has encrypted data
|
||||
* @returns {boolean|null} null if could not be determined
|
||||
*/
|
||||
hasEncryptedData(){
|
||||
return this._postMessage({type: 'CHECK_ENCRYPTION'},
|
||||
(resolve,reject,msg) => {
|
||||
if( msg.type === 'ENCRYPTION_STATUS' ){
|
||||
resolve(msg.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* set password to be used when reading archive
|
||||
*/
|
||||
usePassword(archivePassword){
|
||||
return this._postMessage({type: 'SET_PASSPHRASE', passphrase: archivePassword},
|
||||
(resolve,reject,msg) => {
|
||||
if( msg.type === 'PASSPHRASE_STATUS' ){
|
||||
resolve(msg.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns object containing directory structure and file information
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
getFilesObject(){
|
||||
if( this._processed > 0 ){
|
||||
return Promise.resolve().then( () => this._content );
|
||||
}
|
||||
return this._postMessage({type: 'LIST_FILES'}, (resolve,reject,msg) => {
|
||||
if( msg.type === 'ENTRY' ){
|
||||
const entry = msg.entry;
|
||||
const [ target, prop ] = this._getProp(this._content,entry.path);
|
||||
if( entry.type === 'FILE' ){
|
||||
target[prop] = new CompressedFile(entry.fileName,entry.size,entry.path,this);
|
||||
}
|
||||
return true;
|
||||
}else if( msg.type === 'END' ){
|
||||
this._processed = 1;
|
||||
resolve(this._cloneContent(this._content));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getFilesArray(){
|
||||
return this.getFilesObject().then( (obj) => {
|
||||
return this._objectToArray(obj);
|
||||
});
|
||||
}
|
||||
|
||||
extractSingleFile(target){
|
||||
return this._postMessage({type: 'EXTRACT_SINGLE_FILE', target: target},
|
||||
(resolve,reject,msg) => {
|
||||
if( msg.type === 'FILE' ){
|
||||
const file = new File([msg.entry.fileData], msg.entry.fileName, {
|
||||
type: 'application/octet-stream'
|
||||
});
|
||||
resolve(file);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns object containing directory structure and extracted File objects
|
||||
* @param {Function} extractCallback
|
||||
*
|
||||
*/
|
||||
extractFiles(extractCallback){
|
||||
if( this._processed > 1 ){
|
||||
return Promise.resolve().then( () => this._content );
|
||||
}
|
||||
return this._postMessage({type: 'EXTRACT_FILES'}, (resolve,reject,msg) => {
|
||||
if( msg.type === 'ENTRY' ){
|
||||
const [ target, prop ] = this._getProp(this._content,msg.entry.path);
|
||||
if( msg.entry.type === 'FILE' ){
|
||||
target[prop] = new File([msg.entry.fileData], msg.entry.fileName, {
|
||||
type: 'application/octet-stream'
|
||||
});
|
||||
if (extractCallback !== undefined) {
|
||||
setTimeout(extractCallback.bind(null,{
|
||||
file: target[prop],
|
||||
path: msg.entry.path,
|
||||
}));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}else if( msg.type === 'END' ){
|
||||
this._processed = 2;
|
||||
this._worker.terminate();
|
||||
resolve(this._cloneContent(this._content));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_cloneContent(obj){
|
||||
if( obj instanceof File || obj instanceof CompressedFile || obj === null ) return obj;
|
||||
const o = {};
|
||||
for( const prop of Object.keys(obj) ){
|
||||
o[prop] = this._cloneContent(obj[prop]);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
_objectToArray(obj,path = ''){
|
||||
const files = [];
|
||||
for( const key of Object.keys(obj) ){
|
||||
if( obj[key] instanceof File || obj[key] instanceof CompressedFile || obj[key] === null ){
|
||||
files.push({
|
||||
file: obj[key] || key,
|
||||
path: path
|
||||
});
|
||||
}else{
|
||||
files.push( ...this._objectToArray(obj[key],`${path}${key}/`) );
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
_getProp(obj,path){
|
||||
const parts = path.split('/');
|
||||
if( parts[parts.length -1] === '' ) parts.pop();
|
||||
let cur = obj, prev = null;
|
||||
for( const part of parts ){
|
||||
cur[part] = cur[part] || {};
|
||||
prev = cur;
|
||||
cur = cur[part];
|
||||
}
|
||||
return [ prev, parts[parts.length-1] ];
|
||||
}
|
||||
|
||||
_postMessage(msg,callback){
|
||||
this._worker.postMessage(msg);
|
||||
return new Promise((resolve,reject) => {
|
||||
this._callbacks.push( this._msgHandler.bind(this,callback,resolve,reject) );
|
||||
});
|
||||
}
|
||||
|
||||
_msgHandler(callback,resolve,reject,msg){
|
||||
if( msg.type === 'BUSY' ){
|
||||
reject('worker is busy');
|
||||
}else if( msg.type === 'ERROR' ){
|
||||
reject(msg.error);
|
||||
}else{
|
||||
return callback(resolve,reject,msg);
|
||||
}
|
||||
}
|
||||
|
||||
_workerMsg({data: msg}){
|
||||
const callback = this._callbacks[this._callbacks.length -1];
|
||||
const next = callback(msg);
|
||||
if( !next ){
|
||||
this._callbacks.pop();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
136
static/js/libarchive.js/src/webworker/archive-reader.js
Normal file
136
static/js/libarchive.js/src/webworker/archive-reader.js
Normal file
@@ -0,0 +1,136 @@
|
||||
|
||||
const TYPE_MAP = {
|
||||
32768: 'FILE',
|
||||
16384: 'DIR',
|
||||
40960: 'SYMBOLIC_LINK',
|
||||
49152: 'SOCKET',
|
||||
8192: 'CHARACTER_DEVICE',
|
||||
24576: 'BLOCK_DEVICE',
|
||||
4096: 'NAMED_PIPE',
|
||||
};
|
||||
|
||||
export class ArchiveReader{
|
||||
/**
|
||||
* archive reader
|
||||
* @param {WasmModule} wasmModule emscripten module
|
||||
*/
|
||||
constructor(wasmModule){
|
||||
this._wasmModule = wasmModule;
|
||||
this._runCode = wasmModule.runCode;
|
||||
this._file = null;
|
||||
this._passphrase = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* open archive, needs to closed manually
|
||||
* @param {File} file
|
||||
*/
|
||||
open(file){
|
||||
if( this._file !== null ){
|
||||
console.warn('Closing previous file');
|
||||
this.close();
|
||||
}
|
||||
const { promise, resolve, reject } = this._promiseHandles();
|
||||
this._file = file;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => this._loadFile(reader.result,resolve,reject);
|
||||
reader.readAsArrayBuffer(file);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* close archive
|
||||
*/
|
||||
close(){
|
||||
this._runCode.closeArchive(this._archive);
|
||||
this._wasmModule._free(this._filePtr);
|
||||
this._file = null;
|
||||
this._filePtr = null;
|
||||
this._archive = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* detect if archive has encrypted data
|
||||
* @returns {boolean|null} null if could not be determined
|
||||
*/
|
||||
hasEncryptedData(){
|
||||
this._archive = this._runCode.openArchive( this._filePtr, this._fileLength, this._passphrase );
|
||||
this._runCode.getNextEntry(this._archive);
|
||||
const status = this._runCode.hasEncryptedEntries(this._archive);
|
||||
if( status === 0 ){
|
||||
return false;
|
||||
} else if( status > 0 ){
|
||||
return true;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set passphrase to be used with archive
|
||||
* @param {*} passphrase
|
||||
*/
|
||||
setPassphrase(passphrase){
|
||||
this._passphrase = passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* get archive entries
|
||||
* @param {boolean} skipExtraction
|
||||
* @param {string} except don't skip this entry
|
||||
*/
|
||||
*entries(skipExtraction = false, except = null){
|
||||
this._archive = this._runCode.openArchive( this._filePtr, this._fileLength, this._passphrase );
|
||||
let entry;
|
||||
while( true ){
|
||||
entry = this._runCode.getNextEntry(this._archive);
|
||||
if( entry === 0 ) break;
|
||||
|
||||
const entryData = {
|
||||
size: this._runCode.getEntrySize(entry),
|
||||
path: this._runCode.getEntryName(entry),
|
||||
type: TYPE_MAP[this._runCode.getEntryType(entry)],
|
||||
ref: entry,
|
||||
};
|
||||
|
||||
if( entryData.type === 'FILE' ){
|
||||
let fileName = entryData.path.split('/');
|
||||
entryData.fileName = fileName[fileName.length - 1];
|
||||
}
|
||||
|
||||
if( skipExtraction && except !== entryData.path ){
|
||||
this._runCode.skipEntry(this._archive);
|
||||
}else{
|
||||
const ptr = this._runCode.getFileData(this._archive,entryData.size);
|
||||
if( ptr < 0 ){
|
||||
throw new Error(this._runCode.getError(this._archive));
|
||||
}
|
||||
entryData.fileData = this._wasmModule.HEAP8.slice(ptr,ptr+entryData.size);
|
||||
this._wasmModule._free(ptr);
|
||||
}
|
||||
yield entryData;
|
||||
}
|
||||
}
|
||||
|
||||
_loadFile(fileBuffer,resolve,reject){
|
||||
try{
|
||||
const array = new Uint8Array(fileBuffer);
|
||||
this._fileLength = array.length;
|
||||
this._filePtr = this._runCode.malloc(this._fileLength);
|
||||
this._wasmModule.HEAP8.set(array, this._filePtr);
|
||||
resolve();
|
||||
}catch(error){
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
_promiseHandles(){
|
||||
let resolve = null,reject = null;
|
||||
const promise = new Promise((_resolve,_reject) => {
|
||||
resolve = _resolve;
|
||||
reject = _reject;
|
||||
});
|
||||
return { promise, resolve, reject };
|
||||
}
|
||||
|
||||
}
|
||||
15
static/js/libarchive.js/src/webworker/wasm-gen/libarchive.js
Normal file
15
static/js/libarchive.js/src/webworker/wasm-gen/libarchive.js
Normal file
File diff suppressed because one or more lines are too long
BIN
static/js/libarchive.js/src/webworker/wasm-gen/libarchive.wasm
Normal file
BIN
static/js/libarchive.js/src/webworker/wasm-gen/libarchive.wasm
Normal file
Binary file not shown.
97
static/js/libarchive.js/src/webworker/wasm-module.js
Normal file
97
static/js/libarchive.js/src/webworker/wasm-module.js
Normal file
@@ -0,0 +1,97 @@
|
||||
/* eslint-disable no-undef */
|
||||
import libarchive from './wasm-gen/libarchive.js';
|
||||
|
||||
export class WasmModule{
|
||||
constructor(){
|
||||
this.preRun = [];
|
||||
this.postRun = [];
|
||||
this.totalDependencies = 0;
|
||||
}
|
||||
|
||||
print(...text){
|
||||
console.log(text);
|
||||
}
|
||||
|
||||
printErr(...text){
|
||||
console.error(text);
|
||||
}
|
||||
|
||||
initFunctions(){
|
||||
this.runCode = {
|
||||
// const char * get_version()
|
||||
getVersion: this.cwrap('get_version', 'string', []),
|
||||
// void * archive_open( const void * buffer, size_t buffer_size)
|
||||
// retuns archive pointer
|
||||
openArchive: this.cwrap('archive_open', 'number', ['number','number','string']),
|
||||
// void * get_entry(void * archive)
|
||||
// return archive entry pointer
|
||||
getNextEntry: this.cwrap('get_next_entry', 'number', ['number']),
|
||||
// void * get_filedata( void * archive, size_t bufferSize )
|
||||
getFileData: this.cwrap('get_filedata', 'number', ['number','number']),
|
||||
// int archive_read_data_skip(struct archive *_a)
|
||||
skipEntry: this.cwrap('archive_read_data_skip', 'number', ['number']),
|
||||
// void archive_close( void * archive )
|
||||
closeArchive: this.cwrap('archive_close', null, ['number'] ),
|
||||
// la_int64_t archive_entry_size( struct archive_entry * )
|
||||
getEntrySize: this.cwrap('archive_entry_size', 'number', ['number']),
|
||||
// const char * archive_entry_pathname( struct archive_entry * )
|
||||
getEntryName: this.cwrap('archive_entry_pathname', 'string', ['number']),
|
||||
// __LA_MODE_T archive_entry_filetype( struct archive_entry * )
|
||||
/*
|
||||
#define AE_IFMT ((__LA_MODE_T)0170000)
|
||||
#define AE_IFREG ((__LA_MODE_T)0100000) // Regular file
|
||||
#define AE_IFLNK ((__LA_MODE_T)0120000) // Sybolic link
|
||||
#define AE_IFSOCK ((__LA_MODE_T)0140000) // Socket
|
||||
#define AE_IFCHR ((__LA_MODE_T)0020000) // Character device
|
||||
#define AE_IFBLK ((__LA_MODE_T)0060000) // Block device
|
||||
#define AE_IFDIR ((__LA_MODE_T)0040000) // Directory
|
||||
#define AE_IFIFO ((__LA_MODE_T)0010000) // Named pipe
|
||||
*/
|
||||
getEntryType: this.cwrap('archive_entry_filetype', 'number', ['number']),
|
||||
// const char * archive_error_string(struct archive *);
|
||||
getError: this.cwrap('archive_error_string', 'string', ['number']),
|
||||
|
||||
/*
|
||||
* Returns 1 if the archive contains at least one encrypted entry.
|
||||
* If the archive format not support encryption at all
|
||||
* ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED is returned.
|
||||
* If for any other reason (e.g. not enough data read so far)
|
||||
* we cannot say whether there are encrypted entries, then
|
||||
* ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW is returned.
|
||||
* In general, this function will return values below zero when the
|
||||
* reader is uncertain or totally incapable of encryption support.
|
||||
* When this function returns 0 you can be sure that the reader
|
||||
* supports encryption detection but no encrypted entries have
|
||||
* been found yet.
|
||||
*
|
||||
* NOTE: If the metadata/header of an archive is also encrypted, you
|
||||
* cannot rely on the number of encrypted entries. That is why this
|
||||
* function does not return the number of encrypted entries but#
|
||||
* just shows that there are some.
|
||||
*/
|
||||
// __LA_DECL int archive_read_has_encrypted_entries(struct archive *);
|
||||
entryIsEncrypted: this.cwrap('archive_entry_is_encrypted', 'number', ['number']),
|
||||
hasEncryptedEntries: this.cwrap('archive_read_has_encrypted_entries', 'number', ['number']),
|
||||
// __LA_DECL int archive_read_add_passphrase(struct archive *, const char *);
|
||||
addPassphrase: this.cwrap('archive_read_add_passphrase', 'number', ['number','string']),
|
||||
//this.stringToUTF(str), //
|
||||
string: (str) => this.allocate(this.intArrayFromString(str), 'i8', 0),
|
||||
malloc: this.cwrap('malloc', 'number', ['number']),
|
||||
free: this.cwrap('free', null, ['number']),
|
||||
};
|
||||
//console.log(this.runCode.getVersion());
|
||||
}
|
||||
|
||||
monitorRunDependencies(){}
|
||||
|
||||
locateFile(path /* ,prefix */ ){
|
||||
return `wasm-gen/${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getWasmModule(cb){
|
||||
libarchive( new WasmModule() ).then( (module) => {
|
||||
module.initFunctions();
|
||||
cb(module);
|
||||
});
|
||||
}
|
||||
69
static/js/libarchive.js/src/webworker/worker.js
Normal file
69
static/js/libarchive.js/src/webworker/worker.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import {ArchiveReader} from './archive-reader';
|
||||
import {getWasmModule} from './wasm-module';
|
||||
|
||||
let reader = null;
|
||||
let busy = false;
|
||||
|
||||
getWasmModule( (wasmModule) => {
|
||||
reader = new ArchiveReader(wasmModule);
|
||||
busy = false;
|
||||
self.postMessage({type: 'READY'});
|
||||
});
|
||||
|
||||
self.onmessage = async ({data: msg}) => {
|
||||
|
||||
if( busy ){
|
||||
self.postMessage({ type: 'BUSY' });
|
||||
return;
|
||||
}
|
||||
|
||||
let skipExtraction = false;
|
||||
busy = true;
|
||||
try{
|
||||
switch(msg.type){
|
||||
case 'HELLO': // module will respond READY when it's ready
|
||||
break;
|
||||
case 'OPEN':
|
||||
await reader.open(msg.file);
|
||||
self.postMessage({ type: 'OPENED' });
|
||||
break;
|
||||
case 'LIST_FILES':
|
||||
skipExtraction = true;
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'EXTRACT_FILES':
|
||||
for( const entry of reader.entries(skipExtraction) ){
|
||||
self.postMessage({ type: 'ENTRY', entry });
|
||||
}
|
||||
self.postMessage({ type: 'END' });
|
||||
break;
|
||||
case 'EXTRACT_SINGLE_FILE':
|
||||
for( const entry of reader.entries(true,msg.target) ){
|
||||
if( entry.fileData ){
|
||||
self.postMessage({ type: 'FILE', entry });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CHECK_ENCRYPTION':
|
||||
self.postMessage({ type: 'ENCRYPTION_STATUS', status: reader.hasEncryptedData() });
|
||||
break;
|
||||
case 'SET_PASSPHRASE':
|
||||
reader.setPassphrase( msg.passphrase );
|
||||
self.postMessage({ type: 'PASSPHRASE_STATUS', status: true });
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid Command');
|
||||
}
|
||||
}catch(err){
|
||||
self.postMessage({
|
||||
type: 'ERROR',
|
||||
error: {
|
||||
message: err.message,
|
||||
name: err.name,
|
||||
stack: err.stack
|
||||
}
|
||||
});
|
||||
}finally{
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
busy = false;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user