mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			235 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			235 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Modified from https://github.com/nika-begiashvili/libarchivejs
 | |
|  */
 | |
| 
 | |
| const Path = require('path')
 | |
| const libarchive = require('./wasm-libarchive')
 | |
| 
 | |
| const TYPE_MAP = {
 | |
|   32768: 'FILE',
 | |
|   16384: 'DIR',
 | |
|   40960: 'SYMBOLIC_LINK',
 | |
|   49152: 'SOCKET',
 | |
|   8192: 'CHARACTER_DEVICE',
 | |
|   24576: 'BLOCK_DEVICE',
 | |
|   4096: 'NAMED_PIPE',
 | |
| }
 | |
| 
 | |
| 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
 | |
|     this._loadFile(file, resolve, reject)
 | |
|     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 }
 | |
|   }
 | |
| 
 | |
| }
 | |
| 
 | |
| 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']),
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   monitorRunDependencies() { }
 | |
| 
 | |
|   locateFile(path /* ,prefix */) {
 | |
|     const wasmFilepath = Path.join(__dirname, `../../../client/dist/libarchive/wasm-gen/${path}`)
 | |
|     return wasmFilepath
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports.getArchiveReader = (cb) => {
 | |
|   libarchive(new WasmModule()).then((module) => {
 | |
|     module.initFunctions()
 | |
|     cb(new ArchiveReader(module))
 | |
|   })
 | |
| } |