Compare commits

..
28 Commits
Author SHA1 Message Date
Ozzie Isaacs aba88ae53a Update setup.cfg 2019-07-15 21:04:08 +02:00
Ozzie Isaacs 67d0ddb180 Update optional requirements 2019-07-15 21:03:35 +02:00
Ozzie Isaacs b1bb1cfdfa Changed optional dependencies 2019-07-15 20:21:59 +02:00
Ozzieisaacs 37736e11d5 Updated dependencies 2019-07-13 20:32:39 +02:00
Ozzieisaacs 792367e35e Version update 2019-07-13 20:27:32 +02:00
Ozzieisaacs b1cb7123a3 Fix for #959 2019-07-11 20:37:03 +02:00
Ozzieisaacs f79d549910 Fix duplicate user and email (now case insensitive #948)
Fix sorting in comics (#950)
Fix log error on Calibre converter error (#953)
Fix long running tasks (#954)
2019-07-06 14:35:12 +02:00
Ozzieisaacs 66283c542f Merge remote-tracking branch 'dutch/master' 2019-06-22 19:29:39 +02:00
Ozzie Isaacs c1d5f77fe8 Wiki link fixed 2019-06-20 10:38:32 +02:00
Heimen Stoffels d9f69ca264 Updated Dutch translation 2019-06-17 22:38:32 +02:00
Ozzieisaacs cd546eb6d4 Update comic reader js 2019-06-17 19:48:17 +02:00
Ozzieisaacs e67d707867 Fix for #588
Merge remote-tracking branch 'linuxserver/master'
2019-06-15 11:58:55 +02:00
Ozzieisaacs 3f5c6c1fa5 Fix #937 2019-06-13 20:01:37 +02:00
Ozzieisaacs f5e3ed26b9 Fix for #935 2019-06-10 19:31:13 +02:00
Ozzieisaacs c81d4edb7d Fix for #923 2019-06-10 16:38:29 +02:00
Ozzieisaacs f736a15c12 Fix for restart of newer versions of tornado during update
Fix for updater with Beta Versions
2019-06-02 18:47:25 +02:00
Ozzieisaacs 87ca05f129 Merge remote-tracking branch 'setup/feature/setuptools-integration' 2019-05-31 16:24:13 +02:00
Luke Murphy b3a286c0b5 Add initial setuptools integration 2019-05-31 11:54:05 +02:00
Ozzieisaacs 26e45f1f57 Merge remote-tracking branch 'comic-reader/master' 2019-05-31 08:43:51 +02:00
Ozzieisaacs df5d15d1a2 Merge remote-tracking branch 'comicapi/master' 2019-05-30 10:43:28 +02:00
Ozzieisaacs ecedf92783 Update version
Make custom columns visible (#904)
2019-05-30 10:21:11 +02:00
subdiox 867aa2f0bd Beautify bitjs 2019-05-14 04:28:06 +09:00
subdiox 7982ed877c Downgrade bitjs to es5 branch 2019-05-13 14:53:25 +09:00
subdiox c0d136ccd8 Fix slow loading 2019-05-10 08:16:03 +09:00
Iris W 029d299067 updated comicapi version to fix installation error 2019-04-09 15:22:03 -04:00
Iris W b7e30644ab updated comicapi requirement—libunrar no longer necessary (CBR parsing support was always disabled anyway) 2019-04-03 15:36:05 -04:00
Iris Wildthyme cbdc9876b2 comicapi dependency now pippable 2019-04-02 20:58:23 -04:00
Iris Wildthyme 05d0f12608 redid comic importing support to handle metadata properly using comicapi from comictagger. needs work to automate installation 2019-04-02 18:52:45 -04:00
34 changed files with 3191 additions and 1770 deletions
+3
View File
@@ -0,0 +1,3 @@
include cps/static/*
include cps/templates/*
include cps/translations/*
Regular → Executable
+75 -25
View File
@@ -17,33 +17,52 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import zipfile
import tarfile
import os import os
import uploader import uploader
import logging
from iso639 import languages as isoLanguages
logger = logging.getLogger("book_formats")
try:
from comicapi.comicarchive import ComicArchive, MetaDataStyle
use_comic_meta = True
except ImportError as e:
logger.warning('cannot import comicapi, extracting comic metadata will not work: %s', e)
import zipfile
import tarfile
use_comic_meta = False
def extractCover(tmp_file_name, original_file_extension): def extractCover(tmp_file_name, original_file_extension):
cover_data = None if use_comic_meta:
if original_file_extension.upper() == '.CBZ': archive = ComicArchive(tmp_file_name)
cf = zipfile.ZipFile(tmp_file_name) cover_data = None
for name in cf.namelist(): ext = os.path.splitext(archive.getPageName(0))
ext = os.path.splitext(name) if len(ext) > 1:
if len(ext) > 1: extension = ext[1].lower()
extension = ext[1].lower() if extension == '.jpg' or extension == '.jpeg':
if extension == '.jpg': cover_data = archive.getPage(0)
cover_data = cf.read(name) else:
break if original_file_extension.upper() == '.CBZ':
elif original_file_extension.upper() == '.CBT': cf = zipfile.ZipFile(tmp_file_name)
cf = tarfile.TarFile(tmp_file_name) for name in cf.namelist():
for name in cf.getnames(): ext = os.path.splitext(name)
ext = os.path.splitext(name) if len(ext) > 1:
if len(ext) > 1: extension = ext[1].lower()
extension = ext[1].lower() if extension == '.jpg':
if extension == '.jpg': cover_data = cf.read(name)
cover_data = cf.extractfile(name).read() break
break elif original_file_extension.upper() == '.CBT':
cf = tarfile.TarFile(tmp_file_name)
for name in cf.getnames():
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg':
cover_data = cf.extractfile(name).read()
break
prefix = os.path.dirname(tmp_file_name) prefix = os.path.dirname(tmp_file_name)
if cover_data: if cover_data:
tmp_cover_name = prefix + '/cover' + extension tmp_cover_name = prefix + '/cover' + extension
@@ -56,15 +75,46 @@ def extractCover(tmp_file_name, original_file_extension):
def get_comic_info(tmp_file_path, original_file_name, original_file_extension): def get_comic_info(tmp_file_path, original_file_name, original_file_extension):
if use_comic_meta:
archive = ComicArchive(tmp_file_path)
if archive.seemsToBeAComicArchive():
if archive.hasMetadata(MetaDataStyle.CIX):
style = MetaDataStyle.CIX
elif archive.hasMetadata(MetaDataStyle.CBI):
style = MetaDataStyle.CBI
else:
style = None
coverfile = extractCover(tmp_file_path, original_file_extension) if style is not None:
loadedMetadata = archive.readMetadata(style)
return uploader.BookMeta( lang = loadedMetadata.language
if len(lang) == 2:
loadedMetadata.language = isoLanguages.get(part1=lang).name
elif len(lang) == 3:
loadedMetadata.language = isoLanguages.get(part3=lang).name
else:
loadedMetadata.language = ""
return uploader.BookMeta(
file_path=tmp_file_path,
extension=original_file_extension,
title=loadedMetadata.title or original_file_name,
author=" & ".join([credit["person"] for credit in loadedMetadata.credits if credit["role"] == "Writer"]) or u"Unknown",
cover=extractCover(tmp_file_path, original_file_extension),
description=loadedMetadata.comments or "",
tags="",
series=loadedMetadata.series or "",
series_id=loadedMetadata.issue or "",
languages=loadedMetadata.language)
else:
return uploader.BookMeta(
file_path=tmp_file_path, file_path=tmp_file_path,
extension=original_file_extension, extension=original_file_extension,
title=original_file_name, title=original_file_name,
author=u"Unknown", author=u"Unknown",
cover=coverfile, cover=extractCover(tmp_file_path, original_file_extension),
description="", description="",
tags="", tags="",
series="", series="",
+32 -1
View File
@@ -34,7 +34,8 @@ from flask import send_from_directory, make_response, redirect, abort
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_login import current_user from flask_login import current_user
from babel.dates import format_datetime from babel.dates import format_datetime
from datetime import datetime from babel.units import format_unit
from datetime import datetime, timedelta
import shutil import shutil
import requests import requests
try: try:
@@ -566,8 +567,33 @@ def json_serial(obj):
if isinstance(obj, (datetime)): if isinstance(obj, (datetime)):
return obj.isoformat() return obj.isoformat()
if isinstance(obj, (timedelta)):
return {
'__type__': 'timedelta',
'days': obj.days,
'seconds': obj.seconds,
'microseconds': obj.microseconds,
}
raise TypeError ("Type %s not serializable" % type(obj)) raise TypeError ("Type %s not serializable" % type(obj))
# helper function for displaying the runtime of tasks
def format_runtime(runtime):
retVal = ""
if runtime.days:
retVal = format_unit(runtime.days, 'duration-day', length="long", locale=web.get_locale()) + ', '
mins, seconds = divmod(runtime.seconds, 60)
hours, minutes = divmod(mins, 60)
# ToDo: locale.number_symbols._data['timeSeparator'] -> localize time separator ?
if hours:
retVal += '{:d}:{:02d}:{:02d}s'.format(hours, minutes, seconds)
elif minutes:
retVal += '{:2d}:{:02d}s'.format(minutes, seconds)
else:
retVal += '{:2d}s'.format(seconds)
return retVal
# helper function to apply localize status information in tasklist entries # helper function to apply localize status information in tasklist entries
def render_task_status(tasklist): def render_task_status(tasklist):
renderedtasklist=list() renderedtasklist=list()
@@ -579,6 +605,11 @@ def render_task_status(tasklist):
if 'starttime' not in task: if 'starttime' not in task:
task['starttime'] = "" task['starttime'] = ""
if 'formRuntime' not in task:
task['runtime'] = ""
else:
task['runtime'] = format_runtime(task['formRuntime'])
# localize the task status # localize the task status
if isinstance( task['stat'], int ): if isinstance( task['stat'], int ):
if task['stat'] == worker.STAT_WAITING: if task['stat'] == worker.STAT_WAITING:
+5 -4
View File
@@ -103,6 +103,7 @@ class server:
ssl_options=ssl) ssl_options=ssl)
http_server.listen(web.ub.config.config_port) http_server.listen(web.ub.config.config_port)
self.wsgiserver=IOLoop.instance() self.wsgiserver=IOLoop.instance()
web.py3_gevent_link = self.wsgiserver
self.wsgiserver.start() self.wsgiserver.start()
# wait for stop signal # wait for stop signal
self.wsgiserver.close(True) self.wsgiserver.close(True)
@@ -142,10 +143,10 @@ class server:
# ToDo: Somehow caused by circular import under python3 refactor # ToDo: Somehow caused by circular import under python3 refactor
if sys.version_info > (3, 0): if sys.version_info > (3, 0):
if not self.wsgiserver: if not self.wsgiserver:
if gevent_present: # if gevent_present:
self.wsgiserver = web.py3_gevent_link self.wsgiserver = web.py3_gevent_link
else: #else:
self.wsgiserver = IOLoop.instance() # self.wsgiserver = IOLoop.instance()
if self.wsgiserver: if self.wsgiserver:
if gevent_present: if gevent_present:
self.wsgiserver.close() self.wsgiserver.close()
+1 -1
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

@@ -1,3 +1,67 @@
/* alphanum.js (C) Brian Huisman
* Based on the Alphanum Algorithm by David Koelle
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
*
* Distributed under same license as original
*
* Released under the MIT License - https://opensource.org/licenses/MIT
*
* 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.
*/
/* ********************************************************************
* Alphanum sort() function version - case insensitive
* - Slower, but easier to modify for arrays of objects which contain
* string properties
*
*/
function alphanumCase(a, b) {
function chunkify(t) {
var tz = new Array();
var x = 0, y = -1, n = 0, i, j;
while (i = (j = t.charAt(x++)).charCodeAt(0)) {
var m = (i == 46 || (i >=48 && i <= 57));
if (m !== n) {
tz[++y] = "";
n = m;
}
tz[y] += j;
}
return tz;
}
var aa = chunkify(a.filename.toLowerCase());
var bb = chunkify(b.filename.toLowerCase());
for (x = 0; aa[x] && bb[x]; x++) {
if (aa[x] !== bb[x]) {
var c = Number(aa[x]), d = Number(bb[x]);
if (c == aa[x] && d == bb[x]) {
return c - d;
} else return (aa[x] > bb[x]) ? 1 : -1;
}
}
return aa.length - bb.length;
}
// ===========================================================================
/** /**
* archive.js * archive.js
* *
@@ -8,7 +72,7 @@
* Copyright(c) 2011 Google Inc. * Copyright(c) 2011 Google Inc.
*/ */
/* global bitjs */ /* global bitjs, Uint8Array */
var bitjs = bitjs || {}; var bitjs = bitjs || {};
bitjs.archive = bitjs.archive || {}; bitjs.archive = bitjs.archive || {};
@@ -27,8 +91,7 @@ bitjs.archive = bitjs.archive || {};
var args = Array.prototype.slice.call(arguments, 2); var args = Array.prototype.slice.call(arguments, 2);
var foundCaller = false; var foundCaller = false;
for (var ctor = me.constructor; for (var ctor = me.constructor; ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
if (ctor.prototype[optMethodName] === caller) { if (ctor.prototype[optMethodName] === caller) {
foundCaller = true; foundCaller = true;
} else if (foundCaller) { } else if (foundCaller) {
@@ -66,10 +129,10 @@ bitjs.archive = bitjs.archive || {};
*/ */
bitjs.archive.UnarchiveEvent = function(type) { bitjs.archive.UnarchiveEvent = function(type) {
/** /**
* The event type. * The event type.
* *
* @type {string} * @type {string}
*/ */
this.type = type; this.type = type;
}; };
@@ -94,10 +157,10 @@ bitjs.archive = bitjs.archive || {};
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO); bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO);
/** /**
* The information message. * The information message.
* *
* @type {string} * @type {string}
*/ */
this.msg = msg; this.msg = msg;
}; };
bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent); bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent);
@@ -111,10 +174,10 @@ bitjs.archive = bitjs.archive || {};
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR); bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR);
/** /**
* The information message. * The information message.
* *
* @type {string} * @type {string}
*/ */
this.msg = msg; this.msg = msg;
}; };
bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent); bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent);
@@ -178,8 +241,8 @@ bitjs.archive = bitjs.archive || {};
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT); bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT);
/** /**
* @type {UnarchivedFile} * @type {UnarchivedFile}
*/ */
this.unarchivedFile = unarchivedFile; this.unarchivedFile = unarchivedFile;
}; };
bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent); bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent);
@@ -194,23 +257,23 @@ bitjs.archive = bitjs.archive || {};
*/ */
bitjs.archive.Unarchiver = function(arrayBuffer, optPathToBitJS) { bitjs.archive.Unarchiver = function(arrayBuffer, optPathToBitJS) {
/** /**
* The ArrayBuffer object. * The ArrayBuffer object.
* @type {ArrayBuffer} * @type {ArrayBuffer}
* @protected * @protected
*/ */
this.ab = arrayBuffer; this.ab = arrayBuffer;
/** /**
* The path to the BitJS files. * The path to the BitJS files.
* @type {string} * @type {string}
* @private * @private
*/ */
this.pathToBitJS_ = optPathToBitJS || ""; this.pathToBitJS_ = optPathToBitJS || "/";
/** /**
* A map from event type to an array of listeners. * A map from event type to an array of listeners.
* @type {Map.<string, Array>} * @type {Map.<string, Array>}
*/ */
this.listeners_ = {}; this.listeners_ = {};
for (var type in bitjs.archive.UnarchiveEvent.Type) { for (var type in bitjs.archive.UnarchiveEvent.Type) {
this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = []; this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = [];
@@ -282,7 +345,7 @@ bitjs.archive = bitjs.archive || {};
/** /**
* Starts the unarchive in a separate Web Worker thread and returns immediately. * Starts the unarchive in a separate Web Worker thread and returns immediately.
*/ */
bitjs.archive.Unarchiver.prototype.start = function() { bitjs.archive.Unarchiver.prototype.start = function() {
var me = this; var me = this;
var scriptFileName = this.pathToBitJS_ + this.getScriptFileName(); var scriptFileName = this.pathToBitJS_ + this.getScriptFileName();
@@ -354,4 +417,26 @@ bitjs.archive = bitjs.archive || {};
return "untar.js"; return "untar.js";
}; };
/**
* Factory method that creates an unarchiver based on the byte signature found
* in the arrayBuffer.
* @param {ArrayBuffer} ab
* @param {string=} optPathToBitJS Path to the unarchiver script files.
* @return {bitjs.archive.Unarchiver}
*/
bitjs.archive.GetUnarchiver = function(ab, optPathToBitJS) {
var unarchiver = null;
var pathToBitJS = optPathToBitJS || "";
var h = new Uint8Array(ab, 0, 10);
if (h[0] === 0x52 && h[1] === 0x61 && h[2] === 0x72 && h[3] === 0x21) { // Rar!
unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS);
} else if (h[0] === 80 && h[1] === 75) { // PK (Zip)
unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS);
} else { // Try with tar
unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
}
return unarchiver;
};
})(); })();
+864
View File
@@ -0,0 +1,864 @@
/**
* rarvm.js
*
* Licensed under the MIT License
*
* Copyright(c) 2017 Google Inc.
*/
/**
* CRC Implementation.
*/
/* global Uint8Array, Uint32Array, bitjs, DataView */
/* exported MAXWINMASK, UnpackFilter */
var CRCTab = new Array(256).fill(0);
function initCRC() {
for (var i = 0; i < 256; ++i) {
var c = i;
for (var j = 0; j < 8; ++j) {
// Read http://stackoverflow.com/questions/6798111/bitwise-operations-on-32-bit-unsigned-ints
// for the bitwise operator issue (JS interprets operands as 32-bit signed
// integers and we need to deal with unsigned ones here).
c = ((c & 1) ? ((c >>> 1) ^ 0xEDB88320) : (c >>> 1)) >>> 0;
}
CRCTab[i] = c;
}
}
/**
* @param {number} startCRC
* @param {Uint8Array} arr
* @return {number}
*/
function CRC(startCRC, arr) {
if (CRCTab[1] === 0) {
initCRC();
}
/*
#if defined(LITTLE_ENDIAN) && defined(PRESENT_INT32) && defined(ALLOW_NOT_ALIGNED_INT)
while (Size>0 && ((long)Data & 7))
{
StartCRC=CRCTab[(byte)(StartCRC^Data[0])]^(StartCRC>>8);
Size--;
Data++;
}
while (Size>=8)
{
StartCRC^=*(uint32 *)Data;
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC^=*(uint32 *)(Data+4);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
Data+=8;
Size-=8;
}
#endif
*/
for (var i = 0; i < arr.length; ++i) {
var byte = ((startCRC ^ arr[i]) >>> 0) & 0xff;
startCRC = (CRCTab[byte] ^ (startCRC >>> 8)) >>> 0;
}
return startCRC;
}
// ============================================================================================== //
/**
* RarVM Implementation.
*/
var VM_MEMSIZE = 0x40000;
var VM_MEMMASK = (VM_MEMSIZE - 1);
var VM_GLOBALMEMADDR = 0x3C000;
var VM_GLOBALMEMSIZE = 0x2000;
var VM_FIXEDGLOBALSIZE = 64;
var MAXWINSIZE = 0x400000;
var MAXWINMASK = (MAXWINSIZE - 1);
/**
*/
var VmCommands = {
VM_MOV: 0,
VM_CMP: 1,
VM_ADD: 2,
VM_SUB: 3,
VM_JZ: 4,
VM_JNZ: 5,
VM_INC: 6,
VM_DEC: 7,
VM_JMP: 8,
VM_XOR: 9,
VM_AND: 10,
VM_OR: 11,
VM_TEST: 12,
VM_JS: 13,
VM_JNS: 14,
VM_JB: 15,
VM_JBE: 16,
VM_JA: 17,
VM_JAE: 18,
VM_PUSH: 19,
VM_POP: 20,
VM_CALL: 21,
VM_RET: 22,
VM_NOT: 23,
VM_SHL: 24,
VM_SHR: 25,
VM_SAR: 26,
VM_NEG: 27,
VM_PUSHA: 28,
VM_POPA: 29,
VM_PUSHF: 30,
VM_POPF: 31,
VM_MOVZX: 32,
VM_MOVSX: 33,
VM_XCHG: 34,
VM_MUL: 35,
VM_DIV: 36,
VM_ADC: 37,
VM_SBB: 38,
VM_PRINT: 39,
/*
#ifdef VM_OPTIMIZE
VM_MOVB, VM_MOVD, VM_CMPB, VM_CMPD,
VM_ADDB, VM_ADDD, VM_SUBB, VM_SUBD, VM_INCB, VM_INCD, VM_DECB, VM_DECD,
VM_NEGB, VM_NEGD,
#endif
*/
// TODO: This enum value would be much larger if VM_OPTIMIZE.
VM_STANDARD: 40,
};
/**
*/
var VmStandardFilters = {
VMSF_NONE: 0,
VMSF_E8: 1,
VMSF_E8E9: 2,
VMSF_ITANIUM: 3,
VMSF_RGB: 4,
VMSF_AUDIO: 5,
VMSF_DELTA: 6,
VMSF_UPCASE: 7,
};
/**
*/
var VmFlags = {
VM_FC: 1,
VM_FZ: 2,
VM_FS: 0x80000000,
};
/**
*/
var VmOpType = {
VM_OPREG: 0,
VM_OPINT: 1,
VM_OPREGMEM: 2,
VM_OPNONE: 3,
};
/**
* Finds the key that maps to a given value in an object. This function is useful in debugging
* variables that use the above enums.
* @param {Object} obj
* @param {number} val
* @return {string} The key/enum value as a string.
*/
function findKeyForValue(obj, val) {
for (var key in obj) {
if (obj[key] === val) {
return key;
}
}
return null;
}
function getDebugString(obj, val) {
var s = "Unknown.";
if (obj === VmCommands) {
s = "VmCommands.";
} else if (obj === VmStandardFilters) {
s = "VmStandardFilters.";
} else if (obj === VmFlags) {
s = "VmOpType.";
} else if (obj === VmOpType) {
s = "VmOpType.";
}
return s + findKeyForValue(obj, val);
}
/**
* @struct
* @constructor
*/
var VmPreparedOperand = function() {
/** @type {VmOpType} */
this.Type;
/** @type {number} */
this.Data = 0;
/** @type {number} */
this.Base = 0;
// TODO: In C++ this is a uint*
/** @type {Array<number>} */
this.Addr = null;
};
/** @return {string} */
VmPreparedOperand.prototype.toString = function() {
if (this.Type === null) {
return "Error: Type was null in VmPreparedOperand";
}
return "{ " +
"Type: " + getDebugString(VmOpType, this.Type) +
", Data: " + this.Data +
", Base: " + this.Base +
" }";
};
/**
* @struct
* @constructor
*/
var VmPreparedCommand = function() {
/** @type {VmCommands} */
this.OpCode;
/** @type {boolean} */
this.ByteMode = false;
/** @type {VmPreparedOperand} */
this.Op1 = new VmPreparedOperand();
/** @type {VmPreparedOperand} */
this.Op2 = new VmPreparedOperand();
};
/** @return {string} */
VmPreparedCommand.prototype.toString = function(indent) {
if (this.OpCode === null) {
return "Error: OpCode was null in VmPreparedCommand";
}
indent = indent || "";
return indent + "{\n" +
indent + " OpCode: " + getDebugString(VmCommands, this.OpCode) + ",\n" +
indent + " ByteMode: " + this.ByteMode + ",\n" +
indent + " Op1: " + this.Op1.toString() + ",\n" +
indent + " Op2: " + this.Op2.toString() + ",\n" +
indent + "}";
};
/**
* @struct
* @constructor
*/
var VmPreparedProgram = function() {
/** @type {Array<VmPreparedCommand>} */
this.Cmd = [];
/** @type {Array<VmPreparedCommand>} */
this.AltCmd = null;
/** @type {Uint8Array} */
this.GlobalData = new Uint8Array();
/** @type {Uint8Array} */
this.StaticData = new Uint8Array(); // static data contained in DB operators
/** @type {Uint32Array} */
this.InitR = new Uint32Array(7);
/**
* A pointer to bytes that have been filtered by a program.
* @type {Uint8Array}
*/
this.FilteredData = null;
};
/** @return {string} */
VmPreparedProgram.prototype.toString = function() {
var s = "{\n Cmd: [\n";
for (var i = 0; i < this.Cmd.length; ++i) {
s += this.Cmd[i].toString(" ") + ",\n";
}
s += "],\n";
// TODO: Dump GlobalData, StaticData, InitR?
s += " }\n";
return s;
};
/**
* @struct
* @constructor
*/
var UnpackFilter = function() {
/** @type {number} */
this.BlockStart = 0;
/** @type {number} */
this.BlockLength = 0;
/** @type {number} */
this.ExecCount = 0;
/** @type {boolean} */
this.NextWindow = false;
// position of parent filter in Filters array used as prototype for filter
// in PrgStack array. Not defined for filters in Filters array.
/** @type {number} */
this.ParentFilter = null;
/** @type {VmPreparedProgram} */
this.Prg = new VmPreparedProgram();
};
var VMCF_OP0 = 0;
var VMCF_OP1 = 1;
var VMCF_OP2 = 2;
var VMCF_OPMASK = 3;
var VMCF_BYTEMODE = 4;
var VMCF_JUMP = 8;
var VMCF_PROC = 16;
var VMCF_USEFLAGS = 32;
var VMCF_CHFLAGS = 64;
var VmCmdFlags = [
/* VM_MOV */
VMCF_OP2 | VMCF_BYTEMODE,
/* VM_CMP */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_ADD */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_SUB */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_JZ */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JNZ */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_INC */
VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_DEC */
VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_JMP */
VMCF_OP1 | VMCF_JUMP,
/* VM_XOR */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_AND */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_OR */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_TEST */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_JS */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JNS */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JB */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JBE */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JA */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JAE */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_PUSH */
VMCF_OP1,
/* VM_POP */
VMCF_OP1,
/* VM_CALL */
VMCF_OP1 | VMCF_PROC,
/* VM_RET */
VMCF_OP0 | VMCF_PROC,
/* VM_NOT */
VMCF_OP1 | VMCF_BYTEMODE,
/* VM_SHL */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_SHR */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_SAR */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_NEG */
VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_PUSHA */
VMCF_OP0,
/* VM_POPA */
VMCF_OP0,
/* VM_PUSHF */
VMCF_OP0 | VMCF_USEFLAGS,
/* VM_POPF */
VMCF_OP0 | VMCF_CHFLAGS,
/* VM_MOVZX */
VMCF_OP2,
/* VM_MOVSX */
VMCF_OP2,
/* VM_XCHG */
VMCF_OP2 | VMCF_BYTEMODE,
/* VM_MUL */
VMCF_OP2 | VMCF_BYTEMODE,
/* VM_DIV */
VMCF_OP2 | VMCF_BYTEMODE,
/* VM_ADC */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS,
/* VM_SBB */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS,
/* VM_PRINT */
VMCF_OP0,
];
/**
* @param {number} length
* @param {number} crc
* @param {VmStandardFilters} type
* @struct
* @constructor
*/
var StandardFilterSignature = function(length, crc, type) {
/** @type {number} */
this.Length = length;
/** @type {number} */
this.CRC = crc;
/** @type {VmStandardFilters} */
this.Type = type;
};
/**
* @type {Array<StandardFilterSignature>}
*/
var StdList = [
new StandardFilterSignature(53, 0xad576887, VmStandardFilters.VMSF_E8),
new StandardFilterSignature(57, 0x3cd7e57e, VmStandardFilters.VMSF_E8E9),
new StandardFilterSignature(120, 0x3769893f, VmStandardFilters.VMSF_ITANIUM),
new StandardFilterSignature(29, 0x0e06077d, VmStandardFilters.VMSF_DELTA),
new StandardFilterSignature(149, 0x1c2c5dc8, VmStandardFilters.VMSF_RGB),
new StandardFilterSignature(216, 0xbc85e701, VmStandardFilters.VMSF_AUDIO),
new StandardFilterSignature(40, 0x46b9c560, VmStandardFilters.VMSF_UPCASE),
];
/**
* @constructor
*/
var RarVM = function() {
/** @private {Uint8Array} */
this.mem_ = null;
/** @private {Uint32Array<number>} */
this.R_ = new Uint32Array(8);
/** @private {number} */
this.flags_ = 0;
};
/**
* Initializes the memory of the VM.
*/
RarVM.prototype.init = function() {
if (!this.mem_) {
this.mem_ = new Uint8Array(VM_MEMSIZE);
}
};
/**
* @param {Uint8Array} code
* @return {VmStandardFilters}
*/
RarVM.prototype.isStandardFilter = function(code) {
var codeCRC = (CRC(0xffffffff, code, code.length) ^ 0xffffffff) >>> 0;
for (var i = 0; i < StdList.length; ++i) {
if (StdList[i].CRC === codeCRC && StdList[i].Length === code.length) {
return StdList[i].Type;
}
}
return VmStandardFilters.VMSF_NONE;
};
/**
* @param {VmPreparedOperand} op
* @param {boolean} byteMode
* @param {bitjs.io.BitStream} bstream A rtl bit stream.
*/
RarVM.prototype.decodeArg = function(op, byteMode, bstream) {
var data = bstream.peekBits(16);
if (data & 0x8000) {
op.Type = VmOpType.VM_OPREG; // Operand is register (R[0]..R[7])
bstream.readBits(1); // 1 flag bit and...
op.Data = bstream.readBits(3); // ... 3 register number bits
op.Addr = [this.R_[op.Data]]; // TODO &R[Op.Data] // Register address
} else {
if ((data & 0xc000) === 0) {
op.Type = VmOpType.VM_OPINT; // Operand is integer
bstream.readBits(2); // 2 flag bits
if (byteMode) {
op.Data = bstream.readBits(8); // Byte integer.
} else {
op.Data = RarVM.readData(bstream); // 32 bit integer.
}
} else {
// Operand is data addressed by register data, base address or both.
op.Type = VmOpType.VM_OPREGMEM;
if ((data & 0x2000) === 0) {
bstream.readBits(3); // 3 flag bits
// Base address is zero, just use the address from register.
op.Data = bstream.readBits(3); // (Data>>10)&7
op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data]
op.Base = 0;
} else {
bstream.readBits(4); // 4 flag bits
if ((data & 0x1000) === 0) {
// Use both register and base address.
op.Data = bstream.readBits(3);
op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data]
} else {
// Use base address only. Access memory by fixed address.
op.Data = 0;
}
op.Base = RarVM.readData(bstream); // Read base address.
}
}
}
};
/**
* @param {VmPreparedProgram} prg
*/
RarVM.prototype.execute = function(prg) {
this.R_.set(prg.InitR);
var globalSize = Math.min(prg.GlobalData.length, VM_GLOBALMEMSIZE);
if (globalSize) {
this.mem_.set(prg.GlobalData.subarray(0, globalSize), VM_GLOBALMEMADDR);
}
var staticSize = Math.min(prg.StaticData.length, VM_GLOBALMEMSIZE - globalSize);
if (staticSize) {
this.mem_.set(prg.StaticData.subarray(0, staticSize), VM_GLOBALMEMADDR + globalSize);
}
this.R_[7] = VM_MEMSIZE;
this.flags_ = 0;
var preparedCodes = prg.AltCmd ? prg.AltCmd : prg.Cmd;
if (prg.Cmd.length > 0 && !this.executeCode(preparedCodes)) {
// Invalid VM program. Let's replace it with 'return' command.
preparedCodes.OpCode = VmCommands.VM_RET;
}
var dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR);
var newBlockPos = dataView.getUint32(0x20, true /* little endian */ ) & VM_MEMMASK;
var newBlockSize = dataView.getUint32(0x1c, true /* little endian */ ) & VM_MEMMASK;
if (newBlockPos + newBlockSize >= VM_MEMSIZE) {
newBlockPos = newBlockSize = 0;
}
prg.FilteredData = this.mem_.subarray(newBlockPos, newBlockPos + newBlockSize);
prg.GlobalData = new Uint8Array(0);
var dataSize = Math.min(dataView.getUint32(0x30),
(VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE));
if (dataSize !== 0) {
var len = dataSize + VM_FIXEDGLOBALSIZE;
prg.GlobalData = new Uint8Array(len);
prg.GlobalData.set(mem.subarray(VM_GLOBALMEMADDR, VM_GLOBALMEMADDR + len));
}
};
/**
* @param {Array<VmPreparedCommand>} preparedCodes
* @return {boolean}
*/
RarVM.prototype.executeCode = function(preparedCodes) {
var codeIndex = 0;
var cmd = preparedCodes[codeIndex];
// TODO: Why is this an infinite loop instead of just returning
// when a VM_RET is hit?
while (1) {
switch (cmd.OpCode) {
case VmCommands.VM_RET:
if (this.R_[7] >= VM_MEMSIZE) {
return true;
}
//SET_IP(GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK]));
this.R_[7] += 4;
continue;
case VmCommands.VM_STANDARD:
this.executeStandardFilter(cmd.Op1.Data);
break;
default:
console.error("RarVM OpCode not supported: " + getDebugString(VmCommands, cmd.OpCode));
break;
} // switch (cmd.OpCode)
codeIndex++;
cmd = preparedCodes[codeIndex];
}
};
/**
* @param {number} filterType
*/
RarVM.prototype.executeStandardFilter = function(filterType) {
switch (filterType) {
case VmStandardFilters.VMSF_DELTA:
var dataSize = this.R_[4];
var channels = this.R_[0];
var srcPos = 0;
var border = dataSize * 2;
//SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize);
var dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR);
dataView.setUint32(0x20, dataSize, true /* little endian */ );
if (dataSize >= VM_GLOBALMEMADDR / 2) {
break;
}
// Bytes from same channels are grouped to continual data blocks,
// so we need to place them back to their interleaving positions.
for (var curChannel = 0; curChannel < channels; ++curChannel) {
var prevByte = 0;
for (var destPos = dataSize + curChannel; destPos < border; destPos += channels) {
prevByte = (prevByte - this.mem_[srcPos++]) & 0xff;
this.mem_[destPos] = prevByte;
}
}
break;
default:
console.error("RarVM Standard Filter not supported: " + getDebugString(VmStandardFilters, filterType));
break;
}
};
/**
* @param {Uint8Array} code
* @param {VmPreparedProgram} prg
*/
RarVM.prototype.prepare = function(code, prg) {
var codeSize = code.length;
var i;
var curCmd;
//InitBitInput();
//memcpy(InBuf,Code,Min(CodeSize,BitInput::MAX_SIZE));
var bstream = new bitjs.io.BitStream(code.buffer, true /* rtl */ );
// Calculate the single byte XOR checksum to check validity of VM code.
var xorSum = 0;
for (i = 1; i < codeSize; ++i) {
xorSum ^= code[i];
}
bstream.readBits(8);
prg.Cmd = []; // TODO: Is this right? I don't see it being done in rarvm.cpp.
// VM code is valid if equal.
if (xorSum === code[0]) {
var filterType = this.isStandardFilter(code);
if (filterType !== VmStandardFilters.VMSF_NONE) {
// VM code is found among standard filters.
curCmd = new VmPreparedCommand();
prg.Cmd.push(curCmd);
curCmd.OpCode = VmCommands.VM_STANDARD;
curCmd.Op1.Data = filterType;
// TODO: Addr=&CurCmd->Op1.Data
curCmd.Op1.Addr = [curCmd.Op1.Data];
curCmd.Op2.Addr = [null]; // &CurCmd->Op2.Data;
curCmd.Op1.Type = VmOpType.VM_OPNONE;
curCmd.Op2.Type = VmOpType.VM_OPNONE;
codeSize = 0;
}
var dataFlag = bstream.readBits(1);
// Read static data contained in DB operators. This data cannot be
// changed, it is a part of VM code, not a filter parameter.
if (dataFlag & 0x8000) {
var dataSize = RarVM.readData(bstream) + 1;
// TODO: This accesses the byte pointer of the bstream directly. Is that ok?
for (i = 0; i < bstream.bytePtr < codeSize && i < dataSize; ++i) {
// Append a byte to the program's static data.
var newStaticData = new Uint8Array(prg.StaticData.length + 1);
newStaticData.set(prg.StaticData);
newStaticData[newStaticData.length - 1] = bstream.readBits(8);
prg.StaticData = newStaticData;
}
}
while (bstream.bytePtr < codeSize) {
curCmd = new VmPreparedCommand();
prg.Cmd.push(curCmd); // Prg->Cmd.Add(1)
var flag = bstream.peekBits(1);
if (!flag) { // (Data&0x8000)==0
curCmd.OpCode = bstream.readBits(4);
} else {
curCmd.OpCode = (bstream.readBits(6) - 24);
}
if (VmCmdFlags[curCmd.OpCode] & VMCF_BYTEMODE) {
curCmd.ByteMode = (bstream.readBits(1) !== 0);
} else {
curCmd.ByteMode = 0;
}
curCmd.Op1.Type = VmOpType.VM_OPNONE;
curCmd.Op2.Type = VmOpType.VM_OPNONE;
var opNum = (VmCmdFlags[curCmd.OpCode] & VMCF_OPMASK);
curCmd.Op1.Addr = null;
curCmd.Op2.Addr = null;
if (opNum > 0) {
this.decodeArg(curCmd.Op1, curCmd.ByteMode, bstream); // reading the first operand
if (opNum === 2) {
this.decodeArg(curCmd.Op2, curCmd.ByteMode, bstream); // reading the second operand
} else {
if (curCmd.Op1.Type === VmOpType.VM_OPINT && (VmCmdFlags[curCmd.OpCode] & (VMCF_JUMP | VMCF_PROC))) {
// Calculating jump distance.
var distance = curCmd.Op1.Data;
if (distance >= 256) {
distance -= 256;
} else {
if (distance >= 136) {
distance -= 264;
} else {
if (distance >= 16) {
distance -= 8;
} else {
if (distance >= 8) {
distance -= 16;
}
}
}
distance += prg.Cmd.length;
}
curCmd.Op1.Data = distance;
}
}
} // if (OpNum>0)
} // while ((uint)InAddr<CodeSize)
} // if (XorSum==Code[0])
curCmd = new VmPreparedCommand();
prg.Cmd.push(curCmd);
curCmd.OpCode = VmCommands.VM_RET;
// TODO: Addr=&CurCmd->Op1.Data
curCmd.Op1.Addr = [curCmd.Op1.Data];
curCmd.Op2.Addr = [curCmd.Op2.Data];
curCmd.Op1.Type = VmOpType.VM_OPNONE;
curCmd.Op2.Type = VmOpType.VM_OPNONE;
// If operand 'Addr' field has not been set by DecodeArg calls above,
// let's set it to point to operand 'Data' field. It is necessary for
// VM_OPINT type operands (usual integers) or maybe if something was
// not set properly for other operands. 'Addr' field is required
// for quicker addressing of operand data.
for (i = 0; i < prg.Cmd.length; ++i) {
var cmd = prg.Cmd[i];
if (cmd.Op1.Addr === null) {
cmd.Op1.Addr = [cmd.Op1.Data];
}
if (cmd.Op2.Addr === null) {
cmd.Op2.Addr = [cmd.Op2.Data];
}
}
/*
#ifdef VM_OPTIMIZE
if (CodeSize!=0)
Optimize(Prg);
#endif
*/
};
/**
* @param {Uint8Array} arr The byte array to set a value in.
* @param {number} value The unsigned 32-bit value to set.
* @param {number} offset Offset into arr to start setting the value, defaults to 0.
*/
RarVM.prototype.setLowEndianValue = function(arr, value, offset) {
var i = offset || 0;
arr[i] = value & 0xff;
arr[i + 1] = (value >>> 8) & 0xff;
arr[i + 2] = (value >>> 16) & 0xff;
arr[i + 3] = (value >>> 24) & 0xff;
};
/**
* Sets a number of bytes of the VM memory at the given position from a
* source buffer of bytes.
* @param {number} pos The position in the VM memory to start writing to.
* @param {Uint8Array} buffer The source buffer of bytes.
* @param {number} dataSize The number of bytes to set.
*/
RarVM.prototype.setMemory = function(pos, buffer, dataSize) {
if (pos < VM_MEMSIZE) {
var numBytes = Math.min(dataSize, VM_MEMSIZE - pos);
for (var i = 0; i < numBytes; ++i) {
this.mem_[pos + i] = buffer[i];
}
}
};
/**
* Static function that reads in the next set of bits for the VM
* (might return 4, 8, 16 or 32 bits).
* @param {bitjs.io.BitStream} bstream A RTL bit stream.
* @return {number} The value of the bits read.
*/
RarVM.readData = function(bstream) {
// Read in the first 2 bits.
var flags = bstream.readBits(2);
switch (flags) { // Data&0xc000
// Return the next 4 bits.
case 0:
return bstream.readBits(4); // (Data>>10)&0xf
case 1: // 0x4000
// 0x3c00 => 0011 1100 0000 0000
if (bstream.peekBits(4) === 0) { // (Data&0x3c00)==0
// Skip the 4 zero bits.
bstream.readBits(4);
// Read in the next 8 and pad with 1s to 32 bits.
return (0xffffff00 | bstream.readBits(8)) >>> 0; // ((Data>>2)&0xff)
}
// Else, read in the next 8.
return bstream.readBits(8);
// Read in the next 16.
case 2: // 0x8000
var val = bstream.getBits();
bstream.readBits(16);
return val; //bstream.readBits(16);
// case 3
default:
return (bstream.readBits(16) << 16) | bstream.readBits(16);
}
};
// ============================================================================================== //
@@ -1,6 +1,8 @@
/** /**
* unrar.js * unrar.js
* *
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc. * Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15 * Copyright(c) 2011 antimatter15
* *
@@ -8,11 +10,14 @@
* *
* http://kthoom.googlecode.com/hg/docs/unrar.html * http://kthoom.googlecode.com/hg/docs/unrar.html
*/ */
/* global bitjs, importScripts */ /* global bitjs, importScripts, RarVM, Uint8Array, UnpackFilter */
/* global VM_FIXEDGLOBALSIZE, VM_GLOBALMEMSIZE, MAXWINMASK, VM_GLOBALMEMADDR, MAXWINSIZE */
// This file expects to be invoked as a Worker (see onmessage below). // This file expects to be invoked as a Worker (see onmessage below).
importScripts("io.js"); importScripts("../io/bitstream.js");
importScripts("../io/bytebuffer.js");
importScripts("archive.js"); importScripts("archive.js");
importScripts("rarvm.js");
// Progress variables. // Progress variables.
var currentFilename = ""; var currentFilename = "";
@@ -59,11 +64,15 @@ var MAIN_HEAD = 0x73,
// PROTECT_HEAD = 0x78, // PROTECT_HEAD = 0x78,
// SIGN_HEAD = 0x79, // SIGN_HEAD = 0x79,
// NEWSUB_HEAD = 0x7a, // NEWSUB_HEAD = 0x7a,
ENDARC_HEAD = 0x7b; ENDARC_HEAD = 0x7b;
// bstream is a bit stream // ============================================================================================== //
/**
* @param {bitjs.io.BitStream} bstream
* @constructor
*/
var RarVolumeHeader = function(bstream) { var RarVolumeHeader = function(bstream) {
var headPos = bstream.bytePtr; var headPos = bstream.bytePtr;
// byte 1,2 // byte 1,2
info("Rar Volume Header @" + bstream.bytePtr); info("Rar Volume Header @" + bstream.bytePtr);
@@ -197,15 +206,18 @@ var RarVolumeHeader = function(bstream) {
} }
while (headPos + this.headSize > bstream.bytePtr) bstream.readBits(1); while (headPos + this.headSize > bstream.bytePtr) {
bstream.readBits(1);
}
info("Found FILE_HEAD with packSize=" + this.packSize + ", unpackedSize= " + this.unpackedSize + ", hostOS=" + this.hostOS + ", unpVer=" + this.unpVer + ", method=" + this.method + ", filename=" + this.filename); // If Info line is commented in firefox fails if server on same computer than browser with error "expected expression, got default"
//info("Found FILE_HEAD with packSize=" + this.packSize + ", unpackedSize= " + this.unpackedSize + ", hostOS=" + this.hostOS + ", unpVer=" + this.unpVer + ", method=" + this.method + ", filename=" + this.filename);
break; break;
default: default:
info("Found a header of type 0x" + byteValueToHexString(this.headType)); info("Found a header of type 0x" + byteValueToHexString(this.headType));
// skip the rest of the header bytes (for now) // skip the rest of the header bytes (for now)
bstream.readBytes( this.headSize - 7 ); bstream.readBytes(this.headSize - 7);
break; break;
} }
}; };
@@ -213,20 +225,22 @@ var RarVolumeHeader = function(bstream) {
//var BLOCK_LZ = 0; //var BLOCK_LZ = 0;
var rLDecode = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224], var rLDecode = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224],
rLBits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5], rLBits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
rDBitLengthCounts = [4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12], rDBitLengthCounts = [4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12],
rSDDecode = [0, 4, 8, 16, 32, 64, 128, 192], rSDDecode = [0, 4, 8, 16, 32, 64, 128, 192],
rSDBits = [2, 2, 3, 4, 5, 6, 6, 6]; rSDBits = [2, 2, 3, 4, 5, 6, 6, 6];
var rDDecode = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, var rDDecode = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32,
48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072,
4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304,
131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824, 131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824,
655360, 720896, 786432, 851968, 917504, 983040]; 655360, 720896, 786432, 851968, 917504, 983040
];
var rDBits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, var rDBits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5,
5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14,
15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]; 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16
];
var rLowDistRepCount = 16; var rLowDistRepCount = 16;
@@ -266,15 +280,51 @@ var RD = { //rep decode
DecodeNum: new Array(rRC) DecodeNum: new Array(rRC)
}; };
/**
* @type {Array<bitjs.io.ByteBuffer>}
*/
var rOldBuffers = [];
/**
* The current buffer we are unpacking to.
* @type {bitjs.io.ByteBuffer}
*/
var rBuffer; var rBuffer;
// read in Huffman tables for RAR /**
* The buffer of the final bytes after filtering (only used in Unpack29).
* @type {bitjs.io.ByteBuffer}
*/
var wBuffer;
var lowDistRepCount = 0;
var prevLowDist = 0;
var rOldDist = [0, 0, 0, 0];
var lastDist;
var lastLength;
/**
* In unpack.cpp, UnpPtr keeps track of what bytes have been unpacked
* into the Window buffer and WrPtr keeps track of what bytes have been
* actually written to disk after the unpacking and optional filtering
* has been done.
*
* In our case, rBuffer is the buffer for the unpacked bytes and wBuffer is
* the final output bytes.
*/
/**
* Read in Huffman tables for RAR
* @param {bitjs.io.BitStream} bstream
*/
function rarReadTables(bstream) { function rarReadTables(bstream) {
var BitLength = new Array(rBC), var BitLength = new Array(rBC);
Table = new Array(rHuffTableSize); var Table = new Array(rHuffTableSize);
var i; var i;
// before we start anything we need to get byte-aligned // before we start anything we need to get byte-aligned
bstream.readBits( (8 - bstream.bitPtr) & 0x7 ); bstream.readBits((8 - bstream.bitPtr) & 0x7);
if (bstream.readBits(1)) { if (bstream.readBits(1)) {
info("Error! PPM not implemented yet"); info("Error! PPM not implemented yet");
@@ -282,12 +332,13 @@ function rarReadTables(bstream) {
} }
if (!bstream.readBits(1)) { //discard old table if (!bstream.readBits(1)) { //discard old table
for (i = UnpOldTable.length; i--;) UnpOldTable[i] = 0; for (i = UnpOldTable.length; i--;) {
UnpOldTable[i] = 0;
}
} }
// read in bit lengths // read in bit lengths
for (var I = 0; I < rBC; ++I) { for (var I = 0; I < rBC; ++I) {
var Length = bstream.readBits(4); var Length = bstream.readBits(4);
if (Length === 15) { if (Length === 15) {
var ZeroCount = bstream.readBits(4); var ZeroCount = bstream.readBits(4);
@@ -346,24 +397,26 @@ function rarReadTables(bstream) {
function rarDecodeNumber(bstream, dec) { function rarDecodeNumber(bstream, dec) {
var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum; var DecodeLen = dec.DecodeLen,
DecodePos = dec.DecodePos,
DecodeNum = dec.DecodeNum;
var bitField = bstream.getBits() & 0xfffe; var bitField = bstream.getBits() & 0xfffe;
//some sort of rolled out binary search //some sort of rolled out binary search
var bits = ((bitField < DecodeLen[8]) ? var bits = ((bitField < DecodeLen[8]) ?
((bitField < DecodeLen[4]) ? ((bitField < DecodeLen[4]) ?
((bitField < DecodeLen[2]) ? ((bitField < DecodeLen[2]) ?
((bitField < DecodeLen[1]) ? 1 : 2) ((bitField < DecodeLen[1]) ? 1 : 2) :
: ((bitField < DecodeLen[3]) ? 3 : 4)) ((bitField < DecodeLen[3]) ? 3 : 4)) :
: (bitField < DecodeLen[6]) ? (bitField < DecodeLen[6]) ?
((bitField < DecodeLen[5]) ? 5 : 6) ((bitField < DecodeLen[5]) ? 5 : 6) :
: ((bitField < DecodeLen[7]) ? 7 : 8)) ((bitField < DecodeLen[7]) ? 7 : 8)) :
: ((bitField < DecodeLen[12]) ? ((bitField < DecodeLen[12]) ?
((bitField < DecodeLen[10]) ? ((bitField < DecodeLen[10]) ?
((bitField < DecodeLen[9]) ? 9 : 10) ((bitField < DecodeLen[9]) ? 9 : 10) :
: ((bitField < DecodeLen[11]) ? 11 : 12)) ((bitField < DecodeLen[11]) ? 11 : 12)) :
: (bitField < DecodeLen[14]) ? (bitField < DecodeLen[14]) ?
((bitField < DecodeLen[13]) ? 13 : 14) ((bitField < DecodeLen[13]) ? 13 : 14) :
: 15)); 15));
bstream.readBits(bits); bstream.readBits(bits);
var N = DecodePos[bits] + ((bitField - DecodeLen[bits - 1]) >>> (16 - bits)); var N = DecodePos[bits] + ((bitField - DecodeLen[bits - 1]) >>> (16 - bits));
@@ -372,12 +425,17 @@ function rarDecodeNumber(bstream, dec) {
function rarMakeDecodeTables(BitLength, offset, dec, size) { function rarMakeDecodeTables(BitLength, offset, dec, size) {
var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum; var DecodeLen = dec.DecodeLen;
var LenCount = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], var DecodePos = dec.DecodePos;
TmpPos = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], var DecodeNum = dec.DecodeNum;
N = 0, M = 0; var LenCount = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var TmpPos = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var N = 0;
var M = 0;
var i; var i;
for (i = DecodeNum.length; i--;) DecodeNum[i] = 0; for (i = DecodeNum.length; i--;) {
DecodeNum[i] = 0;
}
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
LenCount[BitLength[i + offset] & 0xF]++; LenCount[BitLength[i + offset] & 0xF]++;
} }
@@ -399,24 +457,27 @@ function rarMakeDecodeTables(BitLength, offset, dec, size) {
} }
for (I = 0; I < size; ++I) { for (I = 0; I < size; ++I) {
if (BitLength[I + offset] !== 0) { if (BitLength[I + offset] !== 0) {
DecodeNum[ TmpPos[ BitLength[offset + I] & 0xF ]++] = I; DecodeNum[TmpPos[BitLength[offset + I] & 0xF]++] = I;
} }
} }
} }
// TODO: implement // TODO: implement
function Unpack15() { //bstream, Solid) { /**
* @param {bitjs.io.BitStream} bstream
* @param {boolean} Solid
*/
function unpack15() { //bstream, Solid) {
info("ERROR! RAR 1.5 compression not supported"); info("ERROR! RAR 1.5 compression not supported");
} }
var lowDistRepCount = 0, prevLowDist = 0; /**
* Unpacks the bit stream into rBuffer using the Unpack20 algorithm.
var rOldDist = [0, 0, 0, 0]; * @param {bitjs.io.BitStream} bstream
* @param {boolean} Solid
var lastDist = 0; */
var lastLength = 0; function unpack20(bstream) { //, Solid) {
function Unpack20(bstream) { //, Solid) {
var destUnpSize = rBuffer.data.length; var destUnpSize = rBuffer.data.length;
var oldDistPtr = 0; var oldDistPtr = 0;
var Length; var Length;
@@ -441,7 +502,9 @@ function Unpack20(bstream) { //, Solid) {
} }
if (Distance >= 0x2000) { if (Distance >= 0x2000) {
Length++; Length++;
if (Distance >= 0x40000) Length++; if (Distance >= 0x40000) {
Length++;
}
} }
lastLength = Length; lastLength = Length;
lastDist = rOldDist[oldDistPtr++ & 3] = Distance; lastDist = rOldDist[oldDistPtr++ & 3] = Distance;
@@ -450,9 +513,7 @@ function Unpack20(bstream) { //, Solid) {
} }
if (num === 269) { if (num === 269) {
rarReadTables20(bstream); rarReadTables20(bstream);
rarUpdateProgress(); rarUpdateProgress();
continue; continue;
} }
if (num === 256) { if (num === 256) {
@@ -471,7 +532,9 @@ function Unpack20(bstream) { //, Solid) {
Length++; Length++;
if (Distance >= 0x2000) { if (Distance >= 0x2000) {
Length++; Length++;
if (Distance >= 0x40000) Length++; if (Distance >= 0x40000) {
Length++;
}
} }
} }
lastLength = Length; lastLength = Length;
@@ -500,7 +563,6 @@ function rarUpdateProgress() {
postProgress(); postProgress();
} }
var rNC20 = 298, var rNC20 = 298,
rDC20 = 48, rDC20 = 48,
rRC20 = 28, rRC20 = 28,
@@ -516,7 +578,9 @@ function rarReadTables20(bstream) {
var i; var i;
bstream.readBits(1); bstream.readBits(1);
if (!bstream.readBits(1)) { if (!bstream.readBits(1)) {
for (i = UnpOldTable20.length; i--;) UnpOldTable20[i] = 0; for (i = UnpOldTable20.length; i--;) {
UnpOldTable20[i] = 0;
}
} }
TableSize = rNC20 + rDC20 + rRC20; TableSize = rNC20 + rDC20 + rRC20;
for (I = 0; I < rBC20; I++) { for (I = 0; I < rBC20; I++) {
@@ -549,11 +613,248 @@ function rarReadTables20(bstream) {
rarMakeDecodeTables(Table, 0, LD, rNC20); rarMakeDecodeTables(Table, 0, LD, rNC20);
rarMakeDecodeTables(Table, rNC20, DD, rDC20); rarMakeDecodeTables(Table, rNC20, DD, rDC20);
rarMakeDecodeTables(Table, rNC20 + rDC20, RD, rRC20); rarMakeDecodeTables(Table, rNC20 + rDC20, RD, rRC20);
for (i = UnpOldTable20.length; i--;) UnpOldTable20[i] = Table[i]; for (i = UnpOldTable20.length; i--;) {
UnpOldTable20[i] = Table[i];
}
}
// ============================================================================================== //
// Unpack code specific to RarVM
var VM = new RarVM();
/**
* Filters code, one entry per filter.
* @type {Array<UnpackFilter>}
*/
var Filters = [];
/**
* Filters stack, several entrances of same filter are possible.
* @type {Array<UnpackFilter>}
*/
var PrgStack = [];
/**
* Lengths of preceding blocks, one length per filter. Used to reduce
* size required to write block length if lengths are repeating.
* @type {Array<number>}
*/
var OldFilterLengths = [];
var LastFilter = 0;
function initFilters() {
OldFilterLengths = [];
LastFilter = 0;
Filters = [];
PrgStack = [];
} }
function Unpack29(bstream) { /**
* @param {number} firstByte The first byte (flags).
* @param {Uint8Array} vmCode An array of bytes.
*/
function rarAddVMCode(firstByte, vmCode) {
VM.init();
var i;
var bstream = new bitjs.io.BitStream(vmCode.buffer, true /* rtl */ );
var filtPos;
if (firstByte & 0x80) {
filtPos = RarVM.readData(bstream);
if (filtPos === 0) {
initFilters();
} else {
filtPos--;
}
} else {
filtPos = LastFilter;
}
if (filtPos > Filters.length || filtPos > OldFilterLengths.length) {
return false;
}
LastFilter = filtPos;
var newFilter = (filtPos === Filters.length);
// new filter for PrgStack
var stackFilter = new UnpackFilter();
var filter = null;
// new filter code, never used before since VM reset
if (newFilter) {
// too many different filters, corrupt archive
if (filtPos > 1024) {
return false;
}
filter = new UnpackFilter();
Filters.push(filter);
stackFilter.ParentFilter = (Filters.length - 1);
OldFilterLengths.push(0); // OldFilterLengths.Add(1)
filter.ExecCount = 0;
} else { // filter was used in the past
filter = Filters[filtPos];
stackFilter.ParentFilter = filtPos;
filter.ExecCount++;
}
var emptyCount = 0;
for (i = 0; i < PrgStack.length; ++i) {
PrgStack[i - emptyCount] = PrgStack[i];
if (PrgStack[i] === null) {
emptyCount++;
}
if (emptyCount > 0) {
PrgStack[i] = null;
}
}
if (emptyCount === 0) {
PrgStack.push(null); //PrgStack.Add(1);
emptyCount = 1;
}
var stackPos = PrgStack.length - emptyCount;
PrgStack[stackPos] = stackFilter;
stackFilter.ExecCount = filter.ExecCount;
var blockStart = RarVM.readData(bstream);
if (firstByte & 0x40) {
blockStart += 258;
}
stackFilter.BlockStart = (blockStart + rBuffer.ptr) & MAXWINMASK;
if (firstByte & 0x20) {
stackFilter.BlockLength = RarVM.readData(bstream);
} else {
stackFilter.BlockLength = filtPos < OldFilterLengths.length ?
OldFilterLengths[filtPos] :
0;
}
stackFilter.NextWindow = (wBuffer.ptr !== rBuffer.ptr) &&
(((wBuffer.ptr - rBuffer.ptr) & MAXWINMASK) <= blockStart);
OldFilterLengths[filtPos] = stackFilter.BlockLength;
for (i = 0; i < 7; ++i) {
stackFilter.Prg.InitR[i] = 0;
}
stackFilter.Prg.InitR[3] = VM_GLOBALMEMADDR;
stackFilter.Prg.InitR[4] = stackFilter.BlockLength;
stackFilter.Prg.InitR[5] = stackFilter.ExecCount;
// set registers to optional parameters if any
if (firstByte & 0x10) {
var initMask = bstream.readBits(7);
for (i = 0; i < 7; ++i) {
if (initMask & (1 << i)) {
stackFilter.Prg.InitR[i] = RarVM.readData(bstream);
}
}
}
if (newFilter) {
var vmCodeSize = RarVM.readData(bstream);
if (vmCodeSize >= 0x10000 || vmCodeSize === 0) {
return false;
}
vmCode = new Uint8Array(vmCodeSize);
for (i = 0; i < vmCodeSize; ++i) {
//if (Inp.Overflow(3))
// return(false);
vmCode[i] = bstream.readBits(8);
}
VM.prepare(vmCode, filter.Prg);
}
stackFilter.Prg.Cmd = filter.Prg.Cmd;
stackFilter.Prg.AltCmd = filter.Prg.Cmd;
var staticDataSize = filter.Prg.StaticData.length;
if (staticDataSize > 0 && staticDataSize < VM_GLOBALMEMSIZE) {
// read statically defined data contained in DB commands
for (i = 0; i < staticDataSize; ++i) {
stackFilter.Prg.StaticData[i] = filter.Prg.StaticData[i];
}
}
if (stackFilter.Prg.GlobalData.length < VM_FIXEDGLOBALSIZE) {
stackFilter.Prg.GlobalData = new Uint8Array(VM_FIXEDGLOBALSIZE);
}
var globalData = stackFilter.Prg.GlobalData;
for (i = 0; i < 7; ++i) {
VM.setLowEndianValue(globalData, stackFilter.Prg.InitR[i], i * 4);
}
VM.setLowEndianValue(globalData, stackFilter.BlockLength, 0x1c);
VM.setLowEndianValue(globalData, 0, 0x20);
VM.setLowEndianValue(globalData, stackFilter.ExecCount, 0x2c);
for (i = 0; i < 16; ++i) {
globalData[0x30 + i] = 0;
}
// put data block passed as parameter if any
if (firstByte & 8) {
//if (Inp.Overflow(3))
// return(false);
var dataSize = RarVM.readData(bstream);
if (dataSize > (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE)) {
return (false);
}
var curSize = stackFilter.Prg.GlobalData.length;
if (curSize < dataSize + VM_FIXEDGLOBALSIZE) {
// Resize global data and update the stackFilter and local variable.
var numBytesToAdd = dataSize + VM_FIXEDGLOBALSIZE - curSize;
var newGlobalData = new Uint8Array(globalData.length + numBytesToAdd);
newGlobalData.set(globalData);
stackFilter.Prg.GlobalData = newGlobalData;
globalData = newGlobalData;
}
//byte *GlobalData=&StackFilter->Prg.GlobalData[VM_FIXEDGLOBALSIZE];
for (i = 0; i < dataSize; ++i) {
//if (Inp.Overflow(3))
// return(false);
globalData[VM_FIXEDGLOBALSIZE + i] = bstream.readBits(8);
}
}
return true;
}
/**
* @param {!bitjs.io.BitStream} bstream
*/
function rarReadVMCode(bstream) {
var firstByte = bstream.readBits(8);
var length = (firstByte & 7) + 1;
if (length === 7) {
length = bstream.readBits(8) + 7;
} else if (length === 8) {
length = bstream.readBits(16);
}
// Read all bytes of VM code into an array.
var vmCode = new Uint8Array(length);
for (var i = 0; i < length; i++) {
// Do something here with checking readbuf.
vmCode[i] = bstream.readBits(8);
}
return rarAddVMCode(firstByte, vmCode);
}
/**
* Unpacks the bit stream into rBuffer using the Unpack29 algorithm.
* @param {bitjs.io.BitStream} bstream
* @param {boolean} Solid
*/
function unpack29(bstream) {
// lazy initialize rDDecode and rDBits // lazy initialize rDDecode and rDBits
var DDecode = new Array(rDC); var DDecode = new Array(rDC);
@@ -577,7 +878,9 @@ function Unpack29(bstream) {
lastDist = 0; lastDist = 0;
lastLength = 0; lastLength = 0;
var i; var i;
for (i = UnpOldTable.length; i--;) UnpOldTable[i] = 0; for (i = UnpOldTable.length; i--;) {
UnpOldTable[i] = 0;
}
// read in Huffman tables // read in Huffman tables
rarReadTables(bstream); rarReadTables(bstream);
@@ -632,16 +935,19 @@ function Unpack29(bstream) {
continue; continue;
} }
if (num === 256) { if (num === 256) {
if (!rarReadEndOfBlock(bstream)) break; if (!rarReadEndOfBlock(bstream)) {
break;
}
continue; continue;
} }
if (num === 257) { if (num === 257) {
//console.log("READVMCODE"); if (!rarReadVMCode(bstream)) {
if (!rarReadVMCode(bstream)) break; break;
}
continue; continue;
} }
if (num === 258) { if (num === 258) {
if (lastLength != 0) { if (lastLength !== 0) {
rarCopyString(lastLength, lastDist); rarCopyString(lastLength, lastDist);
} }
continue; continue;
@@ -650,8 +956,8 @@ function Unpack29(bstream) {
var DistNum = num - 259; var DistNum = num - 259;
Distance = rOldDist[DistNum]; Distance = rOldDist[DistNum];
for (var I = DistNum; I > 0; I--) { for (var I2 = DistNum; I2 > 0; I2--) {
rOldDist[I] = rOldDist[I - 1]; rOldDist[I2] = rOldDist[I2 - 1];
} }
rOldDist[0] = Distance; rOldDist[0] = Distance;
@@ -674,15 +980,195 @@ function Unpack29(bstream) {
rarCopyString(2, Distance); rarCopyString(2, Distance);
continue; continue;
} }
} } // while (true)
rarUpdateProgress(); rarUpdateProgress();
rarWriteBuf();
}
/**
* Does stuff to the current byte buffer (rBuffer) based on
* the filters loaded into the RarVM and writes out to wBuffer.
*/
function rarWriteBuf() {
var writeSize = (rBuffer.ptr & MAXWINMASK);
var j;
var flt;
for (var i = 0; i < PrgStack.length; ++i) {
flt = PrgStack[i];
if (flt === null) {
continue;
}
if (flt.NextWindow) {
flt.NextWindow = false;
continue;
}
var blockStart = flt.BlockStart;
var blockLength = flt.BlockLength;
var parentPrg;
// WrittenBorder = wBuffer.ptr
if (((blockStart - wBuffer.ptr) & MAXWINMASK) < writeSize) {
if (wBuffer.ptr !== blockStart) {
// Copy blockStart bytes from rBuffer into wBuffer.
rarWriteArea(wBuffer.ptr, blockStart);
writeSize = (rBuffer.ptr - wBuffer.ptr) & MAXWINMASK;
}
if (blockLength <= writeSize) {
var blockEnd = (blockStart + blockLength) & MAXWINMASK;
if (blockStart < blockEnd || blockEnd === 0) {
VM.setMemory(0, rBuffer.data.subarray(blockStart, blockStart + blockLength), blockLength);
} else {
var firstPartLength = MAXWINSIZE - blockStart;
VM.setMemory(0, rBuffer.data.subarray(blockStart, blockStart + firstPartLength), firstPartLength);
VM.setMemory(firstPartLength, rBuffer.data, blockEnd);
}
parentPrg = Filters[flt.ParentFilter].Prg;
var prg = flt.Prg;
if (parentPrg.GlobalData.length > VM_FIXEDGLOBALSIZE) {
// Copy global data from previous script execution if any.
prg.GlobalData = new Uint8Array(parentPrg.GlobalData);
}
rarExecuteCode(prg);
var globalDataLen;
if (prg.GlobalData.length > VM_FIXEDGLOBALSIZE) {
// Save global data for next script execution.
globalDataLen = prg.GlobalData.length;
if (parentPrg.GlobalData.length < globalDataLen) {
parentPrg.GlobalData = new Uint8Array(globalDataLen);
}
parentPrg.GlobalData.set(
this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen),
VM_FIXEDGLOBALSIZE);
} else {
parentPrg.GlobalData = new Uint8Array(0);
}
var filteredData = prg.FilteredData;
PrgStack[i] = null;
while (i + 1 < PrgStack.length) {
var nextFilter = PrgStack[i + 1];
if (nextFilter === null || nextFilter.BlockStart !== blockStart ||
nextFilter.BlockLength !== filteredData.length || nextFilter.NextWindow) {
break;
}
// Apply several filters to same data block.
VM.setMemory(0, filteredData, filteredData.length);
parentPrg = Filters[nextFilter.ParentFilter].Prg;
var nextPrg = nextFilter.Prg;
globalDataLen = parentPrg.GlobalData.length;
if (globalDataLen > VM_FIXEDGLOBALSIZE) {
// Copy global data from previous script execution if any.
nextPrg.GlobalData = new Uint8Array(globalDataLen);
nextPrg.GlobalData.set(parentPrg.GlobalData.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), VM_FIXEDGLOBALSIZE);
}
rarExecuteCode(nextPrg);
if (nextPrg.GlobalData.length > VM_GLOBALMEMSIZE) {
// Save global data for next script execution.
globalDataLen = nextPrg.GlobalData.length;
if (parentPrg.GlobalData.length < globalDataLen) {
parentPrg.GlobalData = new Uint8Array(globalDataLen);
}
parentPrg.GlobalData.set(
this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen),
VM_FIXEDGLOBALSIZE);
} else {
parentPrg.GlobalData = new Uint8Array(0);
}
filteredData = nextPrg.FilteredData;
i++;
PrgStack[i] = null;
} // while (i + 1 < PrgStack.length)
for (j = 0; j < filteredData.length; ++j) {
wBuffer.insertByte(filteredData[j]);
}
writeSize = (rBuffer.ptr - wBuffer.ptr) & MAXWINMASK;
} else { // if (blockLength <= writeSize)
for (j = i; j < PrgStack.length; ++j) {
flt = PrgStack[j];
if (flt !== null && flt.NextWindow) {
flt.NextWindow = false;
}
}
//WrPtr=WrittenBorder;
return;
}
} // if (((blockStart - wBuffer.ptr) & MAXWINMASK) < writeSize)
} // for (var i = 0; i < PrgStack.length; ++i)
// Write any remaining bytes from rBuffer to wBuffer;
rarWriteArea(wBuffer.ptr, rBuffer.ptr);
// Now that the filtered buffer has been written, swap it back to rBuffer.
rBuffer = wBuffer;
}
/**
* Copy bytes from rBuffer to wBuffer.
* @param {number} startPtr The starting point to copy from rBuffer.
* @param {number} endPtr The ending point to copy from rBuffer.
*/
function rarWriteArea(startPtr, endPtr) {
if (endPtr < startPtr) {
console.error("endPtr < startPtr, endPtr=" + endPtr + ", startPtr=" + startPtr);
// rarWriteData(startPtr, -(int)StartPtr & MAXWINMASK);
// RarWriteData(0, endPtr);
return;
} else if (startPtr < endPtr) {
rarWriteData(startPtr, endPtr - startPtr);
}
}
/**
* Writes bytes into wBuffer from rBuffer.
* @param {number} offset The starting point to copy bytes from rBuffer.
* @param {number} numBytes The number of bytes to copy.
*/
function rarWriteData(offset, numBytes) {
if (wBuffer.ptr >= rBuffer.data.length) {
return;
}
var leftToWrite = rBuffer.data.length - wBuffer.ptr;
if (numBytes > leftToWrite) {
numBytes = leftToWrite;
}
for (var i = 0; i < numBytes; ++i) {
wBuffer.insertByte(rBuffer.data[offset + i]);
}
}
/**
* @param {VM_PreparedProgram} prg
*/
function rarExecuteCode(prg) {
if (prg.GlobalData.length > 0) {
var writtenFileSize = wBuffer.ptr;
prg.InitR[6] = writtenFileSize;
VM.setLowEndianValue(prg.GlobalData, writtenFileSize, 0x24);
VM.setLowEndianValue(prg.GlobalData, (writtenFileSize >>> 32) >> 0, 0x28);
VM.execute(prg);
}
} }
function rarReadEndOfBlock(bstream) { function rarReadEndOfBlock(bstream) {
rarUpdateProgress(); rarUpdateProgress();
var NewTable = false, NewFile = false; var NewTable = false,
NewFile = false;
if (bstream.readBits(1)) { if (bstream.readBits(1)) {
NewTable = true; NewTable = true;
} else { } else {
@@ -690,32 +1176,7 @@ function rarReadEndOfBlock(bstream) {
NewTable = !!bstream.readBits(1); NewTable = !!bstream.readBits(1);
} }
//tablesRead = !NewTable; //tablesRead = !NewTable;
return !(NewFile || NewTable && !rarReadTables(bstream)); return !(NewFile || (NewTable && !rarReadTables(bstream)));
}
function rarReadVMCode(bstream) {
var FirstByte = bstream.readBits(8);
var Length = (FirstByte & 7) + 1;
if (Length === 7) {
Length = bstream.readBits(8) + 7;
} else if (Length === 8) {
Length = bstream.readBits(16);
}
var vmCode = [];
for (var I = 0; I < Length; I++) {
//do something here with cheking readbuf
vmCode.push(bstream.readBits(8));
}
return RarAddVMCode(FirstByte, vmCode, Length);
}
function RarAddVMCode(firstByte, vmCode, length) {
//console.log(vmCode);
if (vmCode.length > 0) {
info("Error! RarVM not supported yet!");
}
return true;
} }
function rarInsertLastMatch(length, distance) { function rarInsertLastMatch(length, distance) {
@@ -728,63 +1189,73 @@ function rarInsertOldDist(distance) {
rOldDist.splice(0, 0, distance); rOldDist.splice(0, 0, distance);
} }
//this is the real function, the other one is for debugging /**
* Copies len bytes from distance bytes ago in the buffer to the end of the
* current byte buffer.
* @param {number} length How many bytes to copy.
* @param {number} distance How far back in the buffer from the current write
* pointer to start copying from.
*/
function rarCopyString(length, distance) { function rarCopyString(length, distance) {
var destPtr = rBuffer.ptr - distance; var srcPtr = rBuffer.ptr - distance;
if (destPtr < 0) { if (srcPtr < 0) {
var l = rOldBuffers.length; var l = rOldBuffers.length;
while (destPtr < 0) { while (srcPtr < 0) {
destPtr = rOldBuffers[--l].data.length + destPtr; srcPtr = rOldBuffers[--l].data.length + srcPtr;
}
// TODO: lets hope that it never needs to read beyond file boundaries
while (length--) {
rBuffer.insertByte(rOldBuffers[l].data[srcPtr++]);
} }
//TODO: lets hope that it never needs to read beyond file boundaries
while (length--) rBuffer.insertByte(rOldBuffers[l].data[destPtr++]);
} }
if (length > distance) { if (length > distance) {
while (length--) rBuffer.insertByte(rBuffer.data[destPtr++]); while (length--) {
rBuffer.insertByte(rBuffer.data[srcPtr++]);
}
} else { } else {
rBuffer.insertBytes(rBuffer.data.subarray(destPtr, destPtr + length)); rBuffer.insertBytes(rBuffer.data.subarray(srcPtr, srcPtr + length));
} }
} }
var rOldBuffers = []; /**
// v must be a valid RarVolume * @param {RarLocalFile} v
*/
function unpack(v) { function unpack(v) {
// TODO: implement what happens when unpVer is < 15 // TODO: implement what happens when unpVer is < 15
var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer, var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer;
Solid = v.header.LHD_SOLID, // var Solid = v.header.LHD_SOLID;
bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength ); var bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength);
rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize); rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize);
info("Unpacking " + v.filename + " RAR v" + Ver); info("Unpacking " + v.filename + " RAR v" + Ver);
switch(Ver) { switch (Ver) {
case 15: // rar 1.5 compression case 15: // rar 1.5 compression
Unpack15(); //(bstream, Solid); unpack15(); //(bstream, Solid);
break; break;
case 20: // rar 2.x compression case 20: // rar 2.x compression
case 26: // files larger than 2GB case 26: // files larger than 2GB
Unpack20(bstream); //, Solid); unpack20(bstream); //, Solid);
break; break;
case 29: // rar 3.x compression case 29: // rar 3.x compression
case 36: // alternative hash case 36: // alternative hash
Unpack29(bstream); wBuffer = new bitjs.io.ByteBuffer(rBuffer.data.length);
unpack29(bstream);
break; break;
} // switch(method) } // switch(method)
rOldBuffers.push(rBuffer); rOldBuffers.push(rBuffer);
//TODO: clear these old buffers when there's over 4MB of history // TODO: clear these old buffers when there's over 4MB of history
return rBuffer.data; return rBuffer.data;
} }
// bstream is a bit stream // bstream is a bit stream
var RarLocalFile = function(bstream) { var RarLocalFile = function(bstream) {
this.header = new RarVolumeHeader(bstream); this.header = new RarVolumeHeader(bstream);
this.filename = this.header.filename; this.filename = this.header.filename;
if (this.header.headType != FILE_HEAD && this.header.headType != ENDARC_HEAD) { if (this.header.headType !== FILE_HEAD && this.header.headType !== ENDARC_HEAD) {
this.isValid = false; this.isValid = false;
info("Error! RAR Volume did not include a FILE_HEAD header "); info("Error! RAR Volume did not include a FILE_HEAD header ");
} else { } else {
@@ -798,7 +1269,6 @@ var RarLocalFile = function(bstream) {
}; };
RarLocalFile.prototype.unrar = function() { RarLocalFile.prototype.unrar = function() {
if (!this.header.flags.LHD_SPLIT_BEFORE) { if (!this.header.flags.LHD_SPLIT_BEFORE) {
// unstore file // unstore file
if (this.header.method === 0x30) { if (this.header.method === 0x30) {
@@ -836,11 +1306,10 @@ var unrar = function(arrayBuffer) {
header.headType === 0x72 && header.headType === 0x72 &&
header.flags.value === 0x1A21 && header.flags.value === 0x1A21 &&
header.headSize === 7) { header.headSize === 7) {
info("Found RAR signature"); info("Found RAR signature");
var mhead = new RarVolumeHeader(bstream); var mhead = new RarVolumeHeader(bstream);
if (mhead.headType != MAIN_HEAD) { if (mhead.headType !== MAIN_HEAD) {
info("Error! RAR did not include a MAIN_HEAD header"); info("Error! RAR did not include a MAIN_HEAD header");
} else { } else {
var localFiles = []; var localFiles = [];
@@ -859,18 +1328,15 @@ var unrar = function(arrayBuffer) {
break; break;
} }
//info("bstream" + bstream.bytePtr+"/"+bstream.bytes.length); //info("bstream" + bstream.bytePtr+"/"+bstream.bytes.length);
} while ( localFile.isValid ); } while (localFile.isValid);
totalFilesInArchive = localFiles.length; totalFilesInArchive = localFiles.length;
// now we have all information but things are unpacked // now we have all information but things are unpacked
// TODO: unpack localFiles.sort(alphanumCase);
localFiles = localFiles.sort(function(a, b) {
var aname = a.filename.toLowerCase();
var bname = b.filename.toLowerCase();
return aname > bname ? 1 : -1;
});
info(localFiles.map(function(a) {return a.filename;}).join(", ")); info(localFiles.map(function(a) {
return a.filename;
}).join(", "));
for (var i = 0; i < localFiles.length; ++i) { for (var i = 0; i < localFiles.length; ++i) {
var localfile = localFiles[i]; var localfile = localFiles[i];
+179
View File
@@ -0,0 +1,179 @@
/**
* untar.js
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
*
* Reference Documentation:
*
* TAR format: http://www.gnu.org/software/automake/manual/tar/Standard.html
*/
/* global bitjs, importScripts, Uint8Array */
// This file expects to be invoked as a Worker (see onmessage below).
importScripts("../io/bytestream.js");
importScripts("archive.js");
// Progress variables.
var currentFilename = "";
var currentFileNumber = 0;
var currentBytesUnarchivedInFile = 0;
var currentBytesUnarchived = 0;
var totalUncompressedBytesInArchive = 0;
var totalFilesInArchive = 0;
var allLocalFiles = [];
// Helper functions.
var info = function(str) {
postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
};
var err = function(str) {
postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
};
// Removes all characters from the first zero-byte in the string onwards.
var readCleanString = function(bstr, numBytes) {
var str = bstr.readString(numBytes);
var zIndex = str.indexOf(String.fromCharCode(0));
return zIndex != -1 ? str.substr(0, zIndex) : str;
};
var postProgress = function() {
postMessage(new bitjs.archive.UnarchiveProgressEvent(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive
));
};
// takes a ByteStream and parses out the local file information
var TarLocalFile = function(bstream) {
this.isValid = false;
var bytesRead = 0;
// Read in the header block
this.name = readCleanString(bstream, 100);
this.mode = readCleanString(bstream, 8);
this.uid = readCleanString(bstream, 8);
this.gid = readCleanString(bstream, 8);
this.size = parseInt(readCleanString(bstream, 12), 8);
this.mtime = readCleanString(bstream, 12);
this.chksum = readCleanString(bstream, 8);
this.typeflag = readCleanString(bstream, 1);
this.linkname = readCleanString(bstream, 100);
this.maybeMagic = readCleanString(bstream, 6);
if (this.maybeMagic === "ustar") {
this.version = readCleanString(bstream, 2);
this.uname = readCleanString(bstream, 32);
this.gname = readCleanString(bstream, 32);
this.devmajor = readCleanString(bstream, 8);
this.devminor = readCleanString(bstream, 8);
this.prefix = readCleanString(bstream, 155);
if (this.prefix.length) {
this.name = this.prefix + this.name;
}
bstream.readBytes(12); // 512 - 500
} else {
bstream.readBytes(255); // 512 - 257
}
bytesRead += 512;
// Done header, now rest of blocks are the file contents.
this.filename = this.name;
this.fileData = null;
info("Untarring file '" + this.filename + "'");
info(" size = " + this.size);
info(" typeflag = " + this.typeflag);
// A regular file.
if (this.typeflag == 0) {
info(" This is a regular file.");
var sizeInBytes = parseInt(this.size);
this.fileData = new Uint8Array(bstream.readBytes(sizeInBytes));
bytesRead += sizeInBytes;
if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) {
this.isValid = true;
}
// Round up to 512-byte blocks.
var remaining = 512 - (bytesRead % 512);
if (remaining > 0 && remaining < 512) {
bstream.readBytes(remaining);
}
} else if (this.typeflag == 5) {
info(" This is a directory.");
}
};
var untar = function(arrayBuffer) {
postMessage(new bitjs.archive.UnarchiveStartEvent());
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
allLocalFiles = [];
var bstream = new bitjs.io.ByteStream(arrayBuffer);
postProgress();
/*
// go through whole file, read header of each block and memorize, filepointer
*/
while (bstream.peekNumber(4) !== 0) {
var localFile = new TarLocalFile(bstream);
allLocalFiles.push(localFile);
postProgress();
}
// got all local files, now sort them
allLocalFiles.sort(alphanumCase);
allLocalFiles.forEach(function(oneLocalFile) {
// While we don't encounter an empty block, keep making TarLocalFiles.
if (oneLocalFile && oneLocalFile.isValid) {
// If we make it to this point and haven't thrown an error, we have successfully
// read in the data for a local file, so we can update the actual bytestream.
totalUncompressedBytesInArchive += oneLocalFile.size;
// update progress
currentFilename = oneLocalFile.filename;
currentFileNumber = totalFilesInArchive++;
currentBytesUnarchivedInFile = oneLocalFile.size;
currentBytesUnarchived += oneLocalFile.size;
postMessage(new bitjs.archive.UnarchiveExtractEvent(oneLocalFile));
postProgress();
}
});
totalFilesInArchive = allLocalFiles.length;
postProgress();
postMessage(new bitjs.archive.UnarchiveFinishEvent());
};
// event.data.file has the first ArrayBuffer.
// event.data.bytes has all subsequent ArrayBuffers.
onmessage = function(event) {
try {
untar(event.data.file, true);
} catch (e) {
if (typeof e === "string" && e.startsWith("Error! Overflowed")) {
// Overrun the buffer.
// unarchiveState = UnarchiveState.WAITING;
} else {
err("Found an error while untarring");
err(e);
throw e;
}
}
};
@@ -1,6 +1,8 @@
/** /**
* unzip.js * unzip.js
* *
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc. * Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15 * Copyright(c) 2011 antimatter15
* *
@@ -12,7 +14,9 @@
/* global bitjs, importScripts, Uint8Array*/ /* global bitjs, importScripts, Uint8Array*/
// This file expects to be invoked as a Worker (see onmessage below). // This file expects to be invoked as a Worker (see onmessage below).
importScripts("io.js"); importScripts("../io/bitstream.js");
importScripts("../io/bytebuffer.js");
importScripts("../io/bytestream.js");
importScripts("archive.js"); importScripts("archive.js");
// Progress variables. // Progress variables.
@@ -47,7 +51,7 @@ var zDigitalSignatureSignature = 0x05054b50;
// takes a ByteStream and parses out the local file information // takes a ByteStream and parses out the local file information
var ZipLocalFile = function(bstream) { var ZipLocalFile = function(bstream) {
if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function() {}) { if (typeof bstream !== typeof {} || !bstream.readNumber || typeof bstream.readNumber !== typeof function() {}) {
return null; return null;
} }
@@ -68,23 +72,10 @@ var ZipLocalFile = function(bstream) {
this.filename = bstream.readString(this.fileNameLength); this.filename = bstream.readString(this.fileNameLength);
} }
info("Zip Local File Header:");
info(" version=" + this.version);
info(" general purpose=" + this.generalPurpose);
info(" compression method=" + this.compressionMethod);
info(" last mod file time=" + this.lastModFileTime);
info(" last mod file date=" + this.lastModFileDate);
info(" crc32=" + this.crc32);
info(" compressed size=" + this.compressedSize);
info(" uncompressed size=" + this.uncompressedSize);
info(" file name length=" + this.fileNameLength);
info(" extra field length=" + this.extraFieldLength);
info(" filename = '" + this.filename + "'");
this.extraField = null; this.extraField = null;
if (this.extraFieldLength > 0) { if (this.extraFieldLength > 0) {
this.extraField = bstream.readString(this.extraFieldLength); this.extraField = bstream.readString(this.extraFieldLength);
info(" extra field=" + this.extraField); info(" extra field=" + this.extraField);
} }
// read in the compressed data // read in the compressed data
@@ -98,29 +89,42 @@ var ZipLocalFile = function(bstream) {
// "This descriptor exists only if bit 3 of the general purpose bit flag is set" // "This descriptor exists only if bit 3 of the general purpose bit flag is set"
// But how do you figure out how big the file data is if you don't know the compressedSize // But how do you figure out how big the file data is if you don't know the compressedSize
// from the header?!? // from the header?!?
if ((this.generalPurpose & bitjs.BIT[3]) != 0) { if ((this.generalPurpose & bitjs.BIT[3]) !== 0) {
this.crc32 = bstream.readNumber(4); this.crc32 = bstream.readNumber(4);
this.compressedSize = bstream.readNumber(4); this.compressedSize = bstream.readNumber(4);
this.uncompressedSize = bstream.readNumber(4); this.uncompressedSize = bstream.readNumber(4);
} }
// Now that we have all the bytes for this file, we can print out some information.
info("Zip Local File Header:");
info(" version=" + this.version);
info(" general purpose=" + this.generalPurpose);
info(" compression method=" + this.compressionMethod);
info(" last mod file time=" + this.lastModFileTime);
info(" last mod file date=" + this.lastModFileDate);
info(" crc32=" + this.crc32);
info(" compressed size=" + this.compressedSize);
info(" uncompressed size=" + this.uncompressedSize);
info(" file name length=" + this.fileNameLength);
info(" extra field length=" + this.extraFieldLength);
info(" filename = '" + this.filename + "'");
}; };
// determine what kind of compressed data we have and decompress // determine what kind of compressed data we have and decompress
ZipLocalFile.prototype.unzip = function() { ZipLocalFile.prototype.unzip = function() {
// Zip Version 1.0, no compression (store only) // Zip Version 1.0, no compression (store only)
if (this.compressionMethod == 0 ) { if (this.compressionMethod === 0 ) {
info("ZIP v" + this.version + ", store only: " + this.filename + " (" + this.compressedSize + " bytes)"); info("ZIP v" + this.version + ", store only: " + this.filename + " (" + this.compressedSize + " bytes)");
currentBytesUnarchivedInFile = this.compressedSize; currentBytesUnarchivedInFile = this.compressedSize;
currentBytesUnarchived += this.compressedSize; currentBytesUnarchived += this.compressedSize;
this.fileData = zeroCompression(this.fileData, this.uncompressedSize); this.fileData = zeroCompression(this.fileData, this.uncompressedSize);
} } else if (this.compressionMethod === 8) {
// version == 20, compression method == 8 (DEFLATE) // version == 20, compression method == 8 (DEFLATE)
else if (this.compressionMethod == 8) {
info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)"); info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)");
this.fileData = inflate(this.fileData, this.uncompressedSize); this.fileData = inflate(this.fileData, this.uncompressedSize);
} } else {
else {
err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)"); err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)");
this.fileData = null; this.fileData = null;
} }
@@ -130,6 +134,7 @@ ZipLocalFile.prototype.unzip = function() {
// Takes an ArrayBuffer of a zip file in // Takes an ArrayBuffer of a zip file in
// returns null on error // returns null on error
// returns an array of DecompressedFile objects on success // returns an array of DecompressedFile objects on success
// ToDo This function differs
var unzip = function(arrayBuffer) { var unzip = function(arrayBuffer) {
postMessage(new bitjs.archive.UnarchiveStartEvent()); postMessage(new bitjs.archive.UnarchiveStartEvent());
@@ -143,10 +148,10 @@ var unzip = function(arrayBuffer) {
var bstream = new bitjs.io.ByteStream(arrayBuffer); var bstream = new bitjs.io.ByteStream(arrayBuffer);
// detect local file header signature or return null // detect local file header signature or return null
if (bstream.peekNumber(4) == zLocalFileHeaderSignature) { if (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
var localFiles = []; var localFiles = [];
// loop until we don't see any more local files // loop until we don't see any more local files
while (bstream.peekNumber(4) == zLocalFileHeaderSignature) { while (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
var oneLocalFile = new ZipLocalFile(bstream); var oneLocalFile = new ZipLocalFile(bstream);
// this should strip out directories/folders // this should strip out directories/folders
if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) { if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) {
@@ -157,14 +162,10 @@ var unzip = function(arrayBuffer) {
totalFilesInArchive = localFiles.length; totalFilesInArchive = localFiles.length;
// got all local files, now sort them // got all local files, now sort them
localFiles.sort(function(a, b) { localFiles.sort(alphanumCase);
var aname = a.filename.toLowerCase();
var bname = b.filename.toLowerCase();
return aname > bname ? 1 : -1;
});
// archive extra data record // archive extra data record
if (bstream.peekNumber(4) == zArchiveExtraDataSignature) { if (bstream.peekNumber(4) === zArchiveExtraDataSignature) {
info(" Found an Archive Extra Data Signature"); info(" Found an Archive Extra Data Signature");
// skipping this record for now // skipping this record for now
@@ -175,11 +176,11 @@ var unzip = function(arrayBuffer) {
// central directory structure // central directory structure
// TODO: handle the rest of the structures (Zip64 stuff) // TODO: handle the rest of the structures (Zip64 stuff)
if (bstream.peekNumber(4) == zCentralFileHeaderSignature) { if (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
info(" Found a Central File Header"); info(" Found a Central File Header");
// read all file headers // read all file headers
while (bstream.peekNumber(4) == zCentralFileHeaderSignature) { while (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
bstream.readNumber(4); // signature bstream.readNumber(4); // signature
bstream.readNumber(2); // version made by bstream.readNumber(2); // version made by
bstream.readNumber(2); // version needed to extract bstream.readNumber(2); // version needed to extract
@@ -205,7 +206,7 @@ var unzip = function(arrayBuffer) {
} }
// digital signature // digital signature
if (bstream.peekNumber(4) == zDigitalSignatureSignature) { if (bstream.peekNumber(4) === zDigitalSignatureSignature) {
info(" Found a Digital Signature"); info(" Found a Digital Signature");
bstream.readNumber(4); bstream.readNumber(4);
@@ -230,7 +231,7 @@ var unzip = function(arrayBuffer) {
// actually do the unzipping // actually do the unzipping
localfile.unzip(); localfile.unzip();
if (localfile.fileData != null) { if (localfile.fileData !== null) {
postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile)); postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile));
postProgress(); postProgress();
} }
@@ -245,26 +246,26 @@ var unzip = function(arrayBuffer) {
// containing {length: 6, symbol: X} // containing {length: 6, symbol: X}
function getHuffmanCodes(bitLengths) { function getHuffmanCodes(bitLengths) {
// ensure bitLengths is an array containing at least one element // ensure bitLengths is an array containing at least one element
if (typeof bitLengths != typeof [] || bitLengths.length < 1) { if (typeof bitLengths !== typeof [] || bitLengths.length < 1) {
err("Error! getHuffmanCodes() called with an invalid array"); err("Error! getHuffmanCodes() called with an invalid array");
return null; return null;
} }
// Reference: http://tools.ietf.org/html/rfc1951#page-8 // Reference: http://tools.ietf.org/html/rfc1951#page-8
var numLengths = bitLengths.length, var numLengths = bitLengths.length;
blCount = [], var blCount = [];
MAX_BITS = 1; var MAX_BITS = 1;
// Step 1: count up how many codes of each length we have // Step 1: count up how many codes of each length we have
for (var i = 0; i < numLengths; ++i) { for (var i = 0; i < numLengths; ++i) {
var length = bitLengths[i]; var length = bitLengths[i];
// test to ensure each bit length is a positive, non-zero number // test to ensure each bit length is a positive, non-zero number
if (typeof length != typeof 1 || length < 0) { if (typeof length !== typeof 1 || length < 0) {
err("bitLengths contained an invalid number in getHuffmanCodes(): " + length + " of type " + (typeof length)); err("bitLengths contained an invalid number in getHuffmanCodes(): " + length + " of type " + (typeof length));
return null; return null;
} }
// increment the appropriate bitlength count // increment the appropriate bitlength count
if (blCount[length] == undefined) blCount[length] = 0; if (typeof blCount[length] === "undefined") blCount[length] = 0;
// a length of zero means this symbol is not participating in the huffman coding // a length of zero means this symbol is not participating in the huffman coding
if (length > 0) blCount[length]++; if (length > 0) blCount[length]++;
@@ -272,21 +273,22 @@ function getHuffmanCodes(bitLengths) {
} }
// Step 2: Find the numerical value of the smallest code for each code length // Step 2: Find the numerical value of the smallest code for each code length
var nextCode = [], var nextCode = [];
code = 0; var code = 0;
for (var bits = 1; bits <= MAX_BITS; ++bits) { for (var bits = 1; bits <= MAX_BITS; ++bits) {
var length = bits - 1; var length2 = bits - 1;
// ensure undefined lengths are zero // ensure undefined lengths are zero
if (blCount[length] == undefined) blCount[length] = 0; if (typeof blCount[length2] === "undefined") blCount[length2] = 0;
code = (code + blCount[bits - 1]) << 1; code = (code + blCount[bits - 1]) << 1;
nextCode [bits] = code; nextCode [bits] = code;
} }
// Step 3: Assign numerical values to all codes // Step 3: Assign numerical values to all codes
var table = {}, tableLength = 0; var table = {};
var tableLength = 0;
for (var n = 0; n < numLengths; ++n) { for (var n = 0; n < numLengths; ++n) {
var len = bitLengths[n]; var len = bitLengths[n];
if (len != 0) { if (len !== 0) {
table[nextCode [len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(nextCode [len],len) }; table[nextCode [len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(nextCode [len],len) };
tableLength++; tableLength++;
nextCode [len]++; nextCode [len]++;
@@ -316,6 +318,7 @@ function getHuffmanCodes(bitLengths) {
// fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits // fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits
var fixedHCtoLiteral = null; var fixedHCtoLiteral = null;
var fixedHCtoDistance = null; var fixedHCtoDistance = null;
function getFixedLiteralTable() { function getFixedLiteralTable() {
// create once // create once
if (!fixedHCtoLiteral) { if (!fixedHCtoLiteral) {
@@ -331,6 +334,7 @@ function getFixedLiteralTable() {
} }
return fixedHCtoLiteral; return fixedHCtoLiteral;
} }
function getFixedDistanceTable() { function getFixedDistanceTable() {
// create once // create once
if (!fixedHCtoDistance) { if (!fixedHCtoDistance) {
@@ -348,7 +352,8 @@ function getFixedDistanceTable() {
// extract one bit at a time until we find a matching Huffman Code // extract one bit at a time until we find a matching Huffman Code
// then return that symbol // then return that symbol
function decodeSymbol(bstream, hcTable) { function decodeSymbol(bstream, hcTable) {
var code = 0, len = 0; var code = 0;
var len = 0;
// loop until we match // loop until we match
for (;;) { for (;;) {
@@ -358,8 +363,7 @@ function decodeSymbol(bstream, hcTable) {
++len; ++len;
// check against Huffman Code table and break if found // check against Huffman Code table and break if found
if (hcTable.hasOwnProperty(code) && hcTable[code].length == len) { if (hcTable.hasOwnProperty(code) && hcTable[code].length === len) {
break; break;
} }
if (len > hcTable.maxLength) { if (len > hcTable.maxLength) {
@@ -387,16 +391,36 @@ Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
264 0 10 274 3 43-50 284 5 227-257 264 0 10 274 3 43-50 284 5 227-257
265 1 11,12 275 3 51-58 285 0 258 265 1 11,12 275 3 51-58 285 0 258
266 1 13,14 276 3 59-66 266 1 13,14 276 3 59-66
*/ */
var LengthLookupTable = [ var LengthLookupTable = [
[0, 3], [0, 4], [0, 5], [0, 6], [0, 3],
[0, 7], [0, 8], [0, 9], [0, 10], [0, 4],
[1, 11], [1, 13], [1, 15], [1, 17], [0, 5],
[2, 19], [2, 23], [2, 27], [2, 31], [0, 6],
[3, 35], [3, 43], [3, 51], [3, 59], [0, 7],
[4, 67], [4, 83], [4, 99], [4, 115], [0, 8],
[5, 131], [5, 163], [5, 195], [5, 227], [0, 9],
[0, 10],
[1, 11],
[1, 13],
[1, 15],
[1, 17],
[2, 19],
[2, 23],
[2, 27],
[2, 31],
[3, 35],
[3, 43],
[3, 51],
[3, 59],
[4, 67],
[4, 83],
[4, 99],
[4, 115],
[5, 131],
[5, 163],
[5, 195],
[5, 227],
[0, 258] [0, 258]
]; ];
/* /*
@@ -415,20 +439,36 @@ var LengthLookupTable = [
9 3 25-32 19 8 769-1024 29 13 24577-32768 9 3 25-32 19 8 769-1024 29 13 24577-32768
*/ */
var DistLookupTable = [ var DistLookupTable = [
[0, 1], [0, 2], [0, 3], [0, 4], [0, 1],
[1, 5], [1, 7], [0, 2],
[2, 9], [2, 13], [0, 3],
[3, 17], [3, 25], [0, 4],
[4, 33], [4, 49], [1, 5],
[5, 65], [5, 97], [1, 7],
[6, 129], [6, 193], [2, 9],
[7, 257], [7, 385], [2, 13],
[8, 513], [8, 769], [3, 17],
[9, 1025], [9, 1537], [3, 25],
[10, 2049], [10, 3073], [4, 33],
[11, 4097], [11, 6145], [4, 49],
[12, 8193], [12, 12289], [5, 65],
[13, 16385], [13, 24577] [5, 97],
[6, 129],
[6, 193],
[7, 257],
[7, 385],
[8, 513],
[8, 769],
[9, 1025],
[9, 1537],
[10, 2049],
[10, 3073],
[11, 4097],
[11, 6145],
[12, 8193],
[12, 12289],
[13, 16385],
[13, 24577]
]; ];
function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) { function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
@@ -454,17 +494,15 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
// copy literal byte to output // copy literal byte to output
buffer.insertByte(symbol); buffer.insertByte(symbol);
blockSize++; blockSize++;
} } else {
else {
// end of block reached // end of block reached
if (symbol == 256) { if (symbol === 256) {
break; break;
} } else {
else { var lengthLookup = LengthLookupTable[symbol - 257];
var lengthLookup = LengthLookupTable[symbol - 257], var length = lengthLookup[1] + bstream.readBits(lengthLookup[0]);
length = lengthLookup[1] + bstream.readBits(lengthLookup[0]), var distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)];
distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)], var distance = distLookup[1] + bstream.readBits(distLookup[0]);
distance = distLookup[1] + bstream.readBits(distLookup[0]);
// now apply length and distance appropriately and copy to output // now apply length and distance appropriately and copy to output
@@ -485,9 +523,8 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
buffer.insertByte(data[ch++]); buffer.insertByte(data[ch++]);
} }
} else { } else {
buffer.insertBytes(buffer.data.subarray(ch, ch + length)); buffer.insertBytes(buffer.data.subarray(ch, ch + length));
} }
} // length-distance pair } // length-distance pair
} // length-distance pair or end-of-block } // length-distance pair or end-of-block
} // loop until we reach end of block } // loop until we reach end of block
@@ -514,37 +551,36 @@ function inflate(compressedData, numDecompressedBytes) {
compressedData.byteOffset, compressedData.byteOffset,
compressedData.byteLength); compressedData.byteLength);
var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes); var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
var numBlocks = 0, blockSize = 0; var blockSize = 0;
// block format: http://tools.ietf.org/html/rfc1951#page-9 // block format: http://tools.ietf.org/html/rfc1951#page-9
var bFinal = 0;
do { do {
var bFinal = bstream.readBits(1), bFinal = bstream.readBits(1);
bType = bstream.readBits(2); var bType = bstream.readBits(2);
blockSize = 0; blockSize = 0;
++numBlocks; // ++numBlocks;
// no compression // no compression
if (bType == 0) { if (bType === 0) {
// skip remaining bits in this byte // skip remaining bits in this byte
while (bstream.bitPtr != 0) bstream.readBits(1); while (bstream.bitPtr !== 0) bstream.readBits(1);
var len = bstream.readBits(16); var len = bstream.readBits(16);
bstream.readBits(16); bstream.readBits(16);
// TODO: check if nlen is the ones-complement of len? // TODO: check if nlen is the ones-complement of len?
if (len > 0) buffer.insertBytes(bstream.readBytes(len)); if (len > 0) buffer.insertBytes(bstream.readBytes(len));
blockSize = len; blockSize = len;
} } else if (bType === 1) {
// fixed Huffman codes // fixed Huffman codes
else if(bType == 1) {
blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer); blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer);
} } else if (bType === 2) {
// dynamic Huffman codes // dynamic Huffman codes
else if(bType == 2) {
var numLiteralLengthCodes = bstream.readBits(5) + 257; var numLiteralLengthCodes = bstream.readBits(5) + 257;
var numDistanceCodes = bstream.readBits(5) + 1, var numDistanceCodes = bstream.readBits(5) + 1,
numCodeLengthCodes = bstream.readBits(4) + 4; numCodeLengthCodes = bstream.readBits(4) + 4;
// populate the array of code length codes (first de-compaction) // populate the array of code length codes (first de-compaction)
var codeLengthsCodeLengths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; var codeLengthsCodeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < numCodeLengthCodes; ++i) { for (var i = 0; i < numCodeLengthCodes; ++i) {
codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3); codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3);
} }
@@ -575,19 +611,17 @@ function inflate(compressedData, numDecompressedBytes) {
if (symbol <= 15) { if (symbol <= 15) {
literalCodeLengths.push(symbol); literalCodeLengths.push(symbol);
prevCodeLength = symbol; prevCodeLength = symbol;
} } else if (symbol === 16) {
else if (symbol == 16) {
var repeat = bstream.readBits(2) + 3; var repeat = bstream.readBits(2) + 3;
while (repeat--) { while (repeat--) {
literalCodeLengths.push(prevCodeLength); literalCodeLengths.push(prevCodeLength);
} }
} } else if (symbol === 17) {
else if (symbol == 17) {
var repeat1 = bstream.readBits(3) + 3; var repeat1 = bstream.readBits(3) + 3;
while (repeat1--) { while (repeat1--) {
literalCodeLengths.push(0); literalCodeLengths.push(0);
} }
} else if (symbol == 18) { } else if (symbol === 18) {
var repeat2 = bstream.readBits(7) + 11; var repeat2 = bstream.readBits(7) + 11;
while (repeat2--) { while (repeat2--) {
literalCodeLengths.push(0); literalCodeLengths.push(0);
@@ -599,8 +633,8 @@ function inflate(compressedData, numDecompressedBytes) {
var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes); var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes);
// now generate the true Huffman Code tables using these code lengths // now generate the true Huffman Code tables using these code lengths
var hcLiteralTable = getHuffmanCodes(literalCodeLengths), var hcLiteralTable = getHuffmanCodes(literalCodeLengths);
hcDistanceTable = getHuffmanCodes(distanceCodeLengths); var hcDistanceTable = getHuffmanCodes(distanceCodeLengths);
blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer); blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer);
} else { } else {
// error // error
@@ -613,7 +647,7 @@ function inflate(compressedData, numDecompressedBytes) {
currentBytesUnarchived += blockSize; currentBytesUnarchived += blockSize;
postProgress(); postProgress();
} while (bFinal != 1); } while (bFinal !== 1);
// we are done reading blocks if the bFinal bit was set for this block // we are done reading blocks if the bFinal bit was set for this block
// return the buffer data bytes // return the buffer data bytes
-308
View File
@@ -1,308 +0,0 @@
/*
* bytestream.js
*
* Provides readers for byte streams.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
/**
* This object allows you to peek and consume bytes as numbers and strings out
* of a stream. More bytes can be pushed into the back of the stream via the
* push() method.
*/
bitjs.io.ByteStream = class {
/**
* @param {ArrayBuffer} ab The ArrayBuffer object.
* @param {number=} opt_offset The offset into the ArrayBuffer
* @param {number=} opt_length The length of this BitStream
*/
constructor(ab, opt_offset, opt_length) {
if (!(ab instanceof ArrayBuffer)) {
throw 'Error! BitArray constructed with an invalid ArrayBuffer object';
}
const offset = opt_offset || 0;
const length = opt_length || ab.byteLength;
/**
* The current page of bytes in the stream.
* @type {Uint8Array}
* @private
*/
this.bytes = new Uint8Array(ab, offset, length);
/**
* The next pages of bytes in the stream.
* @type {Array<Uint8Array>}
* @private
*/
this.pages_ = [];
/**
* The byte in the current page that we will read next.
* @type {Number}
* @private
*/
this.ptr = 0;
/**
* An ever-increasing number.
* @type {Number}
* @private
*/
this.bytesRead_ = 0;
}
/**
* Returns how many bytes have been read in the stream since the beginning of time.
*/
getNumBytesRead() {
return this.bytesRead_;
}
/**
* Returns how many bytes are currently in the stream left to be read.
*/
getNumBytesLeft() {
const bytesInCurrentPage = (this.bytes.byteLength - this.ptr);
return this.pages_.reduce((acc, arr) => acc + arr.length, bytesInCurrentPage);
}
/**
* Move the pointer ahead n bytes. If the pointer is at the end of the current array
* of bytes and we have another page of bytes, point at the new page. This is a private
* method, no validation is done.
* @param {number} n Number of bytes to increment.
* @private
*/
movePointer_(n) {
this.ptr += n;
this.bytesRead_ += n;
while (this.ptr >= this.bytes.length && this.pages_.length > 0) {
this.ptr -= this.bytes.length;
this.bytes = this.pages_.shift();
}
}
/**
* Peeks at the next n bytes as an unsigned number but does not advance the
* pointer.
* @param {number} n The number of bytes to peek at. Must be a positive integer.
* @return {number} The n bytes interpreted as an unsigned number.
*/
peekNumber(n) {
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekNumber() with a non-positive integer';
} else if (num === 0) {
return 0;
}
if (n > 4) {
throw 'Error! Called peekNumber(' + n +
') but this method can only reliably read numbers up to 4 bytes long';
}
if (this.getNumBytesLeft() < num) {
throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
}
let result = 0;
let curPage = this.bytes;
let pageIndex = 0;
let ptr = this.ptr;
for (let i = 0; i < num; ++i) {
result |= (curPage[ptr++] << (i * 8));
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
}
return result;
}
/**
* Returns the next n bytes as an unsigned number (or -1 on error)
* and advances the stream pointer n bytes.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {number} The n bytes interpreted as an unsigned number.
*/
readNumber(n) {
const num = this.peekNumber(n);
this.movePointer_(n);
return num;
}
/**
* Returns the next n bytes as a signed number but does not advance the
* pointer.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {number} The bytes interpreted as a signed number.
*/
peekSignedNumber(n) {
let num = this.peekNumber(n);
const HALF = Math.pow(2, (n * 8) - 1);
const FULL = HALF * 2;
if (num >= HALF) num -= FULL;
return num;
}
/**
* Returns the next n bytes as a signed number and advances the stream pointer.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {number} The bytes interpreted as a signed number.
*/
readSignedNumber(n) {
const num = this.peekSignedNumber(n);
this.movePointer_(n);
return num;
}
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @param {boolean} movePointers Whether to move the pointers.
* @return {Uint8Array} The subarray.
*/
peekBytes(n, movePointers) {
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekBytes() with a non-positive integer';
} else if (num === 0) {
return new Uint8Array();
}
const totalBytesLeft = this.getNumBytesLeft();
if (num > totalBytesLeft) {
throw 'Error! Overflowed the byte stream during peekBytes! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
}
const result = new Uint8Array(num);
let curPage = this.bytes;
let ptr = this.ptr;
let bytesLeftToCopy = num;
let pageIndex = 0;
while (bytesLeftToCopy > 0) {
const bytesLeftInPage = curPage.length - ptr;
const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInPage);
result.set(curPage.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy);
ptr += sourceLength;
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
bytesLeftToCopy -= sourceLength;
}
if (movePointers) {
this.movePointer_(num);
}
return result;
}
/**
* Reads the next n bytes as a sub-array.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {Uint8Array} The subarray.
*/
readBytes(n) {
return this.peekBytes(n, true);
}
/**
* Peeks at the next n bytes as an ASCII string but does not advance the pointer.
* @param {number} n The number of bytes to peek at. Must be a positive integer.
* @return {string} The next n bytes as a string.
*/
peekString(n) {
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekString() with a non-positive integer';
} else if (num === 0) {
return '';
}
const totalBytesLeft = this.getNumBytesLeft();
if (num > totalBytesLeft) {
throw 'Error! Overflowed the byte stream while peekString()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
}
let result = new Array(num);
let curPage = this.bytes;
let pageIndex = 0;
let ptr = this.ptr;
for (let i = 0; i < num; ++i) {
result[i] = String.fromCharCode(curPage[ptr++]);
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
}
return result.join('');
}
/**
* Returns the next n bytes as an ASCII string and advances the stream pointer
* n bytes.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {string} The next n bytes as a string.
*/
readString(n) {
const strToReturn = this.peekString(n);
this.movePointer_(n);
return strToReturn;
}
/**
* Feeds more bytes into the back of the stream.
* @param {ArrayBuffer} ab
*/
push(ab) {
if (!(ab instanceof ArrayBuffer)) {
throw 'Error! ByteStream.push() called with an invalid ArrayBuffer object';
}
this.pages_.push(new Uint8Array(ab));
// If the pointer is at the end of the current page of bytes, this will advance
// to the next page.
this.movePointer_(0);
}
/**
* Creates a new ByteStream from this ByteStream that can be read / peeked.
* @return {bitjs.io.ByteStream} A clone of this ByteStream.
*/
tee() {
const clone = new bitjs.io.ByteStream(this.bytes.buffer);
clone.bytes = this.bytes;
clone.ptr = this.ptr;
clone.pages_ = this.pages_.slice();
clone.bytesRead_ = this.bytesRead_;
return clone;
}
}
+24 -9
View File
@@ -58,8 +58,8 @@ $( 'a.navbar-brand' ).clone().appendTo( '.home-btn' ).empty().removeClass('navba
if ( $( 'body.book' ).length > 0 ) { if ( $( 'body.book' ).length > 0 ) {
description = $( '.comments' ); description = $( '.comments' );
bookInfo = $( '.author' ).nextUntil( 'h3:contains("Description")'); bookInfo = $( ".author" ).nextUntil("#decription");
$( 'h3:contains("Description")' ).detach(); $("#decription").detach();
$( '.comments' ).detach(); $( '.comments' ).detach();
$( bookInfo ).wrapAll( '<div class="bookinfo"></div>' ); $( bookInfo ).wrapAll( '<div class="bookinfo"></div>' );
// $( 'h3:contains("Description:")' ).after( '<div class="description"></div>' ); // $( 'h3:contains("Description:")' ).after( '<div class="description"></div>' );
@@ -145,14 +145,29 @@ if ( $( 'body.book' ).length > 0 ) {
.prepend( '<div><img class="bg-blur" src="' + cover + '"></div>' ); .prepend( '<div><img class="bg-blur" src="' + cover + '"></div>' );
// Fix-up book detail headings // Fix-up book detail headings
publisher = $( '.publishers p span' ).text().split( ':' ); publisher = $( '.publishers p span' ).text().split( ':' );
$( '.publishers p span' ).remove(); $( '.publishers p span' ).remove();
$.each(publisher, function(i, val) { $.each(publisher, function(i, val) {
$( '.publishers' ).append( '<span>' + publisher[i] + '</span>' ); $( '.publishers' ).append( '<span>' + publisher[i] + '</span>' );
});
$( '.publishers span:nth-child(3)' ).text(function() {
return $(this).text().replace(/^\s+|^\t+|\t+|\s+$/g, "");
}); });
$( '.publishers span:nth-child(3)' ).text(function() {
return $(this).text().replace(/^\s+|^\t+|\t+|\s+$/g, ""); // Fix-up book custom colums headings
}); // real_custom_column = $( '.real_custom_columns' ).text().split( ':' );
real_custom_column = $( '.real_custom_columns' );
// $( '.real_custom_columns' ).remove();
$.each(real_custom_column, function(i, val) {
real_cc = $(this).text().split( ':' );
$( this ).text("");
if (real_cc.length > 1) {
$( this ).append( '<span>' + real_cc[0] + '</span><span>' + real_cc[1] + '</span>' );
}
});
//$( '.real_custom_columns:nth-child(3)' ).text(function() {
//return $(this).text().replace(/^\s+|^\t+|\t+|\s+$/g, "");
//});
published = $( '.publishing-date p' ) published = $( '.publishing-date p' )
.text().split(': '); .text().split(': ');
+10 -10
View File
@@ -20,16 +20,16 @@
* Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only) * Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only)
*/ */
/* global _, i18nMsg, tinymce */ /* global _, i18nMsg, tinymce */
var dbResults = []; // var dbResults = [];
var ggResults = []; var ggResults = [];
$(function () { $(function () {
var msg = i18nMsg; var msg = i18nMsg;
/*var douban = "https://api.douban.com"; /*var douban = "https://api.douban.com";
var dbSearch = "/v2/book/search";*/ var dbSearch = "/v2/book/search";*/
var dbDone = true; // var dbDone = true;
var google = "https://www.googleapis.com/"; var google = "https://www.googleapis.com";
var ggSearch = "/books/v1/volumes"; var ggSearch = "/books/v1/volumes";
var ggDone = false; var ggDone = false;
@@ -56,11 +56,9 @@ $(function () {
if (showFlag === 1) { if (showFlag === 1) {
$("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>"); $("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
} }
if (ggDone && dbDone) { if (!ggDone) {
if (!ggResults && !dbResults) { $("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>");
$("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>"); return;
return;
}
} }
if (ggDone && ggResults.length > 0) { if (ggDone && ggResults.length > 0) {
ggResults.forEach(function(result) { ggResults.forEach(function(result) {
@@ -137,10 +135,12 @@ $(function () {
dataType: "jsonp", dataType: "jsonp",
jsonp: "callback", jsonp: "callback",
success: function success(data) { success: function success(data) {
ggResults = data.items; if ("items" in data) {
ggResults = data.items;
ggDone = true;
}
}, },
complete: function complete() { complete: function complete() {
ggDone = true;
showResult(); showResult();
$("#show-google").trigger("change"); $("#show-google").trigger("change");
} }
-483
View File
@@ -1,483 +0,0 @@
/*
* io.js
*
* Provides readers for bit/byte streams (reading) and a byte buffer (writing).
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
/* global bitjs, Uint8Array */
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
(function() {
// mask for getting the Nth bit (zero-based)
bitjs.BIT = [ 0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
0x100, 0x200, 0x400, 0x800,
0x1000, 0x2000, 0x4000, 0x8000];
// mask for getting N number of bits (0-8)
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ];
/**
* This bit stream peeks and consumes bits out of a binary stream.
*
* @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
* @param {boolean} rtl Whether the stream reads bits from the byte starting
* from bit 7 to 0 (true) or bit 0 to 7 (false).
* @param {Number} optOffset The offset into the ArrayBuffer
* @param {Number} optLength The length of this BitStream
*/
bitjs.io.BitStream = function(ab, rtl, optOffset, optLength) {
if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") {
throw "Error! BitArray constructed with an invalid ArrayBuffer object";
}
var offset = optOffset || 0;
var length = optLength || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length);
this.bytePtr = 0; // tracks which byte we are on
this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7)
this.peekBits = rtl ? this.peekBitsRtl : this.peekBitsLtr;
};
/**
* byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0
*
* The bit pointer starts at bit0 of byte0 and moves left until it reaches
* bit7 of byte0, then jumps to bit0 of byte1, etc.
* @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.peekBitsLtr = function(n, movePointers) {
if (n <= 0 || typeof n !== typeof 1) {
return 0;
}
var movePointers = movePointers || false;
var bytePtr = this.bytePtr;
var bitPtr = this.bitPtr;
var result = 0;
var bitsIn = 0;
var bytes = this.bytes;
// keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) {
if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr;
}
var numBitsLeftInThisByte = (8 - bitPtr);
var mask;
if (n >= numBitsLeftInThisByte) {
mask = (BITMASK[numBitsLeftInThisByte] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bytePtr++;
bitPtr = 0;
bitsIn += numBitsLeftInThisByte;
n -= numBitsLeftInThisByte;
} else {
mask = (BITMASK[n] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bitPtr += n;
bitsIn += n;
n = 0;
}
}
if (movePointers) {
this.bitPtr = bitPtr;
this.bytePtr = bytePtr;
}
return result;
};
/**
* byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0
*
* The bit pointer starts at bit7 of byte0 and moves right until it reaches
* bit0 of byte0, then goes to bit7 of byte1, etc.
* @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.peekBitsRtl = function(n, movePointers) {
if (n <= 0 || typeof n !== typeof 1) {
return 0;
}
var movePointers = movePointers || false;
var bytePtr = this.bytePtr;
var bitPtr = this.bitPtr;
var result = 0;
var bytes = this.bytes;
// keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) {
if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr;
// return -1;
}
var numBitsLeftInThisByte = (8 - bitPtr);
if (n >= numBitsLeftInThisByte) {
result <<= numBitsLeftInThisByte;
result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]);
bytePtr++;
bitPtr = 0;
n -= numBitsLeftInThisByte;
} else {
result <<= n;
result |= ((bytes[bytePtr] & (BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr));
bitPtr += n;
n = 0;
}
}
if (movePointers) {
this.bitPtr = bitPtr;
this.bytePtr = bytePtr;
}
return result;
};
/**
* Some voodoo magic.
*/
bitjs.io.BitStream.prototype.getBits = function() {
return (((((this.bytes[this.bytePtr] & 0xff) << 16) +
((this.bytes[this.bytePtr + 1] & 0xff) << 8) +
((this.bytes[this.bytePtr + 2] & 0xff))) >>> (8 - this.bitPtr)) & 0xffff);
};
/**
* Reads n bits out of the stream, consuming them (moving the bit pointer).
* @param {number} n The number of bits to read.
* @return {number} The read bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.readBits = function(n) {
return this.peekBits(n, true);
};
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true. Only use this for uncompressed blocks as this throws away remaining
* bits in the current byte.
* @param {number} n The number of bytes to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {Uint8Array} The subarray.
*/
bitjs.io.BitStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
// from http://tools.ietf.org/html/rfc1951#page-11
// "Any bits of input up to the next byte boundary are ignored."
while (this.bitPtr !== 0) {
this.readBits(1);
}
movePointers = movePointers || false;
var bytePtr = this.bytePtr;
// var bitPtr = this.bitPtr;
var result = this.bytes.subarray(bytePtr, bytePtr + n);
if (movePointers) {
this.bytePtr += n;
}
return result;
};
/**
* @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray.
*/
bitjs.io.BitStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true);
};
/**
* This object allows you to peek and consume bytes as numbers and strings
* out of an ArrayBuffer. In this buffer, everything must be byte-aligned.
*
* @param {ArrayBuffer} ab The ArrayBuffer object.
* @param {number=} optOffset The offset into the ArrayBuffer
* @param {number=} optLength The length of this BitStream
* @constructor
*/
bitjs.io.ByteStream = function(ab, optOffset, optLength) {
var offset = optOffset || 0;
var length = optLength || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length);
this.ptr = 0;
};
/**
* Peeks at the next n bytes as an unsigned number but does not advance the
* pointer
* TODO: This apparently cannot read more than 4 bytes as a number?
* @param {number} n The number of bytes to peek at.
* @return {number} The n bytes interpreted as an unsigned number.
*/
bitjs.io.ByteStream.prototype.peekNumber = function(n) {
// TODO: return error if n would go past the end of the stream?
if (n <= 0 || typeof n !== typeof 1) {
return -1;
}
var result = 0;
// read from last byte to first byte and roll them in
var curByte = this.ptr + n - 1;
while (curByte >= this.ptr) {
result <<= 8;
result |= this.bytes[curByte];
--curByte;
}
return result;
};
/**
* Returns the next n bytes as an unsigned number (or -1 on error)
* and advances the stream pointer n bytes.
* @param {number} n The number of bytes to read.
* @return {number} The n bytes interpreted as an unsigned number.
*/
bitjs.io.ByteStream.prototype.readNumber = function(n) {
var num = this.peekNumber( n );
this.ptr += n;
return num;
};
/**
* Returns the next n bytes as a signed number but does not advance the
* pointer.
* @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number.
*/
bitjs.io.ByteStream.prototype.peekSignedNumber = function(n) {
var num = this.peekNumber(n);
var HALF = Math.pow(2, (n * 8) - 1);
var FULL = HALF * 2;
if (num >= HALF) num -= FULL;
return num;
};
/**
* Returns the next n bytes as a signed number and advances the stream pointer.
* @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number.
*/
bitjs.io.ByteStream.prototype.readSignedNumber = function(n) {
var num = this.peekSignedNumber(n);
this.ptr += n;
return num;
};
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true.
* @param {number} n The number of bytes to read.
* @param {boolean} movePointers Whether to move the pointers.
* @return {Uint8Array} The subarray.
*/
bitjs.io.ByteStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return null;
}
var result = this.bytes.subarray(this.ptr, this.ptr + n);
if (movePointers) {
this.ptr += n;
}
return result;
};
/**
* Reads the next n bytes as a sub-array.
* @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray.
*/
bitjs.io.ByteStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true);
};
/**
* Peeks at the next n bytes as a string but does not advance the pointer.
* @param {number} n The number of bytes to peek at.
* @return {string} The next n bytes as a string.
*/
bitjs.io.ByteStream.prototype.peekString = function(n) {
if (n <= 0 || typeof n != typeof 1) {
return "";
}
var result = "";
for (var p = this.ptr, end = this.ptr + n; p < end; ++p) {
result += String.fromCharCode(this.bytes[p]);
}
return result;
};
/**
* Returns the next n bytes as an ASCII string and advances the stream pointer
* n bytes.
* @param {number} n The number of bytes to read.
* @return {string} The next n bytes as a string.
*/
bitjs.io.ByteStream.prototype.readString = function(n) {
var strToReturn = this.peekString(n);
this.ptr += n;
return strToReturn;
};
/**
* A write-only Byte buffer which uses a Uint8 Typed Array as a backing store.
* @param {number} numBytes The number of bytes to allocate.
* @constructor
*/
bitjs.io.ByteBuffer = function(numBytes) {
if (typeof numBytes !== typeof 1 || numBytes <= 0) {
throw "Error! ByteBuffer initialized with '" + numBytes + "'";
}
this.data = new Uint8Array(numBytes);
this.ptr = 0;
};
/**
* @param {number} b The byte to insert.
*/
bitjs.io.ByteBuffer.prototype.insertByte = function(b) {
// TODO: throw if byte is invalid?
this.data[this.ptr++] = b;
};
/**
* @param {Array.<number>|Uint8Array|Int8Array} bytes The bytes to insert.
*/
bitjs.io.ByteBuffer.prototype.insertBytes = function(bytes) {
// TODO: throw if bytes is invalid?
this.data.set(bytes, this.ptr);
this.ptr += bytes.length;
};
/**
* Writes an unsigned number into the next n bytes. If the number is too large
* to fit into n bytes or is negative, an error is thrown.
* @param {number} num The unsigned number to write.
* @param {number} numBytes The number of bytes to write the number into.
*/
bitjs.io.ByteBuffer.prototype.writeNumber = function(num, numBytes) {
if (numBytes < 1) {
throw "Trying to write into too few bytes: " + numBytes;
}
if (num < 0) {
throw "Trying to write a negative number (" + num +
") as an unsigned number to an ArrayBuffer";
}
if (num > (Math.pow(2, numBytes * 8) - 1)) {
throw "Trying to write " + num + " into only " + numBytes + " bytes";
}
// Roll 8-bits at a time into an array of bytes.
var bytes = [];
while (numBytes-- > 0) {
var eightBits = num & 255;
bytes.push(eightBits);
num >>= 8;
}
this.insertBytes(bytes);
};
/**
* Writes a signed number into the next n bytes. If the number is too large
* to fit into n bytes, an error is thrown.
* @param {number} num The signed number to write.
* @param {number} numBytes The number of bytes to write the number into.
*/
bitjs.io.ByteBuffer.prototype.writeSignedNumber = function(num, numBytes) {
if (numBytes < 1) {
throw "Trying to write into too few bytes: " + numBytes;
}
var HALF = Math.pow(2, (numBytes * 8) - 1);
if (num >= HALF || num < -HALF) {
throw "Trying to write " + num + " into only " + numBytes + " bytes";
}
// Roll 8-bits at a time into an array of bytes.
var bytes = [];
while (numBytes-- > 0) {
var eightBits = num & 255;
bytes.push(eightBits);
num >>= 8;
}
this.insertBytes(bytes);
};
/**
* @param {string} str The ASCII string to write.
*/
bitjs.io.ByteBuffer.prototype.writeASCIIString = function(str) {
for (var i = 0; i < str.length; ++i) {
var curByte = str.charCodeAt(i);
if (curByte < 0 || curByte > 255) {
throw "Trying to write a non-ASCII string!";
}
this.insertByte(curByte);
}
};
})();
+237
View File
@@ -0,0 +1,237 @@
/*
* bitstream.js
*
* Provides readers for bitstreams.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
/* global bitjs, Uint8Array */
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
(function() {
// mask for getting the Nth bit (zero-based)
bitjs.BIT = [0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
0x100, 0x200, 0x400, 0x800,
0x1000, 0x2000, 0x4000, 0x8000
];
// mask for getting N number of bits (0-8)
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF];
/**
* This bit stream peeks and consumes bits out of a binary stream.
*
* @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
* @param {boolean} rtl Whether the stream reads bits from the byte starting
* from bit 7 to 0 (true) or bit 0 to 7 (false).
* @param {Number} optOffset The offset into the ArrayBuffer
* @param {Number} optLength The length of this BitStream
*/
bitjs.io.BitStream = function(ab, rtl, optOffset, optLength) {
if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") {
throw "Error! BitArray constructed with an invalid ArrayBuffer object";
}
var offset = optOffset || 0;
var length = optLength || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length);
this.bytePtr = 0; // tracks which byte we are on
this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7)
this.peekBits = rtl ? this.peekBitsRtl : this.peekBitsLtr;
};
/**
* byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0
*
* The bit pointer starts at bit0 of byte0 and moves left until it reaches
* bit7 of byte0, then jumps to bit0 of byte1, etc.
* @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.peekBitsLtr = function(n, movePointers) {
if (n <= 0 || typeof n !== typeof 1) {
return 0;
}
var movePointers = movePointers || false,
bytePtr = this.bytePtr,
bitPtr = this.bitPtr,
result = 0,
bitsIn = 0,
bytes = this.bytes;
// keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) {
if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr;
// return -1;
}
var numBitsLeftInThisByte = (8 - bitPtr);
var mask;
if (n >= numBitsLeftInThisByte) {
mask = (BITMASK[numBitsLeftInThisByte] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bytePtr++;
bitPtr = 0;
bitsIn += numBitsLeftInThisByte;
n -= numBitsLeftInThisByte;
} else {
mask = (BITMASK[n] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bitPtr += n;
bitsIn += n;
n = 0;
}
}
if (movePointers) {
this.bitPtr = bitPtr;
this.bytePtr = bytePtr;
}
return result;
};
/**
* byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0
*
* The bit pointer starts at bit7 of byte0 and moves right until it reaches
* bit0 of byte0, then goes to bit7 of byte1, etc.
* @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.peekBitsRtl = function(n, movePointers) {
if (n <= 0 || typeof n !== typeof 1) {
return 0;
}
var movePointers = movePointers || false,
bytePtr = this.bytePtr,
bitPtr = this.bitPtr,
result = 0,
bytes = this.bytes;
// keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) {
if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr;
// return -1;
}
var numBitsLeftInThisByte = (8 - bitPtr);
if (n >= numBitsLeftInThisByte) {
result <<= numBitsLeftInThisByte;
result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]);
bytePtr++;
bitPtr = 0;
n -= numBitsLeftInThisByte;
} else {
result <<= n;
result |= ((bytes[bytePtr] & (BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr));
bitPtr += n;
n = 0;
}
}
if (movePointers) {
this.bitPtr = bitPtr;
this.bytePtr = bytePtr;
}
return result;
};
/**
* Peek at 16 bits from current position in the buffer.
* Bit at (bytePtr,bitPtr) has the highest position in returning data.
* Taken from getbits.hpp in unrar.
* TODO: Move this out of BitStream and into unrar.
*/
bitjs.io.BitStream.prototype.getBits = function() {
return (((((this.bytes[this.bytePtr] & 0xff) << 16) +
((this.bytes[this.bytePtr + 1] & 0xff) << 8) +
((this.bytes[this.bytePtr + 2] & 0xff))) >>> (8 - this.bitPtr)) & 0xffff);
};
/**
* Reads n bits out of the stream, consuming them (moving the bit pointer).
* @param {number} n The number of bits to read.
* @return {number} The read bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.readBits = function(n) {
return this.peekBits(n, true);
};
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true. Only use this for uncompressed blocks as this throws away remaining
* bits in the current byte.
* @param {number} n The number of bytes to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {Uint8Array} The subarray.
*/
bitjs.io.BitStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n !== typeof 1) {
return 0;
}
// from http://tools.ietf.org/html/rfc1951#page-11
// "Any bits of input up to the next byte boundary are ignored."
while (this.bitPtr !== 0) {
this.readBits(1);
}
var movePointers = movePointers || false;
var bytePtr = this.bytePtr;
// bitPtr = this.bitPtr;
var result = this.bytes.subarray(bytePtr, bytePtr + n);
if (movePointers) {
this.bytePtr += n;
}
return result;
};
/**
* @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray.
*/
bitjs.io.BitStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true);
};
})();
+124
View File
@@ -0,0 +1,124 @@
/*
* bytestream.js
*
* Provides a writer for bytes.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
/* global bitjs, Uint8Array */
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
(function() {
/**
* A write-only Byte buffer which uses a Uint8 Typed Array as a backing store.
* @param {number} numBytes The number of bytes to allocate.
* @constructor
*/
bitjs.io.ByteBuffer = function(numBytes) {
if (typeof numBytes !== typeof 1 || numBytes <= 0) {
throw "Error! ByteBuffer initialized with '" + numBytes + "'";
}
this.data = new Uint8Array(numBytes);
this.ptr = 0;
};
/**
* @param {number} b The byte to insert.
*/
bitjs.io.ByteBuffer.prototype.insertByte = function(b) {
// TODO: throw if byte is invalid?
this.data[this.ptr++] = b;
};
/**
* @param {Array.<number>|Uint8Array|Int8Array} bytes The bytes to insert.
*/
bitjs.io.ByteBuffer.prototype.insertBytes = function(bytes) {
// TODO: throw if bytes is invalid?
this.data.set(bytes, this.ptr);
this.ptr += bytes.length;
};
/**
* Writes an unsigned number into the next n bytes. If the number is too large
* to fit into n bytes or is negative, an error is thrown.
* @param {number} num The unsigned number to write.
* @param {number} numBytes The number of bytes to write the number into.
*/
bitjs.io.ByteBuffer.prototype.writeNumber = function(num, numBytes) {
if (numBytes < 1) {
throw "Trying to write into too few bytes: " + numBytes;
}
if (num < 0) {
throw "Trying to write a negative number (" + num +
") as an unsigned number to an ArrayBuffer";
}
if (num > (Math.pow(2, numBytes * 8) - 1)) {
throw "Trying to write " + num + " into only " + numBytes + " bytes";
}
// Roll 8-bits at a time into an array of bytes.
var bytes = [];
while (numBytes-- > 0) {
var eightBits = num & 255;
bytes.push(eightBits);
num >>= 8;
}
this.insertBytes(bytes);
};
/**
* Writes a signed number into the next n bytes. If the number is too large
* to fit into n bytes, an error is thrown.
* @param {number} num The signed number to write.
* @param {number} numBytes The number of bytes to write the number into.
*/
bitjs.io.ByteBuffer.prototype.writeSignedNumber = function(num, numBytes) {
if (numBytes < 1) {
throw "Trying to write into too few bytes: " + numBytes;
}
var HALF = Math.pow(2, (numBytes * 8) - 1);
if (num >= HALF || num < -HALF) {
throw "Trying to write " + num + " into only " + numBytes + " bytes";
}
// Roll 8-bits at a time into an array of bytes.
var bytes = [];
while (numBytes-- > 0) {
var eightBits = num & 255;
bytes.push(eightBits);
num >>= 8;
}
this.insertBytes(bytes);
};
/**
* @param {string} str The ASCII string to write.
*/
bitjs.io.ByteBuffer.prototype.writeASCIIString = function(str) {
for (var i = 0; i < str.length; ++i) {
var curByte = str.charCodeAt(i);
if (curByte < 0 || curByte > 255) {
throw "Trying to write a non-ASCII string!";
}
this.insertByte(curByte);
}
};
})();
+166
View File
@@ -0,0 +1,166 @@
/*
* bytestream.js
*
* Provides readers for byte streams.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
/* global bitjs, Uint8Array */
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
(function() {
/**
* This object allows you to peek and consume bytes as numbers and strings
* out of an ArrayBuffer. In this buffer, everything must be byte-aligned.
*
* @param {ArrayBuffer} ab The ArrayBuffer object.
* @param {number=} optOffset The offset into the ArrayBuffer
* @param {number=} optLength The length of this BitStream
* @constructor
*/
bitjs.io.ByteStream = function(ab, optOffset, optLength) {
var offset = optOffset || 0;
var length = optLength || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length);
this.ptr = 0;
};
/**
* Peeks at the next n bytes as an unsigned number but does not advance the
* pointer
* TODO: This apparently cannot read more than 4 bytes as a number?
* @param {number} n The number of bytes to peek at.
* @return {number} The n bytes interpreted as an unsigned number.
*/
bitjs.io.ByteStream.prototype.peekNumber = function(n) {
// TODO: return error if n would go past the end of the stream?
if (n <= 0 || typeof n !== typeof 1) {
return -1;
}
var result = 0;
// read from last byte to first byte and roll them in
var curByte = this.ptr + n - 1;
while (curByte >= this.ptr) {
result <<= 8;
result |= this.bytes[curByte];
--curByte;
}
return result;
};
/**
* Returns the next n bytes as an unsigned number (or -1 on error)
* and advances the stream pointer n bytes.
* @param {number} n The number of bytes to read.
* @return {number} The n bytes interpreted as an unsigned number.
*/
bitjs.io.ByteStream.prototype.readNumber = function(n) {
var num = this.peekNumber(n);
this.ptr += n;
return num;
};
/**
* Returns the next n bytes as a signed number but does not advance the
* pointer.
* @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number.
*/
bitjs.io.ByteStream.prototype.peekSignedNumber = function(n) {
var num = this.peekNumber(n);
var HALF = Math.pow(2, (n * 8) - 1);
var FULL = HALF * 2;
if (num >= HALF) num -= FULL;
return num;
};
/**
* Returns the next n bytes as a signed number and advances the stream pointer.
* @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number.
*/
bitjs.io.ByteStream.prototype.readSignedNumber = function(n) {
var num = this.peekSignedNumber(n);
this.ptr += n;
return num;
};
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true.
* @param {number} n The number of bytes to read.
* @param {boolean} movePointers Whether to move the pointers.
* @return {Uint8Array} The subarray.
*/
bitjs.io.ByteStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n !== typeof 1) {
return null;
}
var result = this.bytes.subarray(this.ptr, this.ptr + n);
if (movePointers) {
this.ptr += n;
}
return result;
};
/**
* Reads the next n bytes as a sub-array.
* @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray.
*/
bitjs.io.ByteStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true);
};
/**
* Peeks at the next n bytes as a string but does not advance the pointer.
* @param {number} n The number of bytes to peek at.
* @return {string} The next n bytes as a string.
*/
bitjs.io.ByteStream.prototype.peekString = function(n) {
if (n <= 0 || typeof n !== typeof 1) {
return "";
}
var result = "";
for (var p = this.ptr, end = this.ptr + n; p < end; ++p) {
result += String.fromCharCode(this.bytes[p]);
}
return result;
};
/**
* Returns the next n bytes as an ASCII string and advances the stream pointer
* n bytes.
* @param {number} n The number of bytes to read.
* @return {string} The next n bytes as a string.
*/
bitjs.io.ByteStream.prototype.readString = function(n) {
var strToReturn = this.peekString(n);
this.ptr += n;
return strToReturn;
};
})();
+40 -29
View File
@@ -15,7 +15,9 @@
* Typed Arrays: http://www.khronos.org/registry/typedarray/specs/latest/#6 * Typed Arrays: http://www.khronos.org/registry/typedarray/specs/latest/#6
*/ */
/* global screenfull, bitjs */ /* global screenfull, bitjs, Uint8Array, opera */
/* exported init, event */
if (window.opera) { if (window.opera) {
window.console.log = function(str) { window.console.log = function(str) {
@@ -102,12 +104,12 @@ kthoom.setSettings = function() {
var createURLFromArray = function(array, mimeType) { var createURLFromArray = function(array, mimeType) {
var offset = array.byteOffset; var offset = array.byteOffset;
var len = array.byteLength; var len = array.byteLength;
var url; // var url;
var blob; var blob;
if (mimeType === 'image/xml+svg') { if (mimeType === "image/xml+svg") {
const xmlStr = new TextDecoder('utf-8').decode(array); var xmlStr = new TextDecoder("utf-8").decode(array);
return 'data:image/svg+xml;UTF-8,' + encodeURIComponent(xmlStr); return "data:image/svg+xml;UTF-8," + encodeURIComponent(xmlStr);
} }
// TODO: Move all this browser support testing to a common place // TODO: Move all this browser support testing to a common place
@@ -141,7 +143,7 @@ kthoom.ImageFile = function(file) {
var fileExtension = file.filename.split(".").pop().toLowerCase(); var fileExtension = file.filename.split(".").pop().toLowerCase();
this.mimeType = fileExtension === "png" ? "image/png" : this.mimeType = fileExtension === "png" ? "image/png" :
(fileExtension === "jpg" || fileExtension === "jpeg") ? "image/jpeg" : (fileExtension === "jpg" || fileExtension === "jpeg") ? "image/jpeg" :
fileExtension === "gif" ? "image/gif" : fileExtension == 'svg' ? 'image/xml+svg' : undefined; fileExtension === "gif" ? "image/gif" : fileExtension === "svg" ? "image/xml+svg" : undefined;
if ( this.mimeType !== undefined) { if ( this.mimeType !== undefined) {
this.dataURI = createURLFromArray(file.fileData, this.mimeType); this.dataURI = createURLFromArray(file.fileData, this.mimeType);
this.data = file; this.data = file;
@@ -155,16 +157,22 @@ function initProgressClick() {
currentImage = page; currentImage = page;
updatePage(); updatePage();
}); });
}; }
function loadFromArrayBuffer(ab) { function loadFromArrayBuffer(ab) {
var start = (new Date).getTime(); var start = (new Date).getTime();
var h = new Uint8Array(ab, 0, 10); var h = new Uint8Array(ab, 0, 10);
var pathToBitJS = "../../static/js/"; var pathToBitJS = "../../static/js/archive/";
var lastCompletion = 0;
if (h[0] === 0x52 && h[1] === 0x61 && h[2] === 0x72 && h[3] === 0x21) { //Rar! if (h[0] === 0x52 && h[1] === 0x61 && h[2] === 0x72 && h[3] === 0x21) { //Rar!
unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS); unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS);
} else if (h[0] === 80 && h[1] === 75) { //PK (Zip) } else if (h[0] === 80 && h[1] === 75) { //PK (Zip)
unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS); unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS);
} else if (h[0] === 255 && h[1] === 216) { // JPEG
// ToDo: check
updateProgress(100);
lastCompletion = 100;
return;
} else { // Try with tar } else { // Try with tar
unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS); unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
} }
@@ -176,9 +184,13 @@ function loadFromArrayBuffer(ab) {
if (totalImages === 0) { if (totalImages === 0) {
totalImages = e.totalFilesInArchive; totalImages = e.totalFilesInArchive;
} }
updateProgress(percentage *100); updateProgress(percentage * 100);
lastCompletion = percentage * 100; lastCompletion = percentage * 100;
}); });
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.INFO,
function(e) {
// console.log(e.msg); // Enable debug output here
});
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT, unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT,
function(e) { function(e) {
// convert DecompressedFile into a bunch of ImageFiles // convert DecompressedFile into a bunch of ImageFiles
@@ -203,8 +215,7 @@ function loadFromArrayBuffer(ab) {
if (imageFiles.length === currentImage + 1) { if (imageFiles.length === currentImage + 1) {
updatePage(lastCompletion); updatePage(lastCompletion);
} }
} } else {
else {
totalImages--; totalImages--;
} }
} }
@@ -223,22 +234,22 @@ function loadFromArrayBuffer(ab) {
function scrollTocToActive() { function scrollTocToActive() {
// Scroll to the thumbnail in the TOC on page change // Scroll to the thumbnail in the TOC on page change
$('#tocView').stop().animate({ $("#tocView").stop().animate({
scrollTop: $('#tocView a.active').position().top scrollTop: $("#tocView a.active").position().top
}, 200); }, 200);
} }
function updatePage() { function updatePage() {
$('.page').text((currentImage + 1 ) + "/" + totalImages); $(".page").text((currentImage + 1 ) + "/" + totalImages);
// Mark the current page in the TOC // Mark the current page in the TOC
$('#tocView a[data-page]') $("#tocView a[data-page]")
// Remove the currently active thumbnail // Remove the currently active thumbnail
.removeClass('active') .removeClass("active")
// Find the new one // Find the new one
.filter('[data-page='+ (currentImage + 1) +']') .filter("[data-page=" + (currentImage + 1) + "]")
// Set it to active // Set it to active
.addClass('active'); .addClass("active");
scrollTocToActive(); scrollTocToActive();
updateProgress(); updateProgress();
@@ -262,8 +273,8 @@ function updateProgress(loadPercentage) {
if (loadPercentage === 100) { if (loadPercentage === 100) {
$("#progress") $("#progress")
.removeClass('loading') .removeClass("loading")
.find(".load").text(''); .find(".load").text("");
} }
} }
@@ -318,7 +329,7 @@ function setImage(url) {
xhr.onload = function() { xhr.onload = function() {
$("#mainText").css("display", ""); $("#mainText").css("display", "");
$("#mainText").innerHTML("<iframe style=\"width:100%;height:700px;border:0\" src=\"data:text/html," + escape(xhr.responseText) + "\"></iframe>"); $("#mainText").innerHTML("<iframe style=\"width:100%;height:700px;border:0\" src=\"data:text/html," + escape(xhr.responseText) + "\"></iframe>");
} };
xhr.send(null); xhr.send(null);
} else if (!/(jpg|jpeg|png|gif)$/.test(imageFiles[currentImage].filename) && imageFiles[currentImage].data.uncompressedSize < 10 * 1024) { } else if (!/(jpg|jpeg|png|gif)$/.test(imageFiles[currentImage].filename) && imageFiles[currentImage].data.uncompressedSize < 10 * 1024) {
xhr.open("GET", url, true); xhr.open("GET", url, true);
@@ -370,17 +381,17 @@ function setImage(url) {
function showLeftPage() { function showLeftPage() {
if (settings.direction === 0) { if (settings.direction === 0) {
showPrevPage() showPrevPage();
} else { } else {
showNextPage() showNextPage();
} }
} }
function showRightPage() { function showRightPage() {
if (settings.direction === 0) { if (settings.direction === 0) {
showNextPage() showNextPage();
} else { } else {
showPrevPage() showPrevPage();
} }
} }
@@ -496,7 +507,7 @@ function keyHandler(evt) {
updateScale(false); updateScale(false);
break; break;
case kthoom.Key.SPACE: case kthoom.Key.SPACE:
var container = $('#mainContent'); var container = $("#mainContent");
var atTop = container.scrollTop() === 0; var atTop = container.scrollTop() === 0;
var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height(); var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height();
@@ -569,9 +580,9 @@ function init(filename) {
$(this).toggleClass("icon-menu icon-right"); $(this).toggleClass("icon-menu icon-right");
// We need this in a timeout because if we call it during the CSS transition, IE11 shakes the page ¯\_(ツ)_/¯ // We need this in a timeout because if we call it during the CSS transition, IE11 shakes the page ¯\_(ツ)_/¯
setTimeout(function(){ setTimeout(function() {
// Focus on the TOC or the main content area, depending on which is open // Focus on the TOC or the main content area, depending on which is open
$('#main:not(.closed) #mainContent, #sidebar.open #tocView').focus(); $("#main:not(.closed) #mainContent, #sidebar.open #tocView").focus();
scrollTocToActive(); scrollTocToActive();
}, 500); }, 500);
}); });
@@ -622,7 +633,7 @@ function init(filename) {
} }
// Focus the scrollable area so that keyboard scrolling work as expected // Focus the scrollable area so that keyboard scrolling work as expected
$('#mainContent').focus(); $("#mainContent").focus();
$("#mainImage").click(function(evt) { $("#mainImage").click(function(evt) {
// Firefox does not support offsetX/Y so we have to manually calculate // Firefox does not support offsetX/Y so we have to manually calculate
-209
View File
@@ -1,209 +0,0 @@
/**
* untar.js
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
*
* Reference Documentation:
*
* TAR format: http://www.gnu.org/software/automake/manual/tar/Standard.html
*/
// This file expects to be invoked as a Worker (see onmessage below).
importScripts('bytestream.js');
importScripts('archive.js');
const UnarchiveState = {
NOT_STARTED: 0,
UNARCHIVING: 1,
WAITING: 2,
FINISHED: 3,
};
// State - consider putting these into a class.
let unarchiveState = UnarchiveState.NOT_STARTED;
let bytestream = null;
let allLocalFiles = null;
let logToConsole = false;
// Progress variables.
let currentFilename = "";
let currentFileNumber = 0;
let currentBytesUnarchivedInFile = 0;
let currentBytesUnarchived = 0;
let totalUncompressedBytesInArchive = 0;
let totalFilesInArchive = 0;
// Helper functions.
const info = function(str) {
postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
};
const err = function(str) {
postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
};
// Removes all characters from the first zero-byte in the string onwards.
var readCleanString = function(bstr, numBytes) {
var str = bstr.readString(numBytes);
var zIndex = str.indexOf(String.fromCharCode(0));
return zIndex != -1 ? str.substr(0, zIndex) : str;
};
const postProgress = function() {
postMessage(new bitjs.archive.UnarchiveProgressEvent(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive,
bytestream.getNumBytesRead(),
));
};
class TarLocalFile {
// takes a ByteStream and parses out the local file information
constructor(bstream) {
this.isValid = false;
let bytesRead = 0;
// Read in the header block
this.name = readCleanString(bstream, 100);
this.mode = readCleanString(bstream, 8);
this.uid = readCleanString(bstream, 8);
this.gid = readCleanString(bstream, 8);
this.size = parseInt(readCleanString(bstream, 12), 8);
this.mtime = readCleanString(bstream, 12);
this.chksum = readCleanString(bstream, 8);
this.typeflag = readCleanString(bstream, 1);
this.linkname = readCleanString(bstream, 100);
this.maybeMagic = readCleanString(bstream, 6);
if (this.maybeMagic == "ustar") {
this.version = readCleanString(bstream, 2);
this.uname = readCleanString(bstream, 32);
this.gname = readCleanString(bstream, 32);
this.devmajor = readCleanString(bstream, 8);
this.devminor = readCleanString(bstream, 8);
this.prefix = readCleanString(bstream, 155);
if (this.prefix.length) {
this.name = this.prefix + this.name;
}
bstream.readBytes(12); // 512 - 500in
} else {
bstream.readBytes(255); // 512 - 257
}
bytesRead += 512;
// Done header, now rest of blocks are the file contents.
this.filename = this.name;
this.fileData = null;
info("Untarring file '" + this.filename + "'");
info(" size = " + this.size);
info(" typeflag = " + this.typeflag);
// A regular file.
if (this.typeflag == 0) {
info(" This is a regular file.");
const sizeInBytes = parseInt(this.size);
this.fileData = new Uint8Array(bstream.readBytes(sizeInBytes));
bytesRead += sizeInBytes;
if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) {
this.isValid = true;
}
// Round up to 512-byte blocks.
const remaining = 512 - bytesRead % 512;
if (remaining > 0 && remaining < 512) {
bstream.readBytes(remaining);
}
} else if (this.typeflag == 5) {
info(" This is a directory.")
}
}
}
const untar = function() {
let bstream = bytestream.tee();
// While we don't encounter an empty block, keep making TarLocalFiles.
while (bstream.peekNumber(4) != 0) {
const oneLocalFile = new TarLocalFile(bstream);
if (oneLocalFile && oneLocalFile.isValid) {
// If we make it to this point and haven't thrown an error, we have successfully
// read in the data for a local file, so we can update the actual bytestream.
bytestream = bstream.tee();
allLocalFiles.push(oneLocalFile);
totalUncompressedBytesInArchive += oneLocalFile.size;
// update progress
currentFilename = oneLocalFile.filename;
currentFileNumber = totalFilesInArchive++;
currentBytesUnarchivedInFile = oneLocalFile.size;
currentBytesUnarchived += oneLocalFile.size;
postMessage(new bitjs.archive.UnarchiveExtractEvent(oneLocalFile));
postProgress();
}
}
totalFilesInArchive = allLocalFiles.length;
postProgress();
bytestream = bstream.tee();
};
// event.data.file has the first ArrayBuffer.
// event.data.bytes has all subsequent ArrayBuffers.
onmessage = function(event) {
const bytes = event.data.file || event.data.bytes;
logToConsole = !!event.data.logToConsole;
// This is the very first time we have been called. Initialize the bytestream.
if (!bytestream) {
bytestream = new bitjs.io.ByteStream(bytes);
} else {
bytestream.push(bytes);
}
if (unarchiveState === UnarchiveState.NOT_STARTED) {
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
allLocalFiles = [];
postMessage(new bitjs.archive.UnarchiveStartEvent());
unarchiveState = UnarchiveState.UNARCHIVING;
postProgress();
}
if (unarchiveState === UnarchiveState.UNARCHIVING ||
unarchiveState === UnarchiveState.WAITING) {
try {
untar();
unarchiveState = UnarchiveState.FINISHED;
postMessage(new bitjs.archive.UnarchiveFinishEvent());
} catch (e) {
if (typeof e === 'string' && e.startsWith('Error! Overflowed')) {
// Overrun the buffer.
unarchiveState = UnarchiveState.WAITING;
} else {
console.error('Found an error while untarring');
console.dir(e);
throw e;
}
}
}
};
+4 -6
View File
@@ -150,9 +150,9 @@
{% endif %} {% endif %}
{% if cc|length > 0 %} {% if cc|length > 0 %}
<div class="custom_columns">
<p>
{% for c in cc %} {% for c in cc %}
<div class="real_custom_columns">
{% if entry['custom_column_' ~ c.id]|length > 0 %} {% if entry['custom_column_' ~ c.id]|length > 0 %}
{{ c.name }}: {{ c.name }}:
{% for column in entry['custom_column_' ~ c.id] %} {% for column in entry['custom_column_' ~ c.id] %}
@@ -170,11 +170,9 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<br />
{% endif %} {% endif %}
{% endfor %}
</p>
</div> </div>
{% endfor %}
{% endif %} {% endif %}
{% if not g.user.is_anonymous %} {% if not g.user.is_anonymous %}
@@ -193,7 +191,7 @@
{% if entry.comments|length > 0 and entry.comments[0].text|length > 0%} {% if entry.comments|length > 0 and entry.comments[0].text|length > 0%}
<div class="comments"> <div class="comments">
<h3>{{_('Description:')}}</h3> <h3 id="decription">{{_('Description:')}}</h3>
{{entry.comments[0].text|safe}} {{entry.comments[0].text|safe}}
</div> </div>
{% endif %} {% endif %}
+3 -9
View File
@@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Comic Reader</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Comic Reader</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
@@ -15,7 +15,7 @@
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/kthoom.js') }}"></script> <script src="{{ url_for('static', filename='js/kthoom.js') }}"></script>
<script src="{{ url_for('static', filename='js/archive.js') }}"></script> <script src="{{ url_for('static', filename='js/archive/archive.js') }}"></script>
<script> <script>
var updateArrows = function() { var updateArrows = function() {
if ($('input[name="direction"]:checked').val() === "0") { if ($('input[name="direction"]:checked').val() === "0") {
@@ -31,17 +31,11 @@
init("{{ url_for('serve_book', book_id=comicfile, book_format=extension) }}"); init("{{ url_for('serve_book', book_id=comicfile, book_format=extension) }}");
updateArrows(); updateArrows();
} }
}; }
</script> </script>
</head> </head>
<body> <body>
<div id="sidebar"> <div id="sidebar">
<!--
<div id="panels">
<a id="show-Toc" class="show_view icon-list-1 active" data-view="Toc">TOC</a>
<a id="show-Bookmarks" class="show_view icon-bookmark" data-view="Bookmarks">Bookmarks</a>
</div>
-->
<div id="tocView" class="view" tabindex="-1"> <div id="tocView" class="view" tabindex="-1">
<ul id="thumbnails"></ul> <ul id="thumbnails"></ul>
</div> </div>
Binary file not shown.
File diff suppressed because it is too large Load Diff
+30 -2
View File
@@ -30,7 +30,7 @@ from werkzeug.security import generate_password_hash
import json import json
import datetime import datetime
from binascii import hexlify from binascii import hexlify
import cli from cps import cli
engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False) engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False)
Base = declarative_base() Base = declarative_base()
@@ -179,6 +179,7 @@ class UserBase:
# User Base (all access methods are declared there) # User Base (all access methods are declared there)
class User(UserBase, Base): class User(UserBase, Base):
__tablename__ = 'user' __tablename__ = 'user'
__table_args__ = {'sqlite_autoincrement': True}
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
nickname = Column(String(64), unique=True) nickname = Column(String(64), unique=True)
@@ -715,7 +716,34 @@ def migrate_Database():
conn = engine.connect() conn = engine.connect()
conn.execute("ALTER TABLE Settings ADD column `config_updatechannel` INTEGER DEFAULT 0") conn.execute("ALTER TABLE Settings ADD column `config_updatechannel` INTEGER DEFAULT 0")
session.commit() session.commit()
try:
# check if one table with autoincrement is existing (should be user table)
conn = engine.connect()
conn.execute("SELECT COUNT(*) FROM sqlite_sequence WHERE name='user'")
except exc.OperationalError:
# Create new table user_id and copy contents of table user into it
conn = engine.connect()
conn.execute("CREATE TABLE user_id (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
"nickname VARCHAR(64),"
"email VARCHAR(120),"
"role SMALLINT,"
"password VARCHAR,"
"kindle_mail VARCHAR(120),"
"locale VARCHAR(2),"
"sidebar_view INTEGER,"
"default_language VARCHAR(3),"
"mature_content BOOLEAN,"
"UNIQUE (nickname),"
"UNIQUE (email),"
"CHECK (mature_content IN (0, 1)))")
conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale,"
"sidebar_view, default_language, mature_content) "
"SELECT id, nickname, email, role, password, kindle_mail, locale,"
"sidebar_view, default_language, mature_content FROM user")
# delete old user table and rename new user_id table to user:
conn.execute("DROP TABLE user")
conn.execute("ALTER TABLE user_id RENAME TO user")
session.commit()
# Remove login capability of user Guest # Remove login capability of user Guest
conn = engine.connect() conn = engine.connect()
+15 -8
View File
@@ -237,7 +237,7 @@ class Updater(threading.Thread):
return False return False
def _stable_version_info(self): def _stable_version_info(self):
return {'version': '0.6.3'} # Current version return {'version': '0.6.4'} # Current version
def _nightly_available_updates(self, request_method): def _nightly_available_updates(self, request_method):
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone) tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
@@ -426,18 +426,25 @@ class Updater(threading.Thread):
minor_version_update = int(commit[i]['tag_name'].split('.')[1]) minor_version_update = int(commit[i]['tag_name'].split('.')[1])
patch_version_update = int(commit[i]['tag_name'].split('.')[2]) patch_version_update = int(commit[i]['tag_name'].split('.')[2])
current_version[0] = int(current_version[0])
current_version[1] = int(current_version[1])
try:
current_version[2] = int(current_version[2])
except ValueError:
current_version[2] = int(current_version[2].split(' ')[0])-1
# Check if major versions are identical search for newest nonenqual commit and update to this one # Check if major versions are identical search for newest nonenqual commit and update to this one
if major_version_update == int(current_version[0]): if major_version_update == current_version[0]:
if (minor_version_update == int(current_version[1]) and if (minor_version_update == current_version[1] and
patch_version_update > int(current_version[2])) or \ patch_version_update > current_version[2]) or \
minor_version_update > int(current_version[1]): minor_version_update > current_version[1]:
parents.append([commit[i]['tag_name'],commit[i]['body'].replace('\r\n', '<p>')]) parents.append([commit[i]['tag_name'],commit[i]['body'].replace('\r\n', '<p>')])
i -= 1 i -= 1
continue continue
if major_version_update < int(current_version[0]): if major_version_update < current_version[0]:
i -= 1 i -= 1
continue continue
if major_version_update > int(current_version[0]): if major_version_update > current_version[0]:
# found update update to last version before major update, unless current version is on last version # found update update to last version before major update, unless current version is on last version
# before major update # before major update
if commit[i+1]['tag_name'].split('.')[1] == current_version[1]: if commit[i+1]['tag_name'].split('.')[1] == current_version[1]:
@@ -466,7 +473,7 @@ class Updater(threading.Thread):
'update': True, 'update': True,
'success': True, 'success': True,
'message': _( 'message': _(
u'A new update is available. Click on the button below to update to the latest version.'), u'Click on the button below to update to the latest stable version.'),
'history': parents 'history': parents
}) })
self.updateFile = commit[0]['zipball_url'] self.updateFile = commit[0]['zipball_url']
+38 -13
View File
@@ -112,7 +112,7 @@ try:
from natsort import natsorted as sort from natsort import natsorted as sort
except ImportError: except ImportError:
sort=sorted # Just use regular sort then sort=sorted # Just use regular sort then
# may cause issues with badly named pages in cbz/cbr files # may cause issues with badly named pages in cbz/cbr files
try: try:
import cPickle import cPickle
except ImportError: except ImportError:
@@ -3171,13 +3171,22 @@ def new_user():
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
title=_(u"Add new user")) title=_(u"Add new user"))
content.password = generate_password_hash(to_save["password"]) content.password = generate_password_hash(to_save["password"])
content.nickname = to_save["nickname"] existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == to_save["nickname"].lower())\
if config.config_public_reg and not check_valid_domain(to_save["email"]): .first()
flash(_(u"E-mail is not from valid domain"), category="error") existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower())\
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, .first()
title=_(u"Add new user")) if not existing_user and not existing_email:
content.nickname = to_save["nickname"]
if config.config_public_reg and not check_valid_domain(to_save["email"]):
flash(_(u"E-mail is not from valid domain"), category="error")
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
title=_(u"Add new user"))
else:
content.email = to_save["email"]
else: else:
content.email = to_save["email"] flash(_(u"Found an existing account for this e-mail address or nickname."), category="error")
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
languages=languages, title=_(u"Add new user"), page="newuser")
try: try:
ub.session.add(content) ub.session.add(content)
ub.session.commit() ub.session.commit()
@@ -3246,10 +3255,16 @@ def edit_user(user_id):
if request.method == "POST": if request.method == "POST":
to_save = request.form.to_dict() to_save = request.form.to_dict()
if "delete" in to_save: if "delete" in to_save:
ub.session.query(ub.User).filter(ub.User.id == content.id).delete() if ub.session.query(ub.User).filter(and_(ub.User.role.op('&')
ub.session.commit() (ub.ROLE_ADMIN)== ub.ROLE_ADMIN,
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success") ub.User.id != content.id)).count():
return redirect(url_for('admin')) ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
ub.session.commit()
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
return redirect(url_for('admin'))
else:
flash(_(u"No admin user remaining, can't delete user", nick=content.nickname), category="error")
return redirect(url_for('admin'))
else: else:
if "password" in to_save and to_save["password"]: if "password" in to_save and to_save["password"]:
content.password = generate_password_hash(to_save["password"]) content.password = generate_password_hash(to_save["password"])
@@ -3356,14 +3371,24 @@ def edit_user(user_id):
if "locale" in to_save and to_save["locale"]: if "locale" in to_save and to_save["locale"]:
content.locale = to_save["locale"] content.locale = to_save["locale"]
if to_save["email"] and to_save["email"] != content.email: if to_save["email"] and to_save["email"] != content.email:
content.email = to_save["email"] existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()) \
.first()
if not existing_email:
content.email = to_save["email"]
else:
flash(_(u"Found an existing account for this e-mail address."), category="error")
return render_title_template("user_edit.html", translations=translations, languages=languages,
new_user=0, content=content, downloads=downloads,
title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser")
if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail: if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail:
content.kindle_mail = to_save["kindle_mail"] content.kindle_mail = to_save["kindle_mail"]
try: try:
ub.session.commit() ub.session.commit()
flash(_(u"User '%(nick)s' updated", nick=content.nickname), category="success") flash(_(u"User '%(nick)s' updated", nick=content.nickname), category="success")
except IntegrityError: except IntegrityError as e:
ub.session.rollback() ub.session.rollback()
print(e)
flash(_(u"An unknown error occured."), category="error") flash(_(u"An unknown error occured."), category="error")
return render_title_template("user_edit.html", translations=translations, languages=languages, new_user=0, return render_title_template("user_edit.html", translations=translations, languages=languages, new_user=0,
content=content, downloads=downloads, title=_(u"Edit User %(nick)s", content=content, downloads=downloads, title=_(u"Edit User %(nick)s",
+15 -21
View File
@@ -20,7 +20,7 @@
from __future__ import print_function from __future__ import print_function
import smtplib import smtplib
import threading import threading
from datetime import datetime from datetime import datetime, timedelta
import logging import logging
import time import time
import socket import socket
@@ -221,8 +221,10 @@ class WorkerThread(threading.Thread):
if self.UIqueue[self.current]['stat'] == STAT_STARTED: if self.UIqueue[self.current]['stat'] == STAT_STARTED:
if self.queue[self.current]['taskType'] == TASK_EMAIL: if self.queue[self.current]['taskType'] == TASK_EMAIL:
self.UIqueue[self.current]['progress'] = self.get_send_status() self.UIqueue[self.current]['progress'] = self.get_send_status()
self.UIqueue[self.current]['runtime'] = self._formatRuntime( self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
datetime.now() - self.queue[self.current]['starttime']) self.UIqueue[self.current]['rt'] = self.UIqueue[self.current]['formRuntime'].days*24*60 \
+ self.UIqueue[self.current]['formRuntime'].seconds \
+ self.UIqueue[self.current]['formRuntime'].microseconds
return self.UIqueue return self.UIqueue
def _convert_any_format(self): def _convert_any_format(self):
@@ -259,7 +261,8 @@ class WorkerThread(threading.Thread):
self._handleSuccess() self._handleSuccess()
return file_path + format_new_ext return file_path + format_new_ext
else: else:
web.app.logger.info("Book id %d - target format of %s does not exist. Moving forward with convert.", bookid, format_new_ext) web.app.logger.info("Book id %d - target format of %s does not exist. Moving forward with convert.",
bookid, format_new_ext)
# check if converter-executable is existing # check if converter-executable is existing
if not os.path.exists(web.ub.config.config_converterpath): if not os.path.exists(web.ub.config.config_converterpath):
@@ -300,7 +303,7 @@ class WorkerThread(threading.Thread):
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
command = [x.encode(sys.getfilesystemencoding()) for x in command] command = [x.encode(sys.getfilesystemencoding()) for x in command]
p = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True) p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
except OSError as e: except OSError as e:
self._handleError(_(u"Ebook-converter failed: %(error)s", error=e)) self._handleError(_(u"Ebook-converter failed: %(error)s", error=e))
return return
@@ -328,6 +331,11 @@ class WorkerThread(threading.Thread):
# process returncode # process returncode
check = p.returncode check = p.returncode
calibre_traceback = p.stderr.readlines()
for ele in calibre_traceback:
web.app.logger.debug(ele.strip('\n'))
if not ele.startswith('Traceback') and not ele.startswith(' File'):
error_message = "Calibre failed with error: %s" % ele.strip('\n')
# kindlegen returncodes # kindlegen returncodes
# 0 = Info(prcgen):I1036: Mobi file built successfully # 0 = Info(prcgen):I1036: Mobi file built successfully
@@ -481,31 +489,17 @@ class WorkerThread(threading.Thread):
self._handleError(u'Error sending email: ' + e.strerror) self._handleError(u'Error sending email: ' + e.strerror)
return None return None
def _formatRuntime(self, runtime):
self.UIqueue[self.current]['rt'] = runtime.total_seconds()
val = re.split('\:|\.', str(runtime))[0:3]
erg = list()
for v in val:
if int(v) > 0:
erg.append(v)
retVal = (':'.join(erg)).lstrip('0') + ' s'
if retVal == ' s':
retVal = '0 s'
return retVal
def _handleError(self, error_message): def _handleError(self, error_message):
web.app.logger.error(error_message) web.app.logger.error(error_message)
self.UIqueue[self.current]['stat'] = STAT_FAIL self.UIqueue[self.current]['stat'] = STAT_FAIL
self.UIqueue[self.current]['progress'] = "100 %" self.UIqueue[self.current]['progress'] = "100 %"
self.UIqueue[self.current]['runtime'] = self._formatRuntime( self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
datetime.now() - self.queue[self.current]['starttime'])
self.UIqueue[self.current]['message'] = error_message self.UIqueue[self.current]['message'] = error_message
def _handleSuccess(self): def _handleSuccess(self):
self.UIqueue[self.current]['stat'] = STAT_FINISH_SUCCESS self.UIqueue[self.current]['stat'] = STAT_FINISH_SUCCESS
self.UIqueue[self.current]['progress'] = "100 %" self.UIqueue[self.current]['progress'] = "100 %"
self.UIqueue[self.current]['runtime'] = self._formatRuntime( self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
datetime.now() - self.queue[self.current]['starttime'])
# Enable logging of smtp lib debug output # Enable logging of smtp lib debug output
+1 -1
View File
@@ -20,4 +20,4 @@ Pillow>=4.0.0
rarfile>=2.7 rarfile>=2.7
# other # other
natsort>=2.2.0 natsort>=2.2.0
git+https://github.com/OzzieIsaacs/comicapi.git@5346716578b2843f54d522f44d01bc8d25001d24#egg=comicapi
+20 -10
View File
@@ -56,20 +56,30 @@ Optionally, to enable on-the-fly conversion from one ebook format to another whe
[Download](http://www.amazon.com/gp/feature.html?docId=1000765211) Amazon's KindleGen tool for your platform and place the binary named as `kindlegen` in the `vendor` folder. [Download](http://www.amazon.com/gp/feature.html?docId=1000765211) Amazon's KindleGen tool for your platform and place the binary named as `kindlegen` in the `vendor` folder.
## Docker images ## Docker Images
Pre-built Docker images based on Alpine Linux are available in these Docker Hub repositories: Pre-built Docker images are available in these Docker Hub repositories:
**x64** #### **Technosoft2000 - x64**
+ **technosoft2000** at [technosoft2000/calibre-web](https://hub.docker.com/r/technosoft2000/calibre-web/). If you want the option to convert/download ebooks in multiple formats, use this image as it includes Calibre's ebook-convert binary. The "path to convertertool" should be set to /opt/calibre/ebook-convert. + Docker Hub - [https://hub.docker.com/r/technosoft2000/calibre-web/](https://hub.docker.com/r/technosoft2000/calibre-web/)
+ **linuxserver.io** at [linuxserver/calibre-web](https://hub.docker.com/r/linuxserver/calibre-web/). Cannot convert between ebook formats. + Github - [https://github.com/Technosoft2000/docker-calibre-web](https://github.com/Technosoft2000/docker-calibre-web)
**armhf** Includes the Calibre `ebook-convert` binary.
+ **linuxserver.io** at [lsioarmhf/calibre-web](https://hub.docker.com/r/lsioarmhf/calibre-web/) + The "path to convertertool" should be set to `/opt/calibre/ebook-convert`
**aarch64** #### **LinuxServer - x64, armhf, aarch64**
+ **linuxserver.io** at [lsioarmhf/calibre-web-aarch64](https://hub.docker.com/r/lsioarmhf/calibre-web-aarch64) + Docker Hub - [https://hub.docker.com/r/linuxserver/calibre-web/](https://hub.docker.com/r/linuxserver/calibre-web/)
+ Github - [https://github.com/linuxserver/docker-calibre-web](https://github.com/linuxserver/docker-calibre-web)
+ Github - (Optional Calibre layer) - [https://github.com/linuxserver/docker-calibre-web/tree/calibre](https://github.com/linuxserver/docker-calibre-web/tree/calibre)
This image has the option to pull in an extra docker manifest layer to include the Calibre `ebook-convert` binary. Just include the environmental variable `DOCKER_MODS=linuxserver/calibre-web:calibre` in your docker run/docker compose file. **(x64 only)**
If you do not need this functionality then this can be omitted, keeping the image as lightweight as possible.
Both the Calibre-Web and Calibre-Mod images are rebuilt automatically on new releases of Calibre-Web and Calibre respectively, and on updates to any included base image packages on a weekly basis if required.
+ The "path to convertertool" should be set to `/usr/bin/ebook-convert`
+ The "path to unrar" should be set to `/usr/bin/unrar`
# Wiki # Wiki
For further informations, How To's and FAQ please check the ![Wiki](../../wiki) For further informations, How To's and FAQ please check the [Wiki](../../wiki)
+81
View File
@@ -0,0 +1,81 @@
[bdist_wheel]
universal = 1
[metadata]
name = calibre-web
url = https://github.com/janeczku/calibre-web
project_urls =
Bug Tracker = https://github.com/janeczku/calibre-web/issues
Release Management = https://github.com/janeczku/calibre-web/releases
Documentation = https://github.com/janeczku/calibre-web/wiki
Source Code = https://github.com/janeczku/calibre-web
description = Web app for browsing, reading and downloading eBooks stored in a Calibre database.
long_description = file: README.md
author = @janeczku
maintainer = @janeczku
license = GPLv3+
license_file = LICENSE
classifiers =
Development Status :: 5 - Production/Stable
License :: OSI Approved :: GNU Affero General Public License v3
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
keywords =
calibre
calibre-web
library
[options]
python_requires = >=2.6
packages = find:
include_package_data = True
zip_safe = False
install_requires =
Babel >= 1.3
Flask-Babel >= 0.11.1
Flask-Login >= 0.3.2
Flask-Principal >= 0.3.2
singledispatch >= 3.4.0.0
backports_abc >= 0.4
Flask >= 0.11
iso-639 >= 0.4.5
PyPDF2 == 1.26.0
pytz >= 2016.10
requests >= 2.11.1
SQLAlchemy >= 1.1.0
tornado >= 4.1
unidecode >= 0.04.19
[options.extras_require]
gdrive =
google-api-python-client == 1.6.1
google-api-python-client==1.6.1
gevent==1.2.1
greenlet==0.4.12
httplib2==0.9.2
oauth2client==4.0.0
uritemplate==3.0.0
pyasn1-modules==0.0.8
pyasn1==0.1.9
PyDrive==1.3.1
PyYAML==3.12
rsa==3.4.2
six==1.10.0
goodreads =
goodreads >= 0.3.2
python-Levenshtein>=0.12.0
metadata =
lxml>=3.8.0
Pillow>=4.0.0
rarfile>=2.7
Wand >= 0.4.4
comics=
natsort>=2.2.0
comicapi @ git+https://github.com/OzzieIsaacs/comicapi.git@5346716578b2843f54d522f44d01bc8d25001d24#egg=comicapi
[options.packages.find]
where = .
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2019 decentral1se
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# """Calibre-web distribution package setuptools installer."""
from setuptools import setup
setup()