Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aba88ae53a | ||
|
|
67d0ddb180 | ||
|
|
b1bb1cfdfa | ||
|
|
37736e11d5 | ||
|
|
792367e35e | ||
|
|
b1cb7123a3 | ||
|
|
f79d549910 | ||
|
|
66283c542f | ||
|
|
c1d5f77fe8 | ||
|
|
d9f69ca264 | ||
|
|
cd546eb6d4 | ||
|
|
e67d707867 | ||
|
|
3f5c6c1fa5 | ||
|
|
f5e3ed26b9 | ||
|
|
c81d4edb7d | ||
|
|
f736a15c12 | ||
|
|
87ca05f129 | ||
|
|
b3a286c0b5 | ||
|
|
26e45f1f57 | ||
|
|
df5d15d1a2 | ||
|
|
ecedf92783 | ||
|
|
867aa2f0bd | ||
|
|
7982ed877c | ||
|
|
c0d136ccd8 | ||
|
|
029d299067 | ||
|
|
b7e30644ab | ||
|
|
cbdc9876b2 | ||
|
|
05d0f12608 |
@@ -0,0 +1,3 @@
|
|||||||
|
include cps/static/*
|
||||||
|
include cps/templates/*
|
||||||
|
include cps/translations/*
|
||||||
Regular → Executable
+75
-25
@@ -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
@@ -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
@@ -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()
|
||||||
|
|||||||
Vendored
+1
-1
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;
|
||||||
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
@@ -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];
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
Executable
+237
@@ -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);
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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,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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 
|
For further informations, How To's and FAQ please check the [Wiki](../../wiki)
|
||||||
|
|||||||
@@ -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 = .
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user