forked from advplyr/audiobookshelf
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add:Scanner extracts cover from comic files advplyr#1837 and ComicInf…
…o.xml parser
- Loading branch information
Showing
10 changed files
with
762 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2018 ნიკა | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
/** | ||
* Modified from https://github.com/nika-begiashvili/libarchivejs | ||
*/ | ||
|
||
const Path = require('path') | ||
const { Worker } = require('worker_threads') | ||
|
||
/** | ||
* Represents compressed file before extraction | ||
*/ | ||
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) | ||
} | ||
|
||
} | ||
|
||
class Archive { | ||
/** | ||
* Creates new archive instance from browser native File object | ||
* @param {Buffer} fileBuffer | ||
* @param {object} options | ||
* @returns {Archive} | ||
*/ | ||
static open(fileBuffer) { | ||
const arch = new Archive(fileBuffer, { workerUrl: Path.join(__dirname, 'libarchiveWorker.js') }) | ||
return arch.open() | ||
} | ||
|
||
/** | ||
* Create new archive | ||
* @param {File} file | ||
* @param {Object} options | ||
*/ | ||
constructor(file, options) { | ||
this._worker = new Worker(options.workerUrl) | ||
this._worker.on('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) | ||
} | ||
}) | ||
} | ||
|
||
/** | ||
* Terminate worker to free up memory | ||
*/ | ||
close() { | ||
this._worker.terminate() | ||
this._worker = null | ||
} | ||
|
||
/** | ||
* 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) { | ||
// Prevent extraction if worker already terminated | ||
if (this._worker === null) { | ||
throw new Error("Archive already closed") | ||
} | ||
|
||
return this._postMessage({ type: 'EXTRACT_SINGLE_FILE', target: target }, | ||
(resolve, reject, msg) => { | ||
if (msg.type === 'FILE') { | ||
resolve(msg.entry) | ||
} | ||
} | ||
) | ||
} | ||
|
||
/** | ||
* 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] = msg.entry | ||
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 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 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) { | ||
reject('invalid msg') | ||
return | ||
} | ||
if (msg.type === 'BUSY') { | ||
reject('worker is busy') | ||
} else if (msg.type === 'ERROR') { | ||
reject(msg.error) | ||
} else { | ||
return callback(resolve, reject, msg) | ||
} | ||
} | ||
|
||
_workerMsg(msg) { | ||
const callback = this._callbacks[this._callbacks.length - 1] | ||
const next = callback(msg) | ||
if (!next) { | ||
this._callbacks.pop() | ||
} | ||
} | ||
|
||
} | ||
module.exports = Archive |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* Modified from https://github.com/nika-begiashvili/libarchivejs | ||
*/ | ||
|
||
const { parentPort } = require('worker_threads') | ||
const { getArchiveReader } = require('./wasm-module') | ||
|
||
let reader = null | ||
let busy = false | ||
|
||
getArchiveReader((_reader) => { | ||
reader = _reader | ||
busy = false | ||
parentPort.postMessage({ type: 'READY' }) | ||
}) | ||
|
||
parentPort.on('message', async msg => { | ||
if (busy) { | ||
parentPort.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) | ||
parentPort.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)) { | ||
parentPort.postMessage({ type: 'ENTRY', entry }) | ||
} | ||
parentPort.postMessage({ type: 'END' }) | ||
break | ||
case 'EXTRACT_SINGLE_FILE': | ||
for (const entry of reader.entries(true, msg.target)) { | ||
if (entry.fileData) { | ||
parentPort.postMessage({ type: 'FILE', entry }) | ||
} | ||
} | ||
break | ||
case 'CHECK_ENCRYPTION': | ||
parentPort.postMessage({ type: 'ENCRYPTION_STATUS', status: reader.hasEncryptedData() }) | ||
break | ||
case 'SET_PASSPHRASE': | ||
reader.setPassphrase(msg.passphrase) | ||
parentPort.postMessage({ type: 'PASSPHRASE_STATUS', status: true }) | ||
break | ||
default: | ||
throw new Error('Invalid Command') | ||
} | ||
} catch (err) { | ||
parentPort.postMessage({ | ||
type: 'ERROR', | ||
error: { | ||
message: err.message, | ||
name: err.name, | ||
stack: err.stack | ||
} | ||
}) | ||
} finally { | ||
// eslint-disable-next-line require-atomic-updates | ||
busy = false | ||
} | ||
}) |
Large diffs are not rendered by default.
Oops, something went wrong.
Oops, something went wrong.