From 8c573ff6d829221f00d2204642803d6a4421e8b2 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sat, 18 Nov 2017 10:34:21 +0100 Subject: [PATCH 01/19] Unrar/tar and unzip on server side --- cps/static/js/archive.js | 364 -------------- cps/static/js/io.js | 484 ------------------ cps/static/js/kthoom.js | 215 ++++---- cps/static/js/unrar.js | 891 --------------------------------- cps/static/js/untar.js | 168 ------- cps/static/js/unzip.js | 621 ----------------------- cps/templates/config_edit.html | 9 +- cps/templates/readcbr.html | 4 +- cps/ub.py | 9 + cps/web.py | 225 ++++++--- optional-requirements.txt | 1 + 11 files changed, 267 insertions(+), 2724 deletions(-) delete mode 100644 cps/static/js/archive.js delete mode 100644 cps/static/js/io.js delete mode 100644 cps/static/js/unrar.js delete mode 100644 cps/static/js/untar.js delete mode 100644 cps/static/js/unzip.js diff --git a/cps/static/js/archive.js b/cps/static/js/archive.js deleted file mode 100644 index 28aae182..00000000 --- a/cps/static/js/archive.js +++ /dev/null @@ -1,364 +0,0 @@ -/** - * archive.js - * - * Provides base functionality for unarchiving. - * - * Licensed under the MIT License - * - * Copyright(c) 2011 Google Inc. - */ - -/* global bitjs */ - -var bitjs = bitjs || {}; -bitjs.archive = bitjs.archive || {}; - -(function() { - - // =========================================================================== - // Stolen from Closure because it's the best way to do Java-like inheritance. - bitjs.base = function(me, optMethodName, varArgs) { - var caller = arguments.callee.caller; - if (caller.superClass_) { - // This is a constructor. Call the superclass constructor. - return caller.superClass_.constructor.apply( - me, Array.prototype.slice.call(arguments, 1)); - } - - var args = Array.prototype.slice.call(arguments, 2); - var foundCaller = false; - for (var ctor = me.constructor; - ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) { - if (ctor.prototype[optMethodName] === caller) { - foundCaller = true; - } else if (foundCaller) { - return ctor.prototype[optMethodName].apply(me, args); - } - } - - // If we did not find the caller in the prototype chain, - // then one of two things happened: - // 1) The caller is an instance method. - // 2) This method was not called by the right caller. - if (me[optMethodName] === caller) { - return me.constructor.prototype[optMethodName].apply(me, args); - } else { - throw Error( - "goog.base called from a method of one name " + - "to a method of a different name"); - } - }; - bitjs.inherits = function(childCtor, parentCtor) { - /** @constructor */ - function TempCtor() {} - TempCtor.prototype = parentCtor.prototype; - childCtor.superClass_ = parentCtor.prototype; - childCtor.prototype = new TempCtor(); - childCtor.prototype.constructor = childCtor; - }; - // =========================================================================== - - /** - * An unarchive event. - * - * @param {string} type The event type. - * @constructor - */ - bitjs.archive.UnarchiveEvent = function(type) { - /** - * The event type. - * - * @type {string} - */ - this.type = type; - }; - - /** - * The UnarchiveEvent types. - */ - bitjs.archive.UnarchiveEvent.Type = { - START: "start", - PROGRESS: "progress", - EXTRACT: "extract", - FINISH: "finish", - INFO: "info", - ERROR: "error" - }; - - /** - * Useful for passing info up to the client (for debugging). - * - * @param {string} msg The info message. - */ - bitjs.archive.UnarchiveInfoEvent = function(msg) { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO); - - /** - * The information message. - * - * @type {string} - */ - this.msg = msg; - }; - bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent); - - /** - * An unrecoverable error has occured. - * - * @param {string} msg The error message. - */ - bitjs.archive.UnarchiveErrorEvent = function(msg) { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR); - - /** - * The information message. - * - * @type {string} - */ - this.msg = msg; - }; - bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent); - - /** - * Start event. - * - * @param {string} msg The info message. - */ - bitjs.archive.UnarchiveStartEvent = function() { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.START); - }; - bitjs.inherits(bitjs.archive.UnarchiveStartEvent, bitjs.archive.UnarchiveEvent); - - /** - * Finish event. - * - * @param {string} msg The info message. - */ - bitjs.archive.UnarchiveFinishEvent = function() { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.FINISH); - }; - bitjs.inherits(bitjs.archive.UnarchiveFinishEvent, bitjs.archive.UnarchiveEvent); - - /** - * Progress event. - */ - bitjs.archive.UnarchiveProgressEvent = function( - currentFilename, - currentFileNumber, - currentBytesUnarchivedInFile, - currentBytesUnarchived, - totalUncompressedBytesInArchive, - totalFilesInArchive) - { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.PROGRESS); - - this.currentFilename = currentFilename; - this.currentFileNumber = currentFileNumber; - this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile; - this.totalFilesInArchive = totalFilesInArchive; - this.currentBytesUnarchived = currentBytesUnarchived; - this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive; - }; - bitjs.inherits(bitjs.archive.UnarchiveProgressEvent, bitjs.archive.UnarchiveEvent); - - /** - * All extracted files returned by an Unarchiver will implement - * the following interface: - * - * interface UnarchivedFile { - * string filename - * TypedArray fileData - * } - * - */ - - /** - * Extract event. - */ - bitjs.archive.UnarchiveExtractEvent = function(unarchivedFile) { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT); - - /** - * @type {UnarchivedFile} - */ - this.unarchivedFile = unarchivedFile; - }; - bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent); - - - /** - * Base class for all Unarchivers. - * - * @param {ArrayBuffer} arrayBuffer The Array Buffer. - * @param {string} optPathToBitJS Optional string for where the BitJS files are located. - * @constructor - */ - bitjs.archive.Unarchiver = function(arrayBuffer, optPathToBitJS) { - /** - * The ArrayBuffer object. - * @type {ArrayBuffer} - * @protected - */ - this.ab = arrayBuffer; - - /** - * The path to the BitJS files. - * @type {string} - * @private - */ - this.pathToBitJS_ = optPathToBitJS || ""; - - /** - * A map from event type to an array of listeners. - * @type {Map.} - */ - this.listeners_ = {}; - for (var type in bitjs.archive.UnarchiveEvent.Type) { - this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = []; - } - }; - - /** - * Private web worker initialized during start(). - * @type {Worker} - * @private - */ - bitjs.archive.Unarchiver.prototype.worker_ = null; - - /** - * This method must be overridden by the subclass to return the script filename. - * @return {string} The script filename. - * @protected. - */ - bitjs.archive.Unarchiver.prototype.getScriptFileName = function() { - throw "Subclasses of AbstractUnarchiver must overload getScriptFileName()"; - }; - - /** - * Adds an event listener for UnarchiveEvents. - * - * @param {string} Event type. - * @param {function} An event handler function. - */ - bitjs.archive.Unarchiver.prototype.addEventListener = function(type, listener) { - if (type in this.listeners_) { - if (this.listeners_[type].indexOf(listener) === -1) { - this.listeners_[type].push(listener); - } - } - }; - - /** - * Removes an event listener. - * - * @param {string} Event type. - * @param {EventListener|function} An event listener or handler function. - */ - bitjs.archive.Unarchiver.prototype.removeEventListener = function(type, listener) { - if (type in this.listeners_) { - var index = this.listeners_[type].indexOf(listener); - if (index !== -1) { - this.listeners_[type].splice(index, 1); - } - } - }; - - /** - * Receive an event and pass it to the listener functions. - * - * @param {bitjs.archive.UnarchiveEvent} e - * @private - */ - bitjs.archive.Unarchiver.prototype.handleWorkerEvent_ = function(e) { - if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) && - this.listeners_[e.type] instanceof Array) { - this.listeners_[e.type].forEach(function (listener) { - listener(e); - }); - if (e.type === bitjs.archive.UnarchiveEvent.Type.FINISH) { - this.worker_.terminate(); - } - } else { - console.log(e); - } - }; - - /** - * Starts the unarchive in a separate Web Worker thread and returns immediately. - */ - bitjs.archive.Unarchiver.prototype.start = function() { - var me = this; - var scriptFileName = this.pathToBitJS_ + this.getScriptFileName(); - if (scriptFileName) { - this.worker_ = new Worker(scriptFileName); - - this.worker_.onerror = function(e) { - console.log("Worker error: message = " + e.message); - throw e; - }; - - this.worker_.onmessage = function(e) { - if (typeof e.data === "string") { - // Just log any strings the workers pump our way. - console.log(e.data); - } else { - // Assume that it is an UnarchiveEvent. Some browsers preserve the 'type' - // so that instanceof UnarchiveEvent returns true, but others do not. - me.handleWorkerEvent_(e.data); - } - }; - - this.worker_.postMessage({file: this.ab}); - } - }; - - /** - * Terminates the Web Worker for this Unarchiver and returns immediately. - */ - bitjs.archive.Unarchiver.prototype.stop = function() { - if (this.worker_) { - this.worker_.terminate(); - } - }; - - - /** - * Unzipper - * @extends {bitjs.archive.Unarchiver} - * @constructor - */ - bitjs.archive.Unzipper = function(arrayBuffer, optPathToBitJS) { - bitjs.base(this, arrayBuffer, optPathToBitJS); - }; - bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver); - bitjs.archive.Unzipper.prototype.getScriptFileName = function() { - return "unzip.js"; - }; - - /** - * Unrarrer - * @extends {bitjs.archive.Unarchiver} - * @constructor - */ - bitjs.archive.Unrarrer = function(arrayBuffer, optPathToBitJS) { - bitjs.base(this, arrayBuffer, optPathToBitJS); - }; - bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver); - bitjs.archive.Unrarrer.prototype.getScriptFileName = function() { - return "unrar.js"; - }; - - /** - * Untarrer - * @extends {bitjs.archive.Unarchiver} - * @constructor - */ - bitjs.archive.Untarrer = function(arrayBuffer, optPathToBitJS) { - bitjs.base(this, arrayBuffer, optPathToBitJS); - }; - bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver); - bitjs.archive.Untarrer.prototype.getScriptFileName = function() { - return "untar.js"; - }; - -})(); diff --git a/cps/static/js/io.js b/cps/static/js/io.js deleted file mode 100644 index 6cc4d81c..00000000 --- a/cps/static/js/io.js +++ /dev/null @@ -1,484 +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.|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); - } - }; -})(); diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index 17ee8097..0dd41322 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -17,6 +17,8 @@ */ /* global bitjs */ +var start=0; + if (window.opera) { window.console.log = function(str) { opera.postError(str); @@ -127,11 +129,11 @@ var createURLFromArray = function(array, mimeType) { // This would save 25% on memory since base64-encoded strings are 4/3 the size of the binary kthoom.ImageFile = function(file) { this.filename = file.filename; - var fileExtension = file.filename.split(".").pop().toLowerCase(); + /*var fileExtension = file.filename.split(".").pop().toLowerCase(); var mimeType = fileExtension === "png" ? "image/png" : (fileExtension === "jpg" || fileExtension === "jpeg") ? "image/jpeg" : - fileExtension === "gif" ? "image/gif" : null; - this.dataURI = createURLFromArray(file.fileData, mimeType); + fileExtension === "gif" ? "image/gif" : null;*/ + this.dataURI = file.fileData; // createURLFromArray(file.fileData, mimeType); this.data = file; }; @@ -270,50 +272,22 @@ kthoom.setProgressMeter = function(pct, optLabel) { } function loadFromArrayBuffer(ab) { - var start = (new Date).getTime(); - var h = new Uint8Array(ab, 0, 10); - var pathToBitJS = "../../static/js/"; - 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); + var f=[]; + f.fileData=ab.content; + f.filename=ab.name; + // add any new pages based on the filename + if (imageFilenames.indexOf(f.filename) === -1) { + imageFilenames.push(f.filename); + imageFiles.push(new kthoom.ImageFile(f)); } - // Listen for UnarchiveEvents. - if (unarchiver) { - unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS, - function(e) { - var percentage = e.currentBytesUnarchived / e.totalUncompressedBytesInArchive; - totalImages = e.totalFilesInArchive; - kthoom.setProgressMeter(percentage, "Unzipping"); - // display nav - lastCompletion = percentage * 100; - }); - unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT, - function(e) { - // convert DecompressedFile into a bunch of ImageFiles - if (e.unarchivedFile) { - var f = e.unarchivedFile; - // add any new pages based on the filename - if (imageFilenames.indexOf(f.filename) === -1) { - imageFilenames.push(f.filename); - imageFiles.push(new kthoom.ImageFile(f)); - } - } - // display first page if we haven't yet - if (imageFiles.length === currentImage + 1) { - updatePage(); - } - }); - unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.FINISH, - function() { - var diff = ((new Date).getTime() - start) / 1000; - console.log("Unarchiving done in " + diff + "s"); - }); - unarchiver.start(); - } else { - alert("Some error"); + var percentage = (ab.page+1) / (ab.last+1); + totalImages = ab.last+1; + kthoom.setProgressMeter(percentage, "Unzipping"); + lastCompletion = percentage * 100; + + // display first page if we haven't yet + if (imageFiles.length === currentImage + 1) { + updatePage(); } } @@ -524,69 +498,90 @@ function keyHandler(evt) { } } -function init(filename) { - if (!window.FileReader) { - alert("Sorry, kthoom will not work with your browser because it does not support the File API. Please try kthoom with Chrome 12+ or Firefox 7+"); - } else { - var request = new XMLHttpRequest(); - request.open("GET", filename); - request.responseType = "arraybuffer"; - request.setRequestHeader("X-Test", "test1"); - request.setRequestHeader("X-Test", "test2"); - request.addEventListener("load", function(event) { - if (request.status >= 200 && request.status < 300) { - loadFromArrayBuffer(request.response); - } else { - console.warn(request.statusText, request.responseText); - } - }); - request.send(); - kthoom.initProgressMeter(); - document.body.className += /AppleWebKit/.test(navigator.userAgent) ? " webkit" : ""; - updateScale(true); - kthoom.loadSettings(); - $(document).keydown(keyHandler); - - $(window).resize(function() { - var f = (screen.width - innerWidth < 4 && screen.height - innerHeight < 4); - getElem("titlebar").className = f ? "main" : ""; - updateScale(); - }); - - $("#mainImage").click(function(evt) { - // Firefox does not support offsetX/Y so we have to manually calculate - // where the user clicked in the image. - var mainContentWidth = $("#mainContent").width(); - var mainContentHeight = $("#mainContent").height(); - var comicWidth = evt.target.clientWidth; - var comicHeight = evt.target.clientHeight; - var offsetX = (mainContentWidth - comicWidth) / 2; - var offsetY = (mainContentHeight - comicHeight) / 2; - var clickX = !!evt.offsetX ? evt.offsetX : (evt.clientX - offsetX); - var clickY = !!evt.offsetY ? evt.offsetY : (evt.clientY - offsetY); - - // Determine if the user clicked/tapped the left side or the - // right side of the page. - var clickedPrev = false; - switch (kthoom.rotateTimes) { - case 0: - clickedPrev = clickX < (comicWidth / 2); - break; - case 1: - clickedPrev = clickY < (comicHeight / 2); - break; - case 2: - clickedPrev = clickX > (comicWidth / 2); - break; - case 3: - clickedPrev = clickY > (comicHeight / 2); - break; - } - if (clickedPrev) { - showPrevPage(); - } else { - showNextPage(); - } - }); +function ImageLoadCallback(event) { + var jso=this.response; + if (jso.page !== jso.last) + { + // var secRequest = new XMLHttpRequest(); + this.open("GET", this.fileid + "/"+(jso.page+1)); + this.addEventListener("load",ImageLoadCallback); + this.send(); + } + else + { + var diff = ((new Date).getTime() - start)/1000; + console.log('Transfer done in ' + diff + 's'); } + loadFromArrayBuffer(jso); +} +function init(fileid) { + start = (new Date).getTime(); + var request = new XMLHttpRequest(); + request.open("GET", fileid); + request.responseType = "json"; + request.fileid=fileid.substring(0,fileid.length - 2); + request.addEventListener("load",ImageLoadCallback);/* function(event) { + var jso=request.response; + if (jso.page!=jso.length) + { + // var secRequest = new XMLHttpRequest(); + request.open("GET", fileid + "/../"+(jso.page+1)); + request.send(); + //secRequest.responseType = "json"; + //finished; + } + loadFromArrayBuffer(jso); + + // var byteArray = new Uint8Array(request.response); + // if you want to access the bytes: + });*/ + request.send(); + + kthoom.initProgressMeter(); + document.body.className += /AppleWebKit/.test(navigator.userAgent) ? " webkit" : ""; + updateScale(true); + kthoom.loadSettings(); + $(document).keydown(keyHandler); + + $(window).resize(function() { + var f = (screen.width - innerWidth < 4 && screen.height - innerHeight < 4); + getElem("titlebar").className = f ? "main" : ""; + updateScale(); + }); + + $("#mainImage").click(function(evt) { + // Firefox does not support offsetX/Y so we have to manually calculate + // where the user clicked in the image. + var mainContentWidth = $("#mainContent").width(); + var mainContentHeight = $("#mainContent").height(); + var comicWidth = evt.target.clientWidth; + var comicHeight = evt.target.clientHeight; + var offsetX = (mainContentWidth - comicWidth) / 2; + var offsetY = (mainContentHeight - comicHeight) / 2; + var clickX = !!evt.offsetX ? evt.offsetX : (evt.clientX - offsetX); + var clickY = !!evt.offsetY ? evt.offsetY : (evt.clientY - offsetY); + + // Determine if the user clicked/tapped the left side or the + // right side of the page. + var clickedPrev = false; + switch (kthoom.rotateTimes) { + case 0: + clickedPrev = clickX < (comicWidth / 2); + break; + case 1: + clickedPrev = clickY < (comicHeight / 2); + break; + case 2: + clickedPrev = clickX > (comicWidth / 2); + break; + case 3: + clickedPrev = clickY > (comicHeight / 2); + break; + } + if (clickedPrev) { + showPrevPage(); + } else { + showNextPage(); + } + }); } diff --git a/cps/static/js/unrar.js b/cps/static/js/unrar.js deleted file mode 100644 index f32fd6fa..00000000 --- a/cps/static/js/unrar.js +++ /dev/null @@ -1,891 +0,0 @@ -/** - * unrar.js - * - * Copyright(c) 2011 Google Inc. - * Copyright(c) 2011 antimatter15 - * - * Reference Documentation: - * - * http://kthoom.googlecode.com/hg/docs/unrar.html - */ -/* global bitjs, importScripts */ - -// This file expects to be invoked as a Worker (see onmessage below). -importScripts("io.js"); -importScripts("archive.js"); - -// Progress variables. -var currentFilename = ""; -var currentFileNumber = 0; -var currentBytesUnarchivedInFile = 0; -var currentBytesUnarchived = 0; -var totalUncompressedBytesInArchive = 0; -var totalFilesInArchive = 0; - -// Helper functions. -var info = function(str) { - postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); -}; -var err = function(str) { - postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); -}; -var postProgress = function() { - postMessage(new bitjs.archive.UnarchiveProgressEvent( - currentFilename, - currentFileNumber, - currentBytesUnarchivedInFile, - currentBytesUnarchived, - totalUncompressedBytesInArchive, - totalFilesInArchive)); -}; - -// shows a byte value as its hex representation -var nibble = "0123456789ABCDEF"; -var byteValueToHexString = function(num) { - return nibble[num>>4] + nibble[num & 0xF]; -}; -var twoByteValueToHexString = function(num) { - return nibble[(num>>12) & 0xF] + nibble[(num>>8) & 0xF] + nibble[(num>>4) & 0xF] + nibble[num & 0xF]; -}; - - -// Volume Types -// MARK_HEAD = 0x72; -var MAIN_HEAD = 0x73, - FILE_HEAD = 0x74, - // COMM_HEAD = 0x75, - // AV_HEAD = 0x76, - // SUB_HEAD = 0x77, - // PROTECT_HEAD = 0x78, - // SIGN_HEAD = 0x79, - // NEWSUB_HEAD = 0x7a, - ENDARC_HEAD = 0x7b; - -// bstream is a bit stream -var RarVolumeHeader = function(bstream) { - - var headPos = bstream.bytePtr; - // byte 1,2 - info("Rar Volume Header @" + bstream.bytePtr); - - this.crc = bstream.readBits(16); - info(" crc=" + this.crc); - - // byte 3 - this.headType = bstream.readBits(8); - info(" headType=" + this.headType); - - // Get flags - // bytes 4,5 - this.flags = {}; - this.flags.value = bstream.peekBits(16); - - info(" flags=" + twoByteValueToHexString(this.flags.value)); - switch (this.headType) { - case MAIN_HEAD: - this.flags.MHD_VOLUME = !!bstream.readBits(1); - this.flags.MHD_COMMENT = !!bstream.readBits(1); - this.flags.MHD_LOCK = !!bstream.readBits(1); - this.flags.MHD_SOLID = !!bstream.readBits(1); - this.flags.MHD_PACK_COMMENT = !!bstream.readBits(1); - this.flags.MHD_NEWNUMBERING = this.flags.MHD_PACK_COMMENT; - this.flags.MHD_AV = !!bstream.readBits(1); - this.flags.MHD_PROTECT = !!bstream.readBits(1); - this.flags.MHD_PASSWORD = !!bstream.readBits(1); - this.flags.MHD_FIRSTVOLUME = !!bstream.readBits(1); - this.flags.MHD_ENCRYPTVER = !!bstream.readBits(1); - bstream.readBits(6); // unused - break; - case FILE_HEAD: - this.flags.LHD_SPLIT_BEFORE = !!bstream.readBits(1); // 0x0001 - this.flags.LHD_SPLIT_AFTER = !!bstream.readBits(1); // 0x0002 - this.flags.LHD_PASSWORD = !!bstream.readBits(1); // 0x0004 - this.flags.LHD_COMMENT = !!bstream.readBits(1); // 0x0008 - this.flags.LHD_SOLID = !!bstream.readBits(1); // 0x0010 - bstream.readBits(3); // unused - this.flags.LHD_LARGE = !!bstream.readBits(1); // 0x0100 - this.flags.LHD_UNICODE = !!bstream.readBits(1); // 0x0200 - this.flags.LHD_SALT = !!bstream.readBits(1); // 0x0400 - this.flags.LHD_VERSION = !!bstream.readBits(1); // 0x0800 - this.flags.LHD_EXTTIME = !!bstream.readBits(1); // 0x1000 - this.flags.LHD_EXTFLAGS = !!bstream.readBits(1); // 0x2000 - bstream.readBits(2); // unused - info(" LHD_SPLIT_BEFORE = " + this.flags.LHD_SPLIT_BEFORE); - break; - default: - bstream.readBits(16); - } - - // byte 6,7 - this.headSize = bstream.readBits(16); - info(" headSize=" + this.headSize); - switch (this.headType) { - case MAIN_HEAD: - this.highPosAv = bstream.readBits(16); - this.posAv = bstream.readBits(32); - if (this.flags.MHD_ENCRYPTVER) { - this.encryptVer = bstream.readBits(8); - } - info("Found MAIN_HEAD with highPosAv=" + this.highPosAv + ", posAv=" + this.posAv); - break; - case FILE_HEAD: - this.packSize = bstream.readBits(32); - this.unpackedSize = bstream.readBits(32); - this.hostOS = bstream.readBits(8); - this.fileCRC = bstream.readBits(32); - this.fileTime = bstream.readBits(32); - this.unpVer = bstream.readBits(8); - this.method = bstream.readBits(8); - this.nameSize = bstream.readBits(16); - this.fileAttr = bstream.readBits(32); - - if (this.flags.LHD_LARGE) { - info("Warning: Reading in LHD_LARGE 64-bit size values"); - this.HighPackSize = bstream.readBits(32); - this.HighUnpSize = bstream.readBits(32); - } else { - this.HighPackSize = 0; - this.HighUnpSize = 0; - if (this.unpackedSize == 0xffffffff) { - this.HighUnpSize = 0x7fffffff; - this.unpackedSize = 0xffffffff; - } - } - this.fullPackSize = 0; - this.fullUnpackSize = 0; - this.fullPackSize |= this.HighPackSize; - this.fullPackSize <<= 32; - this.fullPackSize |= this.packSize; - - // read in filename - - this.filename = bstream.readBytes(this.nameSize); - for (var _i = 0, _s = ""; _i < this.filename.length ; _i++) { - _s += String.fromCharCode(this.filename[_i]); - } - - this.filename = _s; - - if (this.flags.LHD_SALT) { - info("Warning: Reading in 64-bit salt value"); - this.salt = bstream.readBits(64); // 8 bytes - } - - if (this.flags.LHD_EXTTIME) { - // 16-bit flags - var extTimeFlags = bstream.readBits(16); - - // this is adapted straight out of arcread.cpp, Archive::ReadHeader() - for (var I = 0; I < 4; ++I) { - var rmode = extTimeFlags >> ((3 - I) * 4); - if ((rmode & 8)==0) - continue; - if (I!=0) { - bstream.readBits(16); - } - var count = (rmode & 3); - for (var J = 0; J < count; ++J) { - bstream.readBits(8); - } - } - } - - if (this.flags.LHD_COMMENT) { - info("Found a LHD_COMMENT"); - } - - - 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); - - break; - default: - info("Found a header of type 0x" + byteValueToHexString(this.headType)); - // skip the rest of the header bytes (for now) - bstream.readBytes( this.headSize - 7 ); - break; - } -}; - -var BLOCK_LZ = 0; - // BLOCK_PPM = 1; - -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], - 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], - rSDBits = [2,2,3, 4, 5, 6, 6, 6]; - -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, - 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, - 131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824, - 655360, 720896, 786432, 851968, 917504, 983040]; - -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, - 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]; - -var rLOW_DIST_REP_COUNT = 16; - -var rNC = 299, - rDC = 60, - rLDC = 17, - rRC = 28, - rBC = 20, - rHUFF_TABLE_SIZE = (rNC + rDC + rRC + rLDC); - -var UnpBlockType = BLOCK_LZ; -var UnpOldTable = new Array(rHUFF_TABLE_SIZE); - -var BD = { //bitdecode - DecodeLen: new Array(16), - DecodePos: new Array(16), - DecodeNum: new Array(rBC) -}; -var LD = { //litdecode - DecodeLen: new Array(16), - DecodePos: new Array(16), - DecodeNum: new Array(rNC) -}; -var DD = { //distdecode - DecodeLen: new Array(16), - DecodePos: new Array(16), - DecodeNum: new Array(rDC) -}; -var LDD = { //low dist decode - DecodeLen: new Array(16), - DecodePos: new Array(16), - DecodeNum: new Array(rLDC) -}; -var RD = { //rep decode - DecodeLen: new Array(16), - DecodePos: new Array(16), - DecodeNum: new Array(rRC) -}; - -var rBuffer; - -// read in Huffman tables for RAR -function RarReadTables(bstream) { - var BitLength = new Array(rBC), - Table = new Array(rHUFF_TABLE_SIZE); - - // before we start anything we need to get byte-aligned - bstream.readBits( (8 - bstream.bitPtr) & 0x7 ); - - if (bstream.readBits(1)) { - info("Error! PPM not implemented yet"); - return; - } - - if (!bstream.readBits(1)) { //discard old table - for (var i = UnpOldTable.length; i--;) UnpOldTable[i] = 0; - } - - // read in bit lengths - for (var I = 0; I < rBC; ++I) { - - var Length = bstream.readBits(4); - if (Length == 15) { - var ZeroCount = bstream.readBits(4); - if (ZeroCount == 0) { - BitLength[I] = 15; - } - else { - ZeroCount += 2; - while (ZeroCount-- > 0 && I < rBC) - BitLength[I++] = 0; - --I; - } - } - else { - BitLength[I] = Length; - } - } - - // now all 20 bit lengths are obtained, we construct the Huffman Table: - - RarMakeDecodeTables(BitLength, 0, BD, rBC); - - var TableSize = rHUFF_TABLE_SIZE; - //console.log(DecodeLen, DecodePos, DecodeNum); - for (var i = 0; i < TableSize;) { - var num = RarDecodeNumber(bstream, BD); - if (num < 16) { - Table[i] = (num + UnpOldTable[i]) & 0xf; - i++; - } else if(num < 18) { - var N = (num == 16) ? (bstream.readBits(3) + 3) : (bstream.readBits(7) + 11); - - while (N-- > 0 && i < TableSize) { - Table[i] = Table[i - 1]; - i++; - } - } else { - var N = (num == 18) ? (bstream.readBits(3) + 3) : (bstream.readBits(7) + 11); - - while (N-- > 0 && i < TableSize) { - Table[i++] = 0; - } - } - } - - RarMakeDecodeTables(Table, 0, LD, rNC); - RarMakeDecodeTables(Table, rNC, DD, rDC); - RarMakeDecodeTables(Table, rNC + rDC, LDD, rLDC); - RarMakeDecodeTables(Table, rNC + rDC + rLDC, RD, rRC); - - for (var i = UnpOldTable.length; i--;) { - UnpOldTable[i] = Table[i]; - } - return true; -} - - -function RarDecodeNumber(bstream, dec) { - var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum; - var bitField = bstream.getBits() & 0xfffe; - //some sort of rolled out binary search - var bits = ((bitField < DecodeLen[8])? - ((bitField < DecodeLen[4])? - ((bitField < DecodeLen[2])? - ((bitField < DecodeLen[1])?1:2) - :((bitField < DecodeLen[3])?3:4)) - :(bitField < DecodeLen[6])? - ((bitField < DecodeLen[5])?5:6) - :((bitField < DecodeLen[7])?7:8)) - :((bitField < DecodeLen[12])? - ((bitField < DecodeLen[10])? - ((bitField < DecodeLen[9])?9:10) - :((bitField < DecodeLen[11])?11:12)) - :(bitField < DecodeLen[14])? - ((bitField < DecodeLen[13])?13:14) - :15)); - bstream.readBits(bits); - var N = DecodePos[bits] + ((bitField - DecodeLen[bits -1]) >>> (16 - bits)); - - return DecodeNum[N]; -} - - -function RarMakeDecodeTables(BitLength, offset, dec, size) { - var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum; - var LenCount = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - TmpPos = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - N = 0, M = 0; - for (var i = DecodeNum.length; i--;) DecodeNum[i] = 0; - for (var i = 0; i < size; i++) { - LenCount[BitLength[i + offset] & 0xF]++; - } - LenCount[0] = 0; - TmpPos[0] = 0; - DecodePos[0] = 0; - DecodeLen[0] = 0; - - for (var I = 1; I < 16; ++I) { - N = 2 * (N+LenCount[I]); - M = (N << (15-I)); - if (M > 0xFFFF) - M = 0xFFFF; - DecodeLen[I] = M; - DecodePos[I] = DecodePos[I-1] + LenCount[I-1]; - TmpPos[I] = DecodePos[I]; - } - for (I = 0; I < size; ++I) - if (BitLength[I + offset] != 0) - DecodeNum[ TmpPos[ BitLength[offset + I] & 0xF ]++] = I; -} - -// TODO: implement -function Unpack15(bstream, Solid) { - info("ERROR! RAR 1.5 compression not supported"); -} - -function Unpack20(bstream, Solid) { - var destUnpSize = rBuffer.data.length; - var oldDistPtr = 0; - - RarReadTables20(bstream); - while (destUnpSize > rBuffer.ptr) { - var num = RarDecodeNumber(bstream, LD); - if (num < 256) { - rBuffer.insertByte(num); - continue; - } - if (num > 269) { - var Length = rLDecode[num -= 270] + 3; - if ((Bits = rLBits[num]) > 0) { - Length += bstream.readBits(Bits); - } - var DistNumber = RarDecodeNumber(bstream, DD); - var Distance = rDDecode[DistNumber] + 1; - if ((Bits = rDBits[DistNumber]) > 0) { - Distance += bstream.readBits(Bits); - } - if (Distance >= 0x2000) { - Length++; - if(Distance >= 0x40000) Length++; - } - lastLength = Length; - lastDist = rOldDist[oldDistPtr++ & 3] = Distance; - RarCopyString(Length, Distance); - continue; - } - if (num == 269) { - RarReadTables20(bstream); - - RarUpdateProgress() - - continue; - } - if (num == 256) { - lastDist = rOldDist[oldDistPtr++ & 3] = lastDist; - RarCopyString(lastLength, lastDist); - continue; - } - if (num < 261) { - var Distance = rOldDist[(oldDistPtr - (num - 256)) & 3]; - var LengthNumber = RarDecodeNumber(bstream, RD); - var Length = rLDecode[LengthNumber] +2; - if ((Bits = rLBits[LengthNumber]) > 0) { - Length += bstream.readBits(Bits); - } - if (Distance >= 0x101) { - Length++; - if (Distance >= 0x2000) { - Length++ - if (Distance >= 0x40000) Length++; - } - } - lastLength = Length; - lastDist = rOldDist[oldDistPtr++ & 3] = Distance; - RarCopyString(Length, Distance); - continue; - } - if (num < 270) { - var Distance = rSDDecode[num -= 261] + 1; - if ((Bits = rSDBits[num]) > 0) { - Distance += bstream.readBits(Bits); - } - lastLength = 2; - lastDist = rOldDist[oldDistPtr++ & 3] = Distance; - RarCopyString(2, Distance); - continue; - } - } - RarUpdateProgress() -} - -function RarUpdateProgress() { - var change = rBuffer.ptr - currentBytesUnarchivedInFile; - currentBytesUnarchivedInFile = rBuffer.ptr; - currentBytesUnarchived += change; - postProgress(); -} - - -var rNC20 = 298, - rDC20 = 48, - rRC20 = 28, - rBC20 = 19, - rMC20 = 257; - -var UnpOldTable20 = new Array(rMC20 * 4); - -function RarReadTables20(bstream) { - var BitLength = new Array(rBC20); - var Table = new Array(rMC20 * 4); - var TableSize, N, I; - var AudioBlock = bstream.readBits(1); - if (!bstream.readBits(1)) - for (var i = UnpOldTable20.length; i--;) UnpOldTable20[i] = 0; - TableSize = rNC20 + rDC20 + rRC20; - for (var I = 0; I < rBC20; I++) - BitLength[I] = bstream.readBits(4); - RarMakeDecodeTables(BitLength, 0, BD, rBC20); - I = 0; - while (I < TableSize) { - var num = RarDecodeNumber(bstream, BD); - if (num < 16) { - Table[I] = num + UnpOldTable20[I] & 0xf; - I++; - } else if(num == 16) { - N = bstream.readBits(2) + 3; - while (N-- > 0 && I < TableSize) { - Table[I] = Table[I - 1]; - I++; - } - } else { - if (num == 17) { - N = bstream.readBits(3) + 3; - } else { - N = bstream.readBits(7) + 11; - } - while (N-- > 0 && I < TableSize) { - Table[I++] = 0; - } - } - } - RarMakeDecodeTables(Table, 0, LD, rNC20); - RarMakeDecodeTables(Table, rNC20, DD, rDC20); - RarMakeDecodeTables(Table, rNC20 + rDC20, RD, rRC20); - for (var i = UnpOldTable20.length; i--;) UnpOldTable20[i] = Table[i]; -} - -var lowDistRepCount = 0, prevLowDist = 0; - -var rOldDist = [0,0,0,0]; -var lastDist; -var lastLength; - - -function Unpack29(bstream, Solid) { - // lazy initialize rDDecode and rDBits - - var DDecode = new Array(rDC); - var DBits = new Array(rDC); - - var Dist=0,BitLength=0,Slot=0; - - for (var I = 0; I < rDBitLengthCounts.length; I++,BitLength++) { - for (var J = 0; J < rDBitLengthCounts[I]; J++,Slot++,Dist+=(1<= 271) { - var Length = rLDecode[num -= 271] + 3; - if ((Bits = rLBits[num]) > 0) { - Length += bstream.readBits(Bits); - } - var DistNumber = RarDecodeNumber(bstream, DD); - var Distance = DDecode[DistNumber]+1; - if ((Bits = DBits[DistNumber]) > 0) { - if (DistNumber > 9) { - if (Bits > 4) { - Distance += ((bstream.getBits() >>> (20 - Bits)) << 4); - bstream.readBits(Bits - 4); - //todo: check this - } - if (lowDistRepCount > 0) { - lowDistRepCount--; - Distance += prevLowDist; - } else { - var LowDist = RarDecodeNumber(bstream, LDD); - if (LowDist == 16) { - lowDistRepCount = rLOW_DIST_REP_COUNT - 1; - Distance += prevLowDist; - } else { - Distance += LowDist; - prevLowDist = LowDist; - } - } - } else { - Distance += bstream.readBits(Bits); - } - } - if (Distance >= 0x2000) { - Length++; - if (Distance >= 0x40000) { - Length++; - } - } - RarInsertOldDist(Distance); - RarInsertLastMatch(Length, Distance); - RarCopyString(Length, Distance); - continue; - } - if (num == 256) { - if (!RarReadEndOfBlock(bstream)) break; - continue; - } - if (num == 257) { - //console.log("READVMCODE"); - if (!RarReadVMCode(bstream)) break; - continue; - } - if (num == 258) { - if (lastLength != 0) { - RarCopyString(lastLength, lastDist); - } - continue; - } - if (num < 263) { - var DistNum = num - 259; - var Distance = rOldDist[DistNum]; - - for (var I = DistNum; I > 0; I--) { - rOldDist[I] = rOldDist[I-1]; - } - rOldDist[0] = Distance; - - var LengthNumber = RarDecodeNumber(bstream, RD); - var Length = rLDecode[LengthNumber] + 2; - if ((Bits = rLBits[LengthNumber]) > 0) { - Length += bstream.readBits(Bits); - } - RarInsertLastMatch(Length, Distance); - RarCopyString(Length, Distance); - continue; - } - if (num < 272) { - var Distance = rSDDecode[num -= 263] + 1; - if ((Bits = rSDBits[num]) > 0) { - Distance += bstream.readBits(Bits); - } - RarInsertOldDist(Distance); - RarInsertLastMatch(2, Distance); - RarCopyString(2, Distance); - continue; - } - } - RarUpdateProgress() -} - -function RarReadEndOfBlock(bstream) { - - RarUpdateProgress() - - var NewTable = false, NewFile = false; - if (bstream.readBits(1)) { - NewTable = true; - } else { - NewFile = true; - NewTable = !!bstream.readBits(1); - } - //tablesRead = !NewTable; - 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) { - lastDist = distance; - lastLength = length; -} - -function RarInsertOldDist(distance) { - rOldDist.splice(3,1); - rOldDist.splice(0,0,distance); -} - -//this is the real function, the other one is for debugging -function RarCopyString(length, distance) { - var destPtr = rBuffer.ptr - distance; - if(destPtr < 0){ - var l = rOldBuffers.length; - while(destPtr < 0){ - destPtr = rOldBuffers[--l].data.length + destPtr; - } - //TODO: lets hope that it never needs to read beyond file boundaries - while(length--) rBuffer.insertByte(rOldBuffers[l].data[destPtr++]); - } - if (length > distance) { - while(length--) rBuffer.insertByte(rBuffer.data[destPtr++]); - } else { - rBuffer.insertBytes(rBuffer.data.subarray(destPtr, destPtr + length)); - } -} - -var rOldBuffers = [] -// v must be a valid RarVolume -function unpack(v) { - - // TODO: implement what happens when unpVer is < 15 - var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer, - Solid = v.header.LHD_SOLID, - bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength ); - - rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize); - - info("Unpacking " + v.filename+" RAR v" + Ver); - - switch(Ver) { - case 15: // rar 1.5 compression - Unpack15(bstream, Solid); - break; - case 20: // rar 2.x compression - case 26: // files larger than 2GB - Unpack20(bstream, Solid); - break; - case 29: // rar 3.x compression - case 36: // alternative hash - Unpack29(bstream, Solid); - break; - } // switch(method) - - rOldBuffers.push(rBuffer); - //TODO: clear these old buffers when there's over 4MB of history - return rBuffer.data; -} - -// bstream is a bit stream -var RarLocalFile = function(bstream) { - - this.header = new RarVolumeHeader(bstream); - this.filename = this.header.filename; - - if (this.header.headType != FILE_HEAD && this.header.headType != ENDARC_HEAD) { - this.isValid = false; - info("Error! RAR Volume did not include a FILE_HEAD header "); - } - else { - // read in the compressed data - this.fileData = null; - if (this.header.packSize > 0) { - this.fileData = bstream.readBytes(this.header.packSize); - this.isValid = true; - } - } -}; - -RarLocalFile.prototype.unrar = function() { - - if (!this.header.flags.LHD_SPLIT_BEFORE) { - // unstore file - if (this.header.method == 0x30) { - info("Unstore "+this.filename); - this.isValid = true; - - currentBytesUnarchivedInFile += this.fileData.length; - currentBytesUnarchived += this.fileData.length; - - // Create a new buffer and copy it over. - var len = this.header.packSize; - var newBuffer = new bitjs.io.ByteBuffer(len); - newBuffer.insertBytes(this.fileData); - this.fileData = newBuffer.data; - } else { - this.isValid = true; - this.fileData = unpack(this); - } - } -} - -var unrar = function(arrayBuffer) { - currentFilename = ""; - currentFileNumber = 0; - currentBytesUnarchivedInFile = 0; - currentBytesUnarchived = 0; - totalUncompressedBytesInArchive = 0; - totalFilesInArchive = 0; - - postMessage(new bitjs.archive.UnarchiveStartEvent()); - var bstream = new bitjs.io.BitStream(arrayBuffer, false /* rtl */); - - var header = new RarVolumeHeader(bstream); - if (header.crc == 0x6152 && - header.headType == 0x72 && - header.flags.value == 0x1A21 && - header.headSize == 7) - { - info("Found RAR signature"); - - var mhead = new RarVolumeHeader(bstream); - if (mhead.headType != MAIN_HEAD) { - info("Error! RAR did not include a MAIN_HEAD header"); - } else { - var localFiles = [], - localFile = null; - do { - try { - localFile = new RarLocalFile(bstream); - info("RAR localFile isValid=" + localFile.isValid + ", volume packSize=" + localFile.header.packSize); - if (localFile && localFile.isValid && localFile.header.packSize > 0) { - totalUncompressedBytesInArchive += localFile.header.unpackedSize; - localFiles.push(localFile); - } else if (localFile.header.packSize == 0 && localFile.header.unpackedSize == 0) { - localFile.isValid = true; - } - } catch(err) { - break; - } - //info("bstream" + bstream.bytePtr+"/"+bstream.bytes.length); - } while( localFile.isValid ); - totalFilesInArchive = localFiles.length; - - // now we have all information but things are unpacked - // TODO: unpack - 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(', ')); - for (var i = 0; i < localFiles.length; ++i) { - var localfile = localFiles[i]; - - // update progress - currentFilename = localfile.header.filename; - currentBytesUnarchivedInFile = 0; - - // actually do the unzipping - localfile.unrar(); - - if (localfile.isValid) { - postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile)); - postProgress(); - } - } - - postProgress(); - } - } - else { - err("Invalid RAR file"); - } - postMessage(new bitjs.archive.UnarchiveFinishEvent()); -}; - -// event.data.file has the ArrayBuffer. -onmessage = function(event) { - var ab = event.data.file; - unrar(ab, true); -}; diff --git a/cps/static/js/untar.js b/cps/static/js/untar.js deleted file mode 100644 index defed7d7..00000000 --- a/cps/static/js/untar.js +++ /dev/null @@ -1,168 +0,0 @@ -/** - * untar.js - * - * 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('io.js'); -importScripts('archive.js'); - -// Progress variables. -var currentFilename = ""; -var currentFileNumber = 0; -var currentBytesUnarchivedInFile = 0; -var currentBytesUnarchived = 0; -var totalUncompressedBytesInArchive = 0; -var totalFilesInArchive = 0; - -// Helper functions. -var info = function(str) { - postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); -}; -var err = function(str) { - postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); -}; -var postProgress = function() { - postMessage(new bitjs.archive.UnarchiveProgressEvent( - currentFilename, - currentFileNumber, - currentBytesUnarchivedInFile, - currentBytesUnarchived, - totalUncompressedBytesInArchive, - totalFilesInArchive)); -}; - -// 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; -}; - -// takes a ByteStream and parses out the local file information -var TarLocalFile = function(bstream) { - this.isValid = false; - - // 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 - } - - // 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.bytes.buffer, bstream.ptr, this.size); - if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) { - this.isValid = true; - } - - bstream.readBytes(this.size); - - // Round up to 512-byte blocks. - var remaining = 512 - this.size % 512; - if (remaining > 0 && remaining < 512) { - bstream.readBytes(remaining); - } - } else if (this.typeflag == 5) { - info(" This is a directory.") - } -}; - -// Takes an ArrayBuffer of a tar file in -// returns null on error -// returns an array of DecompressedFile objects on success -var untar = function(arrayBuffer) { - currentFilename = ""; - currentFileNumber = 0; - currentBytesUnarchivedInFile = 0; - currentBytesUnarchived = 0; - totalUncompressedBytesInArchive = 0; - totalFilesInArchive = 0; - - postMessage(new bitjs.archive.UnarchiveStartEvent()); - var bstream = new bitjs.io.ByteStream(arrayBuffer); - var localFiles = []; - - // While we don't encounter an empty block, keep making TarLocalFiles. - while (bstream.peekNumber(4) != 0) { - var oneLocalFile = new TarLocalFile(bstream); - if (oneLocalFile && oneLocalFile.isValid) { - localFiles.push(oneLocalFile); - totalUncompressedBytesInArchive += oneLocalFile.size; - } - } - totalFilesInArchive = localFiles.length; - - // got all local files, now sort them - localFiles.sort(function(a,b) { - var aname = a.filename.toLowerCase(); - var bname = b.filename.toLowerCase(); - return aname > bname ? 1 : -1; - }); - - // report # files and total length - if (localFiles.length > 0) { - postProgress(); - } - - // now do the shipping of each file - for (var i = 0; i < localFiles.length; ++i) { - var localfile = localFiles[i]; - info("Sending file '" + localfile.filename + "' up"); - - // update progress - currentFilename = localfile.filename; - currentFileNumber = i; - currentBytesUnarchivedInFile = localfile.size; - currentBytesUnarchived += localfile.size; - postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile)); - postProgress(); - } - - postProgress(); - - postMessage(new bitjs.archive.UnarchiveFinishEvent()); -}; - -// event.data.file has the ArrayBuffer. -onmessage = function(event) { - var ab = event.data.file; - untar(ab); -}; diff --git a/cps/static/js/unzip.js b/cps/static/js/unzip.js deleted file mode 100644 index 18b76443..00000000 --- a/cps/static/js/unzip.js +++ /dev/null @@ -1,621 +0,0 @@ -/** - * unzip.js - * - * Copyright(c) 2011 Google Inc. - * Copyright(c) 2011 antimatter15 - * - * Reference Documentation: - * - * ZIP format: http://www.pkware.com/documents/casestudies/APPNOTE.TXT - * DEFLATE format: http://tools.ietf.org/html/rfc1951 - */ -/* global bitjs, importScripts, Uint8Array */ - -// This file expects to be invoked as a Worker (see onmessage below). -importScripts("io.js"); -importScripts("archive.js"); - -// Progress variables. -var currentFilename = ""; -var currentFileNumber = 0; -var currentBytesUnarchivedInFile = 0; -var currentBytesUnarchived = 0; -var totalUncompressedBytesInArchive = 0; -var totalFilesInArchive = 0; - -// Helper functions. -var info = function(str) { - postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); -}; -var err = function(str) { - postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); -}; -var postProgress = function() { - postMessage(new bitjs.archive.UnarchiveProgressEvent( - currentFilename, - currentFileNumber, - currentBytesUnarchivedInFile, - currentBytesUnarchived, - totalUncompressedBytesInArchive, - totalFilesInArchive)); -}; - -var zLocalFileHeaderSignature = 0x04034b50; -var zArchiveExtraDataSignature = 0x08064b50; -var zCentralFileHeaderSignature = 0x02014b50; -var zDigitalSignatureSignature = 0x05054b50; -//var zEndOfCentralDirSignature = 0x06064b50; -//var zEndOfCentralDirLocatorSignature = 0x07064b50; - -// takes a ByteStream and parses out the local file information -var ZipLocalFile = function(bstream) { - if (typeof bstream !== typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function() {} ) { - return null; - } - - bstream.readNumber(4); // swallow signature - this.version = bstream.readNumber(2); - this.generalPurpose = bstream.readNumber(2); - this.compressionMethod = bstream.readNumber(2); - this.lastModFileTime = bstream.readNumber(2); - this.lastModFileDate = bstream.readNumber(2); - this.crc32 = bstream.readNumber(4); - this.compressedSize = bstream.readNumber(4); - this.uncompressedSize = bstream.readNumber(4); - this.fileNameLength = bstream.readNumber(2); - this.extraFieldLength = bstream.readNumber(2); - - this.filename = null; - if (this.fileNameLength > 0) { - 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; - if (this.extraFieldLength > 0) { - this.extraField = bstream.readString(this.extraFieldLength); - info(" extra field=" + this.extraField); - } - - // read in the compressed data - this.fileData = null; - if (this.compressedSize > 0) { - this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.compressedSize); - bstream.ptr += this.compressedSize; - } - - // TODO: deal with data descriptor if present (we currently assume no data descriptor!) - // "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 - // from the header?!? - if ((this.generalPurpose & bitjs.BIT[3]) !== 0) { - this.crc32 = bstream.readNumber(4); - this.compressedSize = bstream.readNumber(4); - this.uncompressedSize = bstream.readNumber(4); - } -}; - -// determine what kind of compressed data we have and decompress -ZipLocalFile.prototype.unzip = function() { - - // Zip Version 1.0, no compression (store only) - if (this.compressionMethod === 0 ) { - info("ZIP v" + this.version + ", store only: " + this.filename + " (" + this.compressedSize + " bytes)"); - currentBytesUnarchivedInFile = this.compressedSize; - currentBytesUnarchived += this.compressedSize; - } - // version == 20, compression method == 8 (DEFLATE) - else if (this.compressionMethod === 8) { - info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)"); - this.fileData = inflate(this.fileData, this.uncompressedSize); - } - else { - err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)"); - this.fileData = null; - } -}; - - -// Takes an ArrayBuffer of a zip file in -// returns null on error -// returns an array of DecompressedFile objects on success -var unzip = function(arrayBuffer) { - postMessage(new bitjs.archive.UnarchiveStartEvent()); - - currentFilename = ""; - currentFileNumber = 0; - currentBytesUnarchivedInFile = 0; - currentBytesUnarchived = 0; - totalUncompressedBytesInArchive = 0; - totalFilesInArchive = 0; - currentBytesUnarchived = 0; - - var bstream = new bitjs.io.ByteStream(arrayBuffer); - // detect local file header signature or return null - if (bstream.peekNumber(4) === zLocalFileHeaderSignature) { - var localFiles = []; - // loop until we don't see any more local files - while (bstream.peekNumber(4) === zLocalFileHeaderSignature) { - var oneLocalFile = new ZipLocalFile(bstream); - // this should strip out directories/folders - if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) { - localFiles.push(oneLocalFile); - totalUncompressedBytesInArchive += oneLocalFile.uncompressedSize; - } - } - totalFilesInArchive = localFiles.length; - - // got all local files, now sort them - localFiles.sort(function(a, b) { - var aname = a.filename.toLowerCase(); - var bname = b.filename.toLowerCase(); - return aname > bname ? 1 : -1; - }); - - // archive extra data record - if (bstream.peekNumber(4) === zArchiveExtraDataSignature) { - info(" Found an Archive Extra Data Signature"); - - // skipping this record for now - bstream.readNumber(4); - var archiveExtraFieldLength = bstream.readNumber(4); - bstream.readString(archiveExtraFieldLength); - } - - // central directory structure - // TODO: handle the rest of the structures (Zip64 stuff) - if (bstream.peekNumber(4) === zCentralFileHeaderSignature) { - info(" Found a Central File Header"); - - // read all file headers - while (bstream.peekNumber(4) === zCentralFileHeaderSignature) { - bstream.readNumber(4); // signature - bstream.readNumber(2); // version made by - bstream.readNumber(2); // version needed to extract - bstream.readNumber(2); // general purpose bit flag - bstream.readNumber(2); // compression method - bstream.readNumber(2); // last mod file time - bstream.readNumber(2); // last mod file date - bstream.readNumber(4); // crc32 - bstream.readNumber(4); // compressed size - bstream.readNumber(4); // uncompressed size - var fileNameLength = bstream.readNumber(2); // file name length - var extraFieldLength = bstream.readNumber(2); // extra field length - var fileCommentLength = bstream.readNumber(2); // file comment length - bstream.readNumber(2); // disk number start - bstream.readNumber(2); // internal file attributes - bstream.readNumber(4); // external file attributes - bstream.readNumber(4); // relative offset of local header - - bstream.readString(fileNameLength); // file name - bstream.readString(extraFieldLength); // extra field - bstream.readString(fileCommentLength); // file comment - } - } - - // digital signature - if (bstream.peekNumber(4) === zDigitalSignatureSignature) { - info(" Found a Digital Signature"); - - bstream.readNumber(4); - var sizeOfSignature = bstream.readNumber(2); - bstream.readString(sizeOfSignature); // digital signature data - } - - // report # files and total length - if (localFiles.length > 0) { - postProgress(); - } - - // now do the unzipping of each file - for (var i = 0; i < localFiles.length; ++i) { - var localfile = localFiles[i]; - - // update progress - currentFilename = localfile.filename; - currentFileNumber = i; - currentBytesUnarchivedInFile = 0; - - // actually do the unzipping - localfile.unzip(); - - if (localfile.fileData !== null) { - postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile)); - postProgress(); - } - } - postProgress(); - postMessage(new bitjs.archive.UnarchiveFinishEvent()); - } -}; - -// returns a table of Huffman codes -// each entry's index is its code and its value is a JavaScript object -// containing {length: 6, symbol: X} -function getHuffmanCodes(bitLengths) { - // ensure bitLengths is an array containing at least one element - if (typeof bitLengths !== typeof [] || bitLengths.length < 1) { - err("Error! getHuffmanCodes() called with an invalid array"); - return null; - } - - // Reference: http://tools.ietf.org/html/rfc1951#page-8 - var numLengths = bitLengths.length, - blCount = [], - MAX_BITS = 1; - - // Step 1: count up how many codes of each length we have - for (var i = 0; i < numLengths; ++i) { - var len = bitLengths[i]; - // test to ensure each bit length is a positive, non-zero number - if (typeof len !== typeof 1 || len < 0) { - err("bitLengths contained an invalid number in getHuffmanCodes(): " + len + " of type " + (typeof len)); - return null; - } - // increment the appropriate bitlength count - if (blCount[len] === undefined) blCount[len] = 0; - // a length of zero means this symbol is not participating in the huffman coding - if (len > 0) blCount[len]++; - - if (len > MAX_BITS) MAX_BITS = len; - } - - // Step 2: Find the numerical value of the smallest code for each code length - var nextCode = [], - code = 0; - for (var bits = 1; bits <= MAX_BITS; ++bits) { - var len = bits-1; - // ensure undefined lengths are zero - if (blCount[len] == undefined) blCount[len] = 0; - code = (code + blCount[bits-1]) << 1; - nextCode[bits] = code; - } - - // Step 3: Assign numerical values to all codes - var table = {}, tableLength = 0; - for (var n = 0; n < numLengths; ++n) { - var len = bitLengths[n]; - if (len !== 0) { - table[nextCode[len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(nextCode[len],len) }; - tableLength++; - nextCode[len]++; - } - } - table.maxLength = tableLength; - - return table; -} - -/* - The Huffman codes for the two alphabets are fixed, and are not - represented explicitly in the data. The Huffman code lengths - for the literal/length alphabet are: - - Lit Value Bits Codes - --------- ---- ----- - 0 - 143 8 00110000 through - 10111111 - 144 - 255 9 110010000 through - 111111111 - 256 - 279 7 0000000 through - 0010111 - 280 - 287 8 11000000 through - 11000111 -*/ -// 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 fixedHCtoDistance = null; -function getFixedLiteralTable() { - // create once - if (!fixedHCtoLiteral) { - var bitlengths = new Array(288); - for (var i = 0; i <= 143; ++i) bitlengths[i] = 8; - for (var i = 144; i <= 255; ++i) bitlengths[i] = 9; - for (var i = 256; i <= 279; ++i) bitlengths[i] = 7; - for (var i = 280; i <= 287; ++i) bitlengths[i] = 8; - - // get huffman code table - fixedHCtoLiteral = getHuffmanCodes(bitlengths); - } - return fixedHCtoLiteral; -} -function getFixedDistanceTable() { - // create once - if (!fixedHCtoDistance) { - var bitlengths = new Array(32); - for (var i = 0; i < 32; ++i) { - bitlengths[i] = 5; - } - - // get huffman code table - fixedHCtoDistance = getHuffmanCodes(bitlengths); - } - return fixedHCtoDistance; -} - -// extract one bit at a time until we find a matching Huffman Code -// then return that symbol -function decodeSymbol(bstream, hcTable) { - var code = 0, len = 0; - // var match = false; - - // loop until we match - for (;;) { - // read in next bit - var bit = bstream.readBits(1); - code = (code<<1) | bit; - ++len; - - // check against Huffman Code table and break if found - if (hcTable.hasOwnProperty(code) && hcTable[code].length == len) { - - break; - } - if (len > hcTable.maxLength) { - err("Bit stream out of sync, didn't find a Huffman Code, length was " + len + - " and table only max code length of " + hcTable.maxLength); - break; - } - } - return hcTable[code].symbol; -} - - -var CodeLengthCodeOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; - /* - Extra Extra Extra - Code Bits Length(s) Code Bits Lengths Code Bits Length(s) - ---- ---- ------ ---- ---- ------- ---- ---- ------- - 257 0 3 267 1 15,16 277 4 67-82 - 258 0 4 268 1 17,18 278 4 83-98 - 259 0 5 269 2 19-22 279 4 99-114 - 260 0 6 270 2 23-26 280 4 115-130 - 261 0 7 271 2 27-30 281 5 131-162 - 262 0 8 272 2 31-34 282 5 163-194 - 263 0 9 273 3 35-42 283 5 195-226 - 264 0 10 274 3 43-50 284 5 227-257 - 265 1 11,12 275 3 51-58 285 0 258 - 266 1 13,14 276 3 59-66 - - */ -var LengthLookupTable = [ - [0, 3], [0, 4], [0, 5], [0, 6], - [0, 7], [0, 8], [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] -]; - /* - Extra Extra Extra - Code Bits Dist Code Bits Dist Code Bits Distance - ---- ---- ---- ---- ---- ------ ---- ---- -------- - 0 0 1 10 4 33-48 20 9 1025-1536 - 1 0 2 11 4 49-64 21 9 1537-2048 - 2 0 3 12 5 65-96 22 10 2049-3072 - 3 0 4 13 5 97-128 23 10 3073-4096 - 4 1 5,6 14 6 129-192 24 11 4097-6144 - 5 1 7,8 15 6 193-256 25 11 6145-8192 - 6 2 9-12 16 7 257-384 26 12 8193-12288 - 7 2 13-16 17 7 385-512 27 12 12289-16384 - 8 3 17-24 18 8 513-768 28 13 16385-24576 - 9 3 25-32 19 8 769-1024 29 13 24577-32768 - */ -var DistLookupTable = [ - [0, 1], [0, 2], [0, 3], [0, 4], - [1, 5], [1, 7], - [2, 9], [2, 13], - [3, 17], [3, 25], - [4, 33], [4, 49], - [5, 65], [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) { - /* - loop (until end of block code recognized) - decode literal/length value from input stream - if value < 256 - copy value (literal byte) to output stream - otherwise - if value = end of block (256) - break from loop - otherwise (value = 257..285) - decode distance from input stream - - move backwards distance bytes in the output - stream, and copy length bytes from this - position to the output stream. - */ - var numSymbols = 0, blockSize = 0; - for (;;) { - var symbol = decodeSymbol(bstream, hcLiteralTable); - ++numSymbols; - if (symbol < 256) { - // copy literal byte to output - buffer.insertByte(symbol); - blockSize++; - } - else { - // end of block reached - if (symbol === 256) { - break; - } - else { - var lengthLookup = LengthLookupTable[symbol-257], - length = lengthLookup[1] + bstream.readBits(lengthLookup[0]), - distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)], - distance = distLookup[1] + bstream.readBits(distLookup[0]); - - // now apply length and distance appropriately and copy to output - - // TODO: check that backward distance < data.length? - - // http://tools.ietf.org/html/rfc1951#page-11 - // "Note also that the referenced string may overlap the current - // position; for example, if the last 2 bytes decoded have values - // X and Y, a string reference with - // adds X,Y,X,Y,X to the output stream." - // - // loop for each character - var ch = buffer.ptr - distance; - blockSize += length; - if(length > distance) { - var data = buffer.data; - while (length--) { - buffer.insertByte(data[ch++]); - } - } else { - buffer.insertBytes(buffer.data.subarray(ch, ch + length)) - } - - } // length-distance pair - } // length-distance pair or end-of-block - } // loop until we reach end of block - return blockSize; -} - -// {Uint8Array} compressedData A Uint8Array of the compressed file data. -// compression method 8 -// deflate: http://tools.ietf.org/html/rfc1951 -function inflate(compressedData, numDecompressedBytes) { - // Bit stream representing the compressed data. - var bstream = new bitjs.io.BitStream(compressedData.buffer, - false /* rtl */, - compressedData.byteOffset, - compressedData.byteLength); - var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes); - var numBlocks = 0; - var blockSize = 0; - - // block format: http://tools.ietf.org/html/rfc1951#page-9 - do { - var bFinal = bstream.readBits(1); - var bType = bstream.readBits(2); - blockSize = 0; - ++numBlocks; - // no compression - if (bType == 0) { - // skip remaining bits in this byte - while (bstream.bitPtr != 0) bstream.readBits(1); - var len = bstream.readBits(16), - nlen = bstream.readBits(16); - // TODO: check if nlen is the ones-complement of len? - - if(len > 0) buffer.insertBytes(bstream.readBytes(len)); - blockSize = len; - } - // fixed Huffman codes - else if(bType === 1) { - blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer); - } - // dynamic Huffman codes - else if(bType === 2) { - var numLiteralLengthCodes = bstream.readBits(5) + 257; - var numDistanceCodes = bstream.readBits(5) + 1, - numCodeLengthCodes = bstream.readBits(4) + 4; - - // 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]; - for (var i = 0; i < numCodeLengthCodes; ++i) { - codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3); - } - - // get the Huffman Codes for the code lengths - var codeLengthsCodes = getHuffmanCodes(codeLengthsCodeLengths); - - // now follow this mapping - /* - 0 - 15: Represent code lengths of 0 - 15 - 16: Copy the previous code length 3 - 6 times. - The next 2 bits indicate repeat length - (0 = 3, ... , 3 = 6) - Example: Codes 8, 16 (+2 bits 11), - 16 (+2 bits 10) will expand to - 12 code lengths of 8 (1 + 6 + 5) - 17: Repeat a code length of 0 for 3 - 10 times. - (3 bits of length) - 18: Repeat a code length of 0 for 11 - 138 times - (7 bits of length) - */ - // to generate the true code lengths of the Huffman Codes for the literal - // and distance tables together - var literalCodeLengths = []; - var prevCodeLength = 0; - while (literalCodeLengths.length < numLiteralLengthCodes + numDistanceCodes) { - var symbol = decodeSymbol(bstream, codeLengthsCodes); - if (symbol <= 15) { - literalCodeLengths.push(symbol); - prevCodeLength = symbol; - } - else if (symbol === 16) { - var repeat = bstream.readBits(2) + 3; - while (repeat--) { - literalCodeLengths.push(prevCodeLength); - } - } - else if (symbol === 17) { - var repeat = bstream.readBits(3) + 3; - while (repeat--) { - literalCodeLengths.push(0); - } - } - else if (symbol === 18) { - var repeat = bstream.readBits(7) + 11; - while (repeat--) { - literalCodeLengths.push(0); - } - } - } - - // now split the distance code lengths out of the literal code array - var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes); - - // now generate the true Huffman Code tables using these code lengths - var hcLiteralTable = getHuffmanCodes(literalCodeLengths), - hcDistanceTable = getHuffmanCodes(distanceCodeLengths); - blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer); - } - // error - else { - err("Error! Encountered deflate block of type 3"); - return null; - } - - // update progress - currentBytesUnarchivedInFile += blockSize; - currentBytesUnarchived += blockSize; - postProgress(); - - } while (bFinal !== 1); - // we are done reading blocks if the bFinal bit was set for this block - - // return the buffer data bytes - return buffer.data; -} - -// event.data.file has the ArrayBuffer. -onmessage = function(event) { - unzip(event.data.file, true); -}; diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index cd8c321c..a8dddef5 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -76,8 +76,7 @@ + autocomplete="off">
@@ -88,6 +87,12 @@
+ {% if rarfile_support %} +
+ + +
+ {% endif %}
diff --git a/cps/templates/readcbr.html b/cps/templates/readcbr.html index 4893e038..cf2753d2 100644 --- a/cps/templates/readcbr.html +++ b/cps/templates/readcbr.html @@ -13,11 +13,11 @@ - + diff --git a/cps/ub.py b/cps/ub.py index 7b1f92ba..4c9159a4 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -296,6 +296,7 @@ class Settings(Base): config_goodreads_api_key = Column(String) config_goodreads_api_secret = Column(String) config_mature_content_tags = Column(String) # type: str + config_rarfile_location = Column(String) def __repr__(self): pass @@ -357,6 +358,7 @@ class Config: self.config_goodreads_api_key = data.config_goodreads_api_key self.config_goodreads_api_secret = data.config_goodreads_api_secret self.config_mature_content_tags = data.config_mature_content_tags + self.config_rarfile_location = data.config_rarfile_location @property def get_main_dir(self): @@ -487,6 +489,13 @@ def migrate_Database(): conn = engine.connect() conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1") session.commit() + try: + session.query(exists().where(Settings.config_rarfile_location)).scalar() + session.commit() + except exc.OperationalError: # Database is not compatible, some rows are missing + conn = engine.connect() + conn.execute("ALTER TABLE Settings ADD column `config_rarfile_location` String DEFAULT ''") + session.commit() try: create = False session.query(exists().where(User.sidebar_view)).scalar() diff --git a/cps/web.py b/cps/web.py index 8f6c9046..ec002fc8 100755 --- a/cps/web.py +++ b/cps/web.py @@ -24,6 +24,12 @@ try: except ImportError: pass # We're not using Python 3 +try: + import rarfile + rar_support=True +except ImportError: + rar_support=False + import mimetypes import logging from logging.handlers import RotatingFileHandler @@ -51,6 +57,7 @@ from flask_babel import Babel from flask_babel import gettext as _ import requests import zipfile +import tarfile from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.datastructures import Headers from babel import Locale as LC @@ -908,6 +915,122 @@ def get_metadata_calibre_companion(uuid): return "" +@app.route("/ajax/getcomic///") +@login_required +def get_comic_book(book_id, book_format, page): + book = db.session.query(db.Books).filter(db.Books.id == book_id).first() + if not book: + return "" + else: + for bookformat in book.data: + if bookformat.format.lower() == book_format.lower(): + cbr_file = os.path.join(config.config_calibre_dir, book.path, bookformat.name) + "." + book_format + if book_format == "cbr": + if rar_support == True: + rarfile.UNRAR_TOOL = config.config_rarfile_location + try: + rf = rarfile.RarFile(cbr_file) + rarNames = rf.namelist() + extractedfile="data:image/png;base64," + (rf.read(rarNames[page])).encode('base64') + fileData={"name": rarNames[page],"page":page, "last":rarNames.__len__()-1, "content": extractedfile} + except: + return "" + # rarfile not valid + # ToDo: error handling + else: + # no support means return nothing + return "" + if book_format == "cbz": + zf = zipfile.ZipFile(cbr_file) + zipNames=zf.namelist() + extractedfile="data:image/png;base64," + (zf.read(zipNames[page])).encode('base64') + fileData={"name": zipNames[page],"page":page, "last":zipNames.__len__()-1, "content": extractedfile} + + if book_format == "cbt": + tf = tarfile.TarFile(u'D:\\zip\\test.cbt') + tarNames=tf.getnames() + extractedfile="data:image/png;base64," + (tf.extractfile(tarNames[page]).read()).encode('base64') + fileData={"name": tarNames[page],"page":page, "last":tarNames.__len__()-1, "content": extractedfile} + return make_response(json.dumps(fileData)) + +@app.route("/ajax/toggleread/", methods=['POST']) +@login_required +def toggle_read(book_id): + book = ub.session.query(ub.ReadBook).filter(ub.and_(ub.ReadBook.user_id == int(current_user.id), + ub.ReadBook.book_id == book_id)).first() + if book: + book.is_read = not book.is_read + else: + readBook = ub.ReadBook() + readBook.user_id = int(current_user.id) + readBook.book_id = book_id + readBook.is_read = True + book = readBook + ub.session.merge(book) + ub.session.commit() + return "" + + +@app.route('/ajax/verify_token', methods=['POST']) +@remote_login_required +def token_verified(): + token = request.form['token'] + auth_token = ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.auth_token == token).first() + + data = {} + + # Token not found + if auth_token is None: + data['status'] = 'error' + data['message'] = _(u"Token not found") + + # Token expired + elif datetime.datetime.now() > auth_token.expiration: + ub.session.delete(auth_token) + ub.session.commit() + + data['status'] = 'error' + data['message'] = _(u"Token has expired") + + elif not auth_token.verified: + data['status'] = 'not_verified' + + else: + user = ub.session.query(ub.User).filter(ub.User.id == auth_token.user_id).first() + login_user(user) + + ub.session.delete(auth_token) + ub.session.commit() + + data['status'] = 'success' + flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") + + response = make_response(json.dumps(data, ensure_ascii=false)) + response.headers["Content-Type"] = "application/json; charset=utf-8" + + return response + + +@app.route("/ajax/bookmark//", methods=['POST']) +@login_required +def bookmark(book_id, book_format): + bookmark_key = request.form["bookmark"] + ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id), + ub.Bookmark.book_id == book_id, + ub.Bookmark.format == book_format)).delete() + if not bookmark_key: + ub.session.commit() + return "", 204 + + bookmark = ub.Bookmark(user_id=current_user.id, + book_id=book_id, + format=book_format, + bookmark_key=bookmark_key) + ub.session.merge(bookmark) + ub.session.commit() + return "", 201 + + @app.route("/get_authors_json", methods=['GET', 'POST']) @login_required_if_no_ano def get_authors_json(): @@ -1297,22 +1420,6 @@ def category(book_id, page): title=_(u"Category: %(name)s", name=name)) -@app.route("/ajax/toggleread/", methods=['POST']) -@login_required -def toggle_read(book_id): - book = ub.session.query(ub.ReadBook).filter(ub.and_(ub.ReadBook.user_id == int(current_user.id), - ub.ReadBook.book_id == book_id)).first() - if book: - book.is_read = not book.is_read - else: - readBook = ub.ReadBook() - readBook.user_id = int(current_user.id) - readBook.book_id = book_id - readBook.is_read = True - book = readBook - ub.session.merge(book) - ub.session.commit() - return "" @app.route("/book/") @@ -1356,24 +1463,6 @@ def show_book(book_id): return redirect(url_for("index")) -@app.route("/ajax/bookmark//", methods=['POST']) -@login_required -def bookmark(book_id, book_format): - bookmark_key = request.form["bookmark"] - ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id), - ub.Bookmark.book_id == book_id, - ub.Bookmark.format == book_format)).delete() - if not bookmark_key: - ub.session.commit() - return "", 204 - - bookmark = ub.Bookmark(user_id=current_user.id, - book_id=book_id, - format=book_format, - bookmark_key=bookmark_key) - ub.session.merge(bookmark) - ub.session.commit() - return "", 201 @app.route("/admin") @@ -1858,14 +1947,15 @@ def read_book(book_id, book_format): elif book_format.lower() == "txt": return render_title_template('readtxt.html', txtfile=book_id, title=_(u"Read a Book")) else: - for fileext in ["cbr","cbt","cbz"]: + if rar_support == True: + extensionList = ["cbr","cbt","cbz"] + else: + extensionList = ["cbt","cbz"] + for fileext in extensionList: if book_format.lower() == fileext: - all_name = str(book_id) + "/" + book.data[0].name + "." + fileext - tmp_file = os.path.join(book_dir, book.data[0].name) + "." + fileext - if not os.path.exists(all_name): - cbr_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + "." + fileext - copyfile(cbr_file, tmp_file) - return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book")) + return render_title_template('readcbr.html', comicfile=book_id, extension=fileext, title=_(u"Read a Book")) + else: + flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") @app.route("/download//") @@ -2021,44 +2111,6 @@ def verify_token(token): return redirect(url_for('index')) -@app.route('/ajax/verify_token', methods=['POST']) -@remote_login_required -def token_verified(): - token = request.form['token'] - auth_token = ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.auth_token == token).first() - - data = {} - - # Token not found - if auth_token is None: - data['status'] = 'error' - data['message'] = _(u"Token not found") - - # Token expired - elif datetime.datetime.now() > auth_token.expiration: - ub.session.delete(auth_token) - ub.session.commit() - - data['status'] = 'error' - data['message'] = _(u"Token has expired") - - elif not auth_token.verified: - data['status'] = 'not_verified' - - else: - user = ub.session.query(ub.User).filter(ub.User.id == auth_token.user_id).first() - login_user(user) - - ub.session.delete(auth_token) - ub.session.commit() - - data['status'] = 'success' - flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") - - response = make_response(json.dumps(data, ensure_ascii=false)) - response.headers["Content-Type"] = "application/json; charset=utf-8" - - return response @app.route('/send/') @@ -2480,6 +2532,12 @@ def configuration_helper(origin): if "config_mature_content_tags" in to_save: content.config_mature_content_tags = to_save["config_mature_content_tags"].strip() + # Rarfile Content configuration + # ToDo check: location valid + if "config_rarfile_location" in to_save: + content.config_rarfile_location = to_save["config_rarfile_location"].strip() + + content.config_default_role = 0 if "admin_role" in to_save: content.config_default_role = content.config_default_role + ub.ROLE_ADMIN @@ -2510,13 +2568,15 @@ def configuration_helper(origin): except e: flash(e, category="error") return render_title_template("config_edit.html", content=config, origin=origin, gdrive=gdrive_support, - goodreads=goodreads_support, title=_(u"Basic Configuration")) + goodreads=goodreads_support, rarfile_support=rar_support, + title=_(u"Basic Configuration")) if db_change: reload(db) if not db.setup_db(): flash(_(u'DB location is not valid, please enter correct path'), category="error") return render_title_template("config_edit.html", content=config, origin=origin, gdrive=gdrive_support, - goodreads=goodreads_support, title=_(u"Basic Configuration")) + goodreads=goodreads_support, rarfile_support=rar_support, + title=_(u"Basic Configuration")) if reboot_required: # db.engine.dispose() # ToDo verify correct ub.session.close() @@ -2530,7 +2590,8 @@ def configuration_helper(origin): success = True return render_title_template("config_edit.html", origin=origin, success=success, content=config, show_authenticate_google_drive=not is_gdrive_ready(), gdrive=gdrive_support, - goodreads=goodreads_support, title=_(u"Basic Configuration")) + goodreads=goodreads_support, rarfile_support=rar_support, + title=_(u"Basic Configuration")) @app.route("/admin/user/new", methods=["GET", "POST"]) diff --git a/optional-requirements.txt b/optional-requirements.txt index cf743dbb..fe37ed61 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -13,3 +13,4 @@ six==1.10.0 uritemplate==3.0.0 goodreads>=0.3.2 python-Levenshtein>=0.12.0 +rarfile>=2.7 From 9f8cbe8c1f7035820437000683dcd60e338f2b89 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sun, 19 Nov 2017 13:20:59 +0100 Subject: [PATCH 02/19] Merge enhanced UI from ytilis repro --- cps/static/css/kthoom.css | 142 ++++++++++++++++++++------ cps/static/js/kthoom.js | 199 ++++++++++++++++++++++++------------- cps/templates/readcbr.html | 120 ++++++++++++++++++++-- 3 files changed, 354 insertions(+), 107 deletions(-) diff --git a/cps/static/css/kthoom.css b/cps/static/css/kthoom.css index a6b41a32..061d171b 100644 --- a/cps/static/css/kthoom.css +++ b/cps/static/css/kthoom.css @@ -1,27 +1,58 @@ body { background: #444; - overflow: hidden; + overflow-x: hidden; + overflow-y: auto; color: white; font-family: sans-serif; margin: 0px; } -.main { - position: re; - left: 5px; - overflow: hidden; - right: 5px; +#main { text-align: center; - top: 5px; + z-index: 2; +} + +#sidebar a, +#sidebar ul, +#sidebar li, +#sidebar li img { + max-width: 100%; + text-align: center; +} + +#sidebar a { + display: inline-block; + position: relative; + cursor: pointer; +} + +#sidebar li img { + display: block; + max-height: 200px; +} + +#sidebar li img + span { + position: absolute; + bottom: 0; + right: 0; + padding: 2px; + min-width: 25px; + line-height: 25px; + background: #6b6b6b; + border-top-left-radius: 5px; +} + +#sidebar #panels { + z-index: 1; } #progress { position: absolute; display: inline; - left: 90px; - right: 160px; + left: 20%; + right: 20%; height: 20px; - margin-top: 1px; + margin-top: 4px; text-align: right; } @@ -29,6 +60,10 @@ body { display: none !important; } +#mainContent { + overflow: auto; +} + #mainText { text-align: left; width: 90%; @@ -42,29 +77,9 @@ body { word-wrap: break-word; } -#mainImage{ - margin-top: 32px; -} - -#titlebar.main { - opacity: 0; - position: absolute; - top: 0; - height: 30px; - left: 0; - right: 0; - background-color: black; - padding-bottom: 70px; - -webkit-transition: opacity 0.2s ease; - -moz-transition: opacity 0.2s ease; - transition: opacity 0.2s ease; - background: -moz-linear-gradient(top, rgba(0,2,34,1) 0%, rgba(0,1,24,1) 30%, rgba(0,0,0,0) 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,2,34,1)), color-stop(30%,rgba(0,1,24,1)), color-stop(100%,rgba(0,0,0,0))); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* IE10+ */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#000222', endColorstr='#00000000',GradientType=0 ); /* IE6-9 */ - background: linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* W3C */ +#titlebar { + min-height: 25px; + height: auto; } #prev { @@ -100,6 +115,67 @@ body { color: #000; } +th, td { + padding: 5px; +} + +th { + text-align: right; + vertical-align: top; +} + +.modal { + /* Makes the modal responsive. Note sure if this should be moved to main.css */ + margin: 0; + max-width: 96%; + transform: translate(-50%, -50%); +} + +.md-content { + min-height: 320px; + height: auto; +} + +.md-content > div { + overflow: hidden; +} + +.md-content > div p { + padding: 5px 0; +} + +.settings-column { + float: left; + min-width: 35%; + padding-bottom: 10px; +} +.inputs { + margin: -5px; +} + +.inputs input { + vertical-align: middle; +} +.inputs label { + display: inline-block; + margin: 5px; + white-space: nowrap; +} + +.dark-theme #main { + background-color: #000; +} +.dark-theme #titlebar { + color: #DDD; +} + +.dark-theme #titlebar a:active { + color: #FFF; +} + +.dark-theme .overlay { + background-color: rgba(0,0,0,0.8); +} diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index 0dd41322..72c1d04d 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -55,9 +55,6 @@ kthoom.Key = { RIGHT_SQUARE_BRACKET: 221 }; -// The rotation orientation of the comic. -kthoom.rotateTimes = 0; - // global variables var unarchiver = null; var currentImage = 0; @@ -66,33 +63,45 @@ var imageFilenames = []; var totalImages = 0; var lastCompletion = 0; -var hflip = false, vflip = false, fitMode = kthoom.Key.B; +var settings = { + hflip: false, + vflip: false, + rotateTimes: 0, + fitMode: kthoom.Key.B, + theme: 'light' +}; + var canKeyNext = true, canKeyPrev = true; kthoom.saveSettings = function() { - localStorage.kthoomSettings = JSON.stringify({ - rotateTimes: kthoom.rotateTimes, - hflip: hflip, - vflip: vflip, - fitMode: fitMode - }); + localStorage.kthoomSettings = JSON.stringify(settings); }; kthoom.loadSettings = function() { try { - if (localStorage.kthoomSettings.length < 10){ + if (!localStorage.kthoomSettings){ return; } - var s = JSON.parse(localStorage.kthoomSettings); - kthoom.rotateTimes = s.rotateTimes; - hflip = s.hflip; - vflip = s.vflip; - fitMode = s.fitMode; + + $.extend(settings, JSON.parse(localStorage.kthoomSettings)); + + kthoom.setSettings(); } catch (err) { alert("Error load settings"); } }; +kthoom.setSettings = function() { + // Set settings control values + $.each(settings, function(key, value) { + if (typeof value === "boolean") { + $('input[name='+key+']').prop('checked', value); + } else { + $('input[name='+key+']').val([value]); + } + }); +}; + var createURLFromArray = function(array, mimeType) { var offset = array.byteOffset, len = array.byteLength; var url; @@ -279,6 +288,17 @@ function loadFromArrayBuffer(ab) { if (imageFilenames.indexOf(f.filename) === -1) { imageFilenames.push(f.filename); imageFiles.push(new kthoom.ImageFile(f)); + + // add thumbnails to the TOC list + $('#thumbnails').append( + "
  • \ + \ + \ + "+ imageFiles.length +" \ + \ +
  • " + ); + //} } var percentage = (ab.page+1) / (ab.last+1); totalImages = ab.last+1; @@ -304,6 +324,11 @@ function updatePage() { } else { setImage("loading"); } + + $('body').toggleClass('dark-theme', settings.theme === 'dark'); + + kthoom.setSettings(); + kthoom.saveSettings(); } function setImage(url) { @@ -359,22 +384,22 @@ function setImage(url) { w = img.width, sw = w, sh = h; - kthoom.rotateTimes = (4 + kthoom.rotateTimes) % 4; + settings.rotateTimes = (4 + settings.rotateTimes) % 4; x.save(); - if (kthoom.rotateTimes % 2 === 1) { + if (settings.rotateTimes % 2 === 1) { sh = w; sw = h; } canvas.height = sh; canvas.width = sw; x.translate(sw / 2, sh / 2); - x.rotate(Math.PI / 2 * kthoom.rotateTimes); + x.rotate(Math.PI / 2 * settings.rotateTimes); x.translate(-w / 2, -h / 2); - if (vflip) { + if (settings.vflip) { x.scale(1, -1); x.translate(0, -h); } - if (hflip) { + if (settings.hflip) { x.scale(-1, 1); x.translate(-w, 0); } @@ -418,19 +443,19 @@ function updateScale(clear) { mainImageStyle.height = ""; mainImageStyle.maxWidth = ""; mainImageStyle.maxHeight = ""; - var maxheight = innerHeight - 15; - if (!/main/.test(getElem("titlebar").className)) { - maxheight -= 25; - } - if (clear || fitMode === kthoom.Key.N) { - } else if (fitMode === kthoom.Key.B) { + var maxheight = innerHeight - 50; + + if (clear || settings.fitMode === kthoom.Key.N) { + } else if (settings.fitMode === kthoom.Key.B) { mainImageStyle.maxWidth = "100%"; mainImageStyle.maxHeight = maxheight + "px"; - } else if (fitMode === kthoom.Key.H) { + } else if (settings.fitMode === kthoom.Key.H) { mainImageStyle.height = maxheight + "px"; - } else if (fitMode === kthoom.Key.W) { + } else if (settings.fitMode === kthoom.Key.W) { mainImageStyle.width = "100%"; } + $('#mainContent').css({maxHeight: maxheight + 5}); + kthoom.setSettings(); kthoom.saveSettings(); } @@ -446,50 +471,53 @@ function keyHandler(evt) { if (evt.ctrlKey || evt.shiftKey || evt.metaKey) return; switch (code) { case kthoom.Key.LEFT: - if (canKeyPrev) showPrevPage(); + showPrevPage(); break; case kthoom.Key.RIGHT: - if (canKeyNext) showNextPage(); + showNextPage(); break; case kthoom.Key.L: - kthoom.rotateTimes--; - if (kthoom.rotateTimes < 0) { - kthoom.rotateTimes = 3; + settings.rotateTimes--; + if (settings.rotateTimes < 0) { + settings.rotateTimes = 3; } updatePage(); break; case kthoom.Key.R: - kthoom.rotateTimes++; - if (kthoom.rotateTimes > 3) { - kthoom.rotateTimes = 0; + settings.rotateTimes++; + if (settings.rotateTimes > 3) { + settings.rotateTimes = 0; } updatePage(); break; case kthoom.Key.F: - if (!hflip && !vflip) { - hflip = true; - } else if (hflip === true) { - vflip = true; - hflip = false; - } else if (vflip === true) { - vflip = false; + if (!settings.hflip && !settings.vflip) { + settings.hflip = true; + } else if (settings.hflip === true && settings.vflip === true) { + settings.vflip = false; + settings.hflip = false; + } else if (settings.hflip === true) { + settings.vflip = true; + settings.hflip = false; + } else if (settings.vflip === true) { + settings.hflip = true; } updatePage(); break; case kthoom.Key.W: - fitMode = kthoom.Key.W; + settings.fitMode = kthoom.Key.W; updateScale(); break; case kthoom.Key.H: - fitMode = kthoom.Key.H; + settings.fitMode = kthoom.Key.H; updateScale(); break; case kthoom.Key.B: - fitMode = kthoom.Key.B; + settings.fitMode = kthoom.Key.B; updateScale(); break; case kthoom.Key.N: - fitMode = kthoom.Key.N; + settings.fitMode = kthoom.Key.N; updateScale(); break; default: @@ -520,35 +548,70 @@ function init(fileid) { request.open("GET", fileid); request.responseType = "json"; request.fileid=fileid.substring(0,fileid.length - 2); - request.addEventListener("load",ImageLoadCallback);/* function(event) { - var jso=request.response; - if (jso.page!=jso.length) - { - // var secRequest = new XMLHttpRequest(); - request.open("GET", fileid + "/../"+(jso.page+1)); - request.send(); - //secRequest.responseType = "json"; - //finished; - } - loadFromArrayBuffer(jso); - - // var byteArray = new Uint8Array(request.response); - // if you want to access the bytes: - });*/ + request.addEventListener("load",ImageLoadCallback); request.send(); - kthoom.initProgressMeter(); document.body.className += /AppleWebKit/.test(navigator.userAgent) ? " webkit" : ""; - updateScale(true); kthoom.loadSettings(); + updateScale(true); $(document).keydown(keyHandler); $(window).resize(function() { - var f = (screen.width - innerWidth < 4 && screen.height - innerHeight < 4); - getElem("titlebar").className = f ? "main" : ""; updateScale(); }); + // Open TOC menu + $("#slider").click(function(evt) { + $('#sidebar').toggleClass('open'); + $('#main').toggleClass('closed'); + $(this).toggleClass('icon-menu icon-right'); + }); + + // Open Settings modal + $("#setting").click(function(evt) { + $("#settings-modal").toggleClass('md-show'); + }); + + // On Settings input change + $("#settings input").on("change", function(evt){ + // Get either the checked boolean or the assigned value + var value = this.type === 'checkbox' ? this.checked : this.value; + + // If it's purely numeric, parse it to an integer + value = /^\d+$/.test(value) ? parseInt(value) : value; + + settings[this.name] = value; + updatePage(); + updateScale(); + }); + + // Close modal + $(".closer, .overlay").click(function(evt) { + $(".md-show").removeClass('md-show'); + }); + + // TOC thumbnail pagination + $('#thumbnails').on("click", "a", function(evt) { + currentImage = $(this).data('page') - 1; + updatePage(); + }); + + // Fullscreen mode + if (typeof screenfull !== "undefined") { + $('#fullscreen').click(function(evt) { + screenfull.toggle($("#container")[0]) + }); + + if (screenfull.raw) { + var $button = $('#fullscreen'); + document.addEventListener(screenfull.raw.fullscreenchange,function(){ + screenfull.isFullscreen + ? $button.addClass("icon-resize-small").removeClass("icon-resize-full") + : $button.addClass("icon-resize-full").removeClass("icon-resize-small") + }); + } + } + $("#mainImage").click(function(evt) { // Firefox does not support offsetX/Y so we have to manually calculate // where the user clicked in the image. @@ -564,7 +627,7 @@ function init(fileid) { // Determine if the user clicked/tapped the left side or the // right side of the page. var clickedPrev = false; - switch (kthoom.rotateTimes) { + switch (settings.rotateTimes) { case 0: clickedPrev = clickX < (comicWidth / 2); break; diff --git a/cps/templates/readcbr.html b/cps/templates/readcbr.html index cf2753d2..59296a7a 100644 --- a/cps/templates/readcbr.html +++ b/cps/templates/readcbr.html @@ -7,13 +7,14 @@ + + + - - - + + - -
    -
    + + +
    +
    +
    + Menu +
    +
    + +   –   + +
    + +
    +
    @@ -40,5 +68,85 @@
    + + +
    + From ec12181803a578c543b5a12cf703a7c4748b83aa Mon Sep 17 00:00:00 2001 From: Andriy Zasypkin Date: Sun, 19 Nov 2017 08:49:40 -0500 Subject: [PATCH 03/19] added python3 compatibility for comic reader --- cps/web.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/cps/web.py b/cps/web.py index ec002fc8..7fb64aa5 100755 --- a/cps/web.py +++ b/cps/web.py @@ -99,6 +99,9 @@ try: except ImportError: from flask_login.__about__ import __version__ as flask_loginVersion +if sys.version_info.major >= 3: + import codecs + import time current_milli_time = lambda: int(round(time.time() * 1000)) @@ -931,25 +934,38 @@ def get_comic_book(book_id, book_format, page): try: rf = rarfile.RarFile(cbr_file) rarNames = rf.namelist() - extractedfile="data:image/png;base64," + (rf.read(rarNames[page])).encode('base64') + if sys.version_info.major >= 3: + b64 = codecs.encode(rf.read(rarNames[page]), 'base64').decode() + else: + b64 = rf.read(rarNames[page]).encode('base64') + extractedfile="data:image/png;base64," + b64 fileData={"name": rarNames[page],"page":page, "last":rarNames.__len__()-1, "content": extractedfile} except: return "" # rarfile not valid # ToDo: error handling else: + # no support means return nothing return "" if book_format == "cbz": zf = zipfile.ZipFile(cbr_file) zipNames=zf.namelist() - extractedfile="data:image/png;base64," + (zf.read(zipNames[page])).encode('base64') + if sys.version_info.major >= 3: + b64 = codecs.encode(zf.read(zipNames[page]), 'base64').decode() + else: + b64 = zf.read(zipNames[page]).encode('base64') + extractedfile="data:image/png;base64," + b64 fileData={"name": zipNames[page],"page":page, "last":zipNames.__len__()-1, "content": extractedfile} if book_format == "cbt": tf = tarfile.TarFile(u'D:\\zip\\test.cbt') tarNames=tf.getnames() - extractedfile="data:image/png;base64," + (tf.extractfile(tarNames[page]).read()).encode('base64') + if sys.version_info.major >= 3: + b64 = codecs.encode(tf.extractfile(tarNames[page]).read(), 'base64').decode() + else: + b64 = (tf.extractfile(tarNames[page]).read()).encode('base64') + extractedfile="data:image/png;base64," + bs fileData={"name": tarNames[page],"page":page, "last":tarNames.__len__()-1, "content": extractedfile} return make_response(json.dumps(fileData)) From 57567850739de958a03d1bfd5042e28bff178e65 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sun, 19 Nov 2017 18:08:55 +0100 Subject: [PATCH 04/19] Error handling rarfiles --- cps/helper.py | 19 ++++ cps/static/js/kthoom.js | 192 +++++++++++++++++++++------------------ cps/templates/stats.html | 4 + cps/web.py | 23 ++++- 4 files changed, 146 insertions(+), 92 deletions(-) diff --git a/cps/helper.py b/cps/helper.py index 52e96b6c..60c9e5fc 100755 --- a/cps/helper.py +++ b/cps/helper.py @@ -508,3 +508,22 @@ class Updater(threading.Thread): logging.getLogger('cps.web').debug("Could not remove:" + item_path) shutil.rmtree(source, ignore_errors=True) +def check_unrar(unrarLocation): + error = False + if os.path.exists(unrarLocation): + try: + p = subprocess.Popen(unrarLocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p.wait() + for lines in p.stdout.readlines(): + if isinstance(lines, bytes): + lines = lines.decode('utf-8') + value=re.search('UNRAR (.*) freeware', lines) + if value: + version = value.group(1) + except Exception: + error = True + version=_(u'Excecution permissions missing') + else: + version = _(u'Unrar binary file not found') + error=True + return (error, version) diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index 72c1d04d..032f5220 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -289,16 +289,15 @@ function loadFromArrayBuffer(ab) { imageFilenames.push(f.filename); imageFiles.push(new kthoom.ImageFile(f)); - // add thumbnails to the TOC list - $('#thumbnails').append( - "
  • \ - \ - \ - "+ imageFiles.length +" \ - \ -
  • " - ); - //} + // add thumbnails to the TOC list + $('#thumbnails').append( + "
  • \ + \ + \ + "+ imageFiles.length +" \ + \ +
  • " + ); } var percentage = (ab.page+1) / (ab.last+1); totalImages = ab.last+1; @@ -339,81 +338,93 @@ function setImage(url) { updateScale(true); canvas.width = innerWidth - 100; canvas.height = 200; - x.fillStyle = "red"; - x.font = "50px sans-serif"; + x.fillStyle = "black"; + x.textAlign="center"; + x.font = "24px sans-serif"; x.strokeStyle = "black"; - x.fillText("Loading Page #" + (currentImage + 1), 100, 100); + x.fillText("Loading Page #" + (currentImage + 1), innerWidth/2, 100); } else { - if ($("body").css("scrollHeight") / innerHeight > 1) { - $("body").css("overflowY", "scroll"); - } - - var img = new Image(); - img.onerror = function() { - canvas.width = innerWidth - 100; - canvas.height = 300; + if (url === "error") { updateScale(true); - x.fillStyle = "orange"; - x.font = "50px sans-serif"; + canvas.width = innerWidth - 100; + canvas.height = 200; + x.fillStyle = "black"; + x.textAlign="center"; + x.font = "24px sans-serif"; x.strokeStyle = "black"; - x.fillText("Page #" + (currentImage + 1) + " (" + - imageFiles[currentImage].filename + ")", 100, 100); - x.fillStyle = "red"; - x.fillText("Is corrupt or not an image", 100, 200); - - var xhr = new XMLHttpRequest(); - if (/(html|htm)$/.test(imageFiles[currentImage].filename)) { - xhr.open("GET", url, true); - xhr.onload = function() { - //document.getElementById('mainText').style.display = ''; - $("#mainText").css("display", ""); - $("#mainText").innerHTML(""); - } - xhr.send(null); - } else if (!/(jpg|jpeg|png|gif)$/.test(imageFiles[currentImage].filename) && imageFiles[currentImage].data.uncompressedSize < 10 * 1024) { - xhr.open("GET", url, true); - xhr.onload = function() { - $("#mainText").css("display", ""); - $("#mainText").innerText(xhr.responseText); - }; - xhr.send(null); - } - }; - img.onload = function() { - var h = img.height, - w = img.width, - sw = w, - sh = h; - settings.rotateTimes = (4 + settings.rotateTimes) % 4; - x.save(); - if (settings.rotateTimes % 2 === 1) { - sh = w; - sw = h; - } - canvas.height = sh; - canvas.width = sw; - x.translate(sw / 2, sh / 2); - x.rotate(Math.PI / 2 * settings.rotateTimes); - x.translate(-w / 2, -h / 2); - if (settings.vflip) { - x.scale(1, -1); - x.translate(0, -h); - } - if (settings.hflip) { - x.scale(-1, 1); - x.translate(-w, 0); + x.fillText("Unable to decompress image #" + (currentImage + 1), innerWidth/2, 100); + } else { + if ($("body").css("scrollHeight") / innerHeight > 1) { + $("body").css("overflowY", "scroll"); } - canvas.style.display = "none"; - scrollTo(0, 0); - x.drawImage(img, 0, 0); - updateScale(); + var img = new Image(); + img.onerror = function() { + canvas.width = innerWidth - 100; + canvas.height = 300; + updateScale(true); + x.fillStyle = "black"; + x.font = "50px sans-serif"; + x.strokeStyle = "black"; + x.fillText("Page #" + (currentImage + 1) + " (" + + imageFiles[currentImage].filename + ")", innerWidth/2, 100); + x.fillStyle = "black"; + x.fillText("Is corrupt or not an image", innerWidth/2, 200); + + var xhr = new XMLHttpRequest(); + if (/(html|htm)$/.test(imageFiles[currentImage].filename)) { + xhr.open("GET", url, true); + xhr.onload = function() { + //document.getElementById('mainText').style.display = ''; + $("#mainText").css("display", ""); + $("#mainText").innerHTML(""); + } + xhr.send(null); + } else if (!/(jpg|jpeg|png|gif)$/.test(imageFiles[currentImage].filename) && imageFiles[currentImage].data.uncompressedSize < 10 * 1024) { + xhr.open("GET", url, true); + xhr.onload = function() { + $("#mainText").css("display", ""); + $("#mainText").innerText(xhr.responseText); + }; + xhr.send(null); + } + }; + img.onload = function() { + var h = img.height, + w = img.width, + sw = w, + sh = h; + settings.rotateTimes = (4 + settings.rotateTimes) % 4; + x.save(); + if (settings.rotateTimes % 2 === 1) { + sh = w; + sw = h; + } + canvas.height = sh; + canvas.width = sw; + x.translate(sw / 2, sh / 2); + x.rotate(Math.PI / 2 * settings.rotateTimes); + x.translate(-w / 2, -h / 2); + if (settings.vflip) { + x.scale(1, -1); + x.translate(0, -h); + } + if (settings.hflip) { + x.scale(-1, 1); + x.translate(-w, 0); + } + canvas.style.display = "none"; + scrollTo(0, 0); + x.drawImage(img, 0, 0); + + updateScale(); - canvas.style.display = ""; - $("body").css("overflowY", ""); - x.restore(); - }; - img.src = url; + canvas.style.display = ""; + $("body").css("overflowY", ""); + x.restore(); + }; + img.src = url; + } } } @@ -528,19 +539,26 @@ function keyHandler(evt) { function ImageLoadCallback(event) { var jso=this.response; - if (jso.page !== jso.last) - { - // var secRequest = new XMLHttpRequest(); - this.open("GET", this.fileid + "/"+(jso.page+1)); - this.addEventListener("load",ImageLoadCallback); - this.send(); + // Unable to decompress file, or no response from server + if (jso === null){ + setImage("error"); } else { - var diff = ((new Date).getTime() - start)/1000; - console.log('Transfer done in ' + diff + 's'); + if (jso.page !== jso.last) + { + // var secRequest = new XMLHttpRequest(); + this.open("GET", this.fileid + "/"+(jso.page+1)); + this.addEventListener("load",ImageLoadCallback); + this.send(); + } + else + { + var diff = ((new Date).getTime() - start)/1000; + console.log('Transfer done in ' + diff + 's'); + } + loadFromArrayBuffer(jso); } - loadFromArrayBuffer(jso); } function init(fileid) { start = (new Date).getTime(); diff --git a/cps/templates/stats.html b/cps/templates/stats.html index 7be8f613..a313e1c4 100644 --- a/cps/templates/stats.html +++ b/cps/templates/stats.html @@ -42,6 +42,10 @@ Kindlegen {{versions['KindlegenVersion']}} + + Unrar + {{versions['unrarVersion']}} + ImageMagick {{versions['ImageVersion']}} diff --git a/cps/web.py b/cps/web.py index ec002fc8..f14a601d 100755 --- a/cps/web.py +++ b/cps/web.py @@ -934,10 +934,11 @@ def get_comic_book(book_id, book_format, page): extractedfile="data:image/png;base64," + (rf.read(rarNames[page])).encode('base64') fileData={"name": rarNames[page],"page":page, "last":rarNames.__len__()-1, "content": extractedfile} except: - return "" # rarfile not valid - # ToDo: error handling + app.logger.error('Unrar binary not found unable to decompress file ' + cbr_file) + return "" else: + app.logger.info('Unrar is not supported please install python rarfile extension') # no support means return nothing return "" if book_format == "cbz": @@ -1507,7 +1508,13 @@ def stats(): versions['requests'] = requests.__version__ versions['pysqlite'] = db.engine.dialect.dbapi.version versions['sqlite'] = db.engine.dialect.dbapi.sqlite_version - + if rar_support: + rarVersion = helper.check_unrar(config.config_rarfile_location) + if not rarVersion[0]: + versions['unrarVersion'] = rarVersion[1] + else: + versions['unrarVersion'] = _('not installed') + app.logger.info(rarVersion[1]) return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=versions, categorycounter=categorys, seriecounter=series, title=_(u"Statistics")) @@ -2535,8 +2542,14 @@ def configuration_helper(origin): # Rarfile Content configuration # ToDo check: location valid if "config_rarfile_location" in to_save: - content.config_rarfile_location = to_save["config_rarfile_location"].strip() - + check = helper.check_unrar(to_save["config_rarfile_location"].strip()) + if not check[0] : + content.config_rarfile_location = to_save["config_rarfile_location"].strip() + else: + flash(check[1], category="error") + return render_title_template("config_edit.html", content=config, origin=origin, gdrive=gdrive_support, + goodreads=goodreads_support, rarfile_support=rar_support, + title=_(u"Basic Configuration")) content.config_default_role = 0 if "admin_role" in to_save: From e7b6963afdec2433ae5bfa91529361f9748b0946 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sun, 19 Nov 2017 20:37:43 +0100 Subject: [PATCH 05/19] Small improvements for comic reader calls --- cps/web.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cps/web.py b/cps/web.py index f14a601d..2e0aec9e 100755 --- a/cps/web.py +++ b/cps/web.py @@ -920,7 +920,7 @@ def get_metadata_calibre_companion(uuid): def get_comic_book(book_id, book_format, page): book = db.session.query(db.Books).filter(db.Books.id == book_id).first() if not book: - return "" + return "", 204 else: for bookformat in book.data: if bookformat.format.lower() == book_format.lower(): @@ -935,12 +935,12 @@ def get_comic_book(book_id, book_format, page): fileData={"name": rarNames[page],"page":page, "last":rarNames.__len__()-1, "content": extractedfile} except: # rarfile not valid - app.logger.error('Unrar binary not found unable to decompress file ' + cbr_file) - return "" + app.logger.error('Unrar binary not found, or unable to decompress file ' + cbr_file) + return "", 204 else: app.logger.info('Unrar is not supported please install python rarfile extension') # no support means return nothing - return "" + return "", 204 if book_format == "cbz": zf = zipfile.ZipFile(cbr_file) zipNames=zf.namelist() @@ -1961,9 +1961,8 @@ def read_book(book_id, book_format): for fileext in extensionList: if book_format.lower() == fileext: return render_title_template('readcbr.html', comicfile=book_id, extension=fileext, title=_(u"Read a Book")) - else: - flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") - + flash(_(u"Error opening eBook. File does not exist or file is not accessible."), category="error") + return redirect(url_for("index")) @app.route("/download//") @login_required_if_no_ano From aae9e285a83e7f1a829431966a283adfbb31e2f6 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Mon, 20 Nov 2017 21:54:55 +0100 Subject: [PATCH 06/19] Code cosmetics --- cps/static/js/edit_books.js | 2 +- cps/static/js/kthoom.js | 203 +++++++++++++++++------------------- 2 files changed, 98 insertions(+), 107 deletions(-) diff --git a/cps/static/js/edit_books.js b/cps/static/js/edit_books.js index 96fcc848..f4b42e2c 100644 --- a/cps/static/js/edit_books.js +++ b/cps/static/js/edit_books.js @@ -46,7 +46,7 @@ function prefixedSource(prefix, query, cb, bhAdapter) { function getPath() { var jsFileLocation = $("script[src*=edit_books]").attr("src"); // the js file path - return jsFileLocation.substr(0,jsFileLocation.search("/static/js/edit_books.js")); // the js folder path + return jsFileLocation.substr(0, jsFileLocation.search("/static/js/edit_books.js")); // the js folder path } var authors = new Bloodhound({ diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index 032f5220..5fd8a06a 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -15,9 +15,8 @@ * Typed Arrays: http://www.khronos.org/registry/typedarray/specs/latest/#6 */ -/* global bitjs */ -var start=0; +var start = 0; if (window.opera) { window.console.log = function(str) { @@ -56,7 +55,7 @@ kthoom.Key = { }; // global variables -var unarchiver = null; +// var unarchiver = null; var currentImage = 0; var imageFiles = []; var imageFilenames = []; @@ -68,10 +67,10 @@ var settings = { vflip: false, rotateTimes: 0, fitMode: kthoom.Key.B, - theme: 'light' + theme: "light" }; -var canKeyNext = true, canKeyPrev = true; +// var canKeyNext = true, canKeyPrev = true; kthoom.saveSettings = function() { localStorage.kthoomSettings = JSON.stringify(settings); @@ -79,7 +78,7 @@ kthoom.saveSettings = function() { kthoom.loadSettings = function() { try { - if (!localStorage.kthoomSettings){ + if (!localStorage.kthoomSettings) { return; } @@ -95,14 +94,14 @@ kthoom.setSettings = function() { // Set settings control values $.each(settings, function(key, value) { if (typeof value === "boolean") { - $('input[name='+key+']').prop('checked', value); + $("input[name=" + key + "]").prop("checked", value); } else { - $('input[name='+key+']').val([value]); + $("input[name=" + key + "]").val([value]); } }); }; -var createURLFromArray = function(array, mimeType) { +/* var createURLFromArray = function(array, mimeType) { var offset = array.byteOffset, len = array.byteLength; var url; var blob; @@ -129,20 +128,13 @@ var createURLFromArray = function(array, mimeType) { } return URL.createObjectURL(blob); -}; +};*/ // Stores an image filename and its data: URI. -// TODO: investigate if we really need to store as base64 (leave off ;base64 and just -// non-safe URL characters are encoded as %xx ?) -// This would save 25% on memory since base64-encoded strings are 4/3 the size of the binary kthoom.ImageFile = function(file) { this.filename = file.filename; - /*var fileExtension = file.filename.split(".").pop().toLowerCase(); - var mimeType = fileExtension === "png" ? "image/png" : - (fileExtension === "jpg" || fileExtension === "jpeg") ? "image/jpeg" : - fileExtension === "gif" ? "image/gif" : null;*/ - this.dataURI = file.fileData; // createURLFromArray(file.fileData, mimeType); + this.dataURI = file.fileData; this.data = file; }; @@ -231,14 +223,14 @@ kthoom.initProgressMeter = function() { svg.appendChild(g); pdiv.appendChild(svg); - var l; + var l = 0; svg.onclick = function(e) { - for (var x = pdiv, l = 0; x !== document.documentElement; x = x.parentNode) l += x.offsetLeft; + for (var x = pdiv; x !== document.documentElement; x = x.parentNode) l += x.offsetLeft; var page = Math.max(1, Math.ceil(((e.clientX - l) / pdiv.offsetWidth) * totalImages)) - 1; currentImage = page; updatePage(); }; -} +}; kthoom.setProgressMeter = function(pct, optLabel) { pct = (pct * 100); @@ -281,26 +273,26 @@ kthoom.setProgressMeter = function(pct, optLabel) { } function loadFromArrayBuffer(ab) { - var f=[]; - f.fileData=ab.content; - f.filename=ab.name; + var f = []; + f.fileData = ab.content; + f.filename = ab.name; // add any new pages based on the filename if (imageFilenames.indexOf(f.filename) === -1) { imageFilenames.push(f.filename); imageFiles.push(new kthoom.ImageFile(f)); // add thumbnails to the TOC list - $('#thumbnails').append( - "
  • \ - \ - \ - "+ imageFiles.length +" \ - \ -
  • " + $("#thumbnails").append( + "
  • " + + "" + + "" + + "" + imageFiles.length + "" + + "" + + "
  • " ); } - var percentage = (ab.page+1) / (ab.last+1); - totalImages = ab.last+1; + var percentage = (ab.page + 1) / (ab.last + 1); + totalImages = ab.last + 1; kthoom.setProgressMeter(percentage, "Unzipping"); lastCompletion = percentage * 100; @@ -324,7 +316,7 @@ function updatePage() { setImage("loading"); } - $('body').toggleClass('dark-theme', settings.theme === 'dark'); + $("body").toggleClass("dark-theme", settings.theme === "dark"); kthoom.setSettings(); kthoom.saveSettings(); @@ -339,20 +331,20 @@ function setImage(url) { canvas.width = innerWidth - 100; canvas.height = 200; x.fillStyle = "black"; - x.textAlign="center"; + x.textAlign = "center"; x.font = "24px sans-serif"; x.strokeStyle = "black"; - x.fillText("Loading Page #" + (currentImage + 1), innerWidth/2, 100); + x.fillText("Loading Page #" + (currentImage + 1), innerWidth / 2, 100); } else { if (url === "error") { updateScale(true); canvas.width = innerWidth - 100; canvas.height = 200; x.fillStyle = "black"; - x.textAlign="center"; + x.textAlign = "center"; x.font = "24px sans-serif"; x.strokeStyle = "black"; - x.fillText("Unable to decompress image #" + (currentImage + 1), innerWidth/2, 100); + x.fillText("Unable to decompress image #" + (currentImage + 1), innerWidth / 2, 100); } else { if ($("body").css("scrollHeight") / innerHeight > 1) { $("body").css("overflowY", "scroll"); @@ -367,15 +359,14 @@ function setImage(url) { x.font = "50px sans-serif"; x.strokeStyle = "black"; x.fillText("Page #" + (currentImage + 1) + " (" + - imageFiles[currentImage].filename + ")", innerWidth/2, 100); + imageFiles[currentImage].filename + ")", innerWidth / 2, 100); x.fillStyle = "black"; - x.fillText("Is corrupt or not an image", innerWidth/2, 200); + x.fillText("Is corrupt or not an image", innerWidth / 2, 200); var xhr = new XMLHttpRequest(); if (/(html|htm)$/.test(imageFiles[currentImage].filename)) { xhr.open("GET", url, true); xhr.onload = function() { - //document.getElementById('mainText').style.display = ''; $("#mainText").css("display", ""); $("#mainText").innerHTML(""); } @@ -465,7 +456,7 @@ function updateScale(clear) { } else if (settings.fitMode === kthoom.Key.W) { mainImageStyle.width = "100%"; } - $('#mainContent').css({maxHeight: maxheight + 5}); + $("#mainContent").css({maxHeight: maxheight + 5}); kthoom.setSettings(); kthoom.saveSettings(); } @@ -473,11 +464,11 @@ function updateScale(clear) { function keyHandler(evt) { var code = evt.keyCode; - if ($("#progress").css("display") === "none"){ + if ($("#progress").css("display") === "none") { return; } - canKeyNext = (($("body").css("offsetWidth") + $("body").css("scrollLeft")) / $("body").css("scrollWidth")) >= 1; - canKeyPrev = (scrollX <= 0); + // canKeyNext = (($("body").css("offsetWidth") + $("body").css("scrollLeft")) / $("body").css("scrollWidth")) >= 1; + // canKeyPrev = (scrollX <= 0); if (evt.ctrlKey || evt.shiftKey || evt.metaKey) return; switch (code) { @@ -540,7 +531,7 @@ function keyHandler(evt) { function ImageLoadCallback(event) { var jso=this.response; // Unable to decompress file, or no response from server - if (jso === null){ + if (jso === null) { setImage("error"); } else @@ -554,8 +545,8 @@ function ImageLoadCallback(event) { } else { - var diff = ((new Date).getTime() - start)/1000; - console.log('Transfer done in ' + diff + 's'); + var diff = ((new Date).getTime() - start) / 1000; + console.log("Transfer done in " + diff + "s"); } loadFromArrayBuffer(jso); } @@ -571,64 +562,64 @@ function init(fileid) { kthoom.initProgressMeter(); document.body.className += /AppleWebKit/.test(navigator.userAgent) ? " webkit" : ""; kthoom.loadSettings(); - updateScale(true); + updateScale(true); $(document).keydown(keyHandler); $(window).resize(function() { updateScale(); }); - // Open TOC menu - $("#slider").click(function(evt) { - $('#sidebar').toggleClass('open'); - $('#main').toggleClass('closed'); - $(this).toggleClass('icon-menu icon-right'); - }); + // Open TOC menu + $("#slider").click(function(evt) { + $("#sidebar").toggleClass("open"); + $("#main").toggleClass("closed"); + $(this).toggleClass("icon-menu icon-right"); + }); - // Open Settings modal - $("#setting").click(function(evt) { - $("#settings-modal").toggleClass('md-show'); - }); + // Open Settings modal + $("#setting").click(function(evt) { + $("#settings-modal").toggleClass("md-show"); + }); - // On Settings input change - $("#settings input").on("change", function(evt){ - // Get either the checked boolean or the assigned value - var value = this.type === 'checkbox' ? this.checked : this.value; + // On Settings input change + $("#settings input").on("change", function(evt) { + // Get either the checked boolean or the assigned value + var value = this.type === "checkbox" ? this.checked : this.value; - // If it's purely numeric, parse it to an integer - value = /^\d+$/.test(value) ? parseInt(value) : value; - - settings[this.name] = value; - updatePage(); - updateScale(); - }); + // If it's purely numeric, parse it to an integer + value = /^\d+$/.test(value) ? parseInt(value) : value; - // Close modal - $(".closer, .overlay").click(function(evt) { - $(".md-show").removeClass('md-show'); - }); + settings[this.name] = value; + updatePage(); + updateScale(); + }); - // TOC thumbnail pagination - $('#thumbnails').on("click", "a", function(evt) { - currentImage = $(this).data('page') - 1; - updatePage(); + // Close modal + $(".closer, .overlay").click(function(evt) { + $(".md-show").removeClass("md-show"); + }); + + // TOC thumbnail pagination + $("#thumbnails").on("click", "a", function(evt) { + currentImage = $(this).data("page") - 1; + updatePage(); + }); + + // Fullscreen mode + if (typeof screenfull !== "undefined") { + $('#fullscreen').click(function(evt) { + screenfull.toggle($("#container")[0]) }); - // Fullscreen mode - if (typeof screenfull !== "undefined") { - $('#fullscreen').click(function(evt) { - screenfull.toggle($("#container")[0]) + if (screenfull.raw) { + var $button = $("#fullscreen"); + document.addEventListener(screenfull.raw.fullscreenchange,function() { + screenfull.isFullscreen + ? $button.addClass("icon-resize-small").removeClass("icon-resize-full") + : $button.addClass("icon-resize-full").removeClass("icon-resize-small") }); - - if (screenfull.raw) { - var $button = $('#fullscreen'); - document.addEventListener(screenfull.raw.fullscreenchange,function(){ - screenfull.isFullscreen - ? $button.addClass("icon-resize-small").removeClass("icon-resize-full") - : $button.addClass("icon-resize-full").removeClass("icon-resize-small") - }); - } } + } $("#mainImage").click(function(evt) { // Firefox does not support offsetX/Y so we have to manually calculate @@ -639,25 +630,25 @@ function init(fileid) { var comicHeight = evt.target.clientHeight; var offsetX = (mainContentWidth - comicWidth) / 2; var offsetY = (mainContentHeight - comicHeight) / 2; - var clickX = !!evt.offsetX ? evt.offsetX : (evt.clientX - offsetX); - var clickY = !!evt.offsetY ? evt.offsetY : (evt.clientY - offsetY); + var clickX = evt.offsetX ? evt.offsetX : (evt.clientX - offsetX); + var clickY = evt.offsetY ? evt.offsetY : (evt.clientY - offsetY); // Determine if the user clicked/tapped the left side or the // right side of the page. var clickedPrev = false; - switch (settings.rotateTimes) { - case 0: - clickedPrev = clickX < (comicWidth / 2); - break; - case 1: - clickedPrev = clickY < (comicHeight / 2); - break; - case 2: - clickedPrev = clickX > (comicWidth / 2); - break; - case 3: - clickedPrev = clickY > (comicHeight / 2); - break; + switch (settings.rotateTimes) { + case 0: + clickedPrev = clickX < (comicWidth / 2); + break; + case 1: + clickedPrev = clickY < (comicHeight / 2); + break; + case 2: + clickedPrev = clickX > (comicWidth / 2); + break; + case 3: + clickedPrev = clickY > (comicHeight / 2); + break; } if (clickedPrev) { showPrevPage(); From 0943d508763fce1f78a652a085af8d4d411ac57b Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Tue, 21 Nov 2017 12:46:03 +0100 Subject: [PATCH 07/19] Code cosmetics Bugfix click on progressbar --- cps/static/js/kthoom.js | 92 +++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index 5fd8a06a..28d6ecd2 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -15,6 +15,7 @@ * Typed Arrays: http://www.khronos.org/registry/typedarray/specs/latest/#6 */ +/* global screenfull */ var start = 0; @@ -36,7 +37,7 @@ function getElem(id) { return document.getElementById(id); } -if (window.kthoom === undefined) { +if (typeof window.kthoom === "undefined" ) { kthoom = {}; } @@ -223,8 +224,8 @@ kthoom.initProgressMeter = function() { svg.appendChild(g); pdiv.appendChild(svg); - var l = 0; svg.onclick = function(e) { + var l = 0; for (var x = pdiv; x !== document.documentElement; x = x.parentNode) l += x.offsetLeft; var page = Math.max(1, Math.ceil(((e.clientX - l) / pdiv.offsetWidth) * totalImages)) - 1; currentImage = page; @@ -300,7 +301,7 @@ function loadFromArrayBuffer(ab) { if (imageFiles.length === currentImage + 1) { updatePage(); } -} +}; function updatePage() { @@ -408,7 +409,7 @@ function setImage(url) { scrollTo(0, 0); x.drawImage(img, 0, 0); - updateScale(); + updateScale(false); canvas.style.display = ""; $("body").css("overflowY", ""); @@ -447,14 +448,21 @@ function updateScale(clear) { mainImageStyle.maxHeight = ""; var maxheight = innerHeight - 50; - if (clear || settings.fitMode === kthoom.Key.N) { - } else if (settings.fitMode === kthoom.Key.B) { - mainImageStyle.maxWidth = "100%"; - mainImageStyle.maxHeight = maxheight + "px"; - } else if (settings.fitMode === kthoom.Key.H) { - mainImageStyle.height = maxheight + "px"; - } else if (settings.fitMode === kthoom.Key.W) { - mainImageStyle.width = "100%"; + if (!clear) { + switch(settings.fitMode) { + case kthoom.Key.B: + mainImageStyle.maxWidth = "100%"; + mainImageStyle.maxHeight = maxheight + "px"; + break; + case kthoom.Key.H: + mainImageStyle.height = maxheight + "px"; + break; + case kthoom.Key.W: + mainImageStyle.width = "100%"; + break; + default: + break; + } } $("#mainContent").css({maxHeight: maxheight + 5}); kthoom.setSettings(); @@ -508,19 +516,19 @@ function keyHandler(evt) { break; case kthoom.Key.W: settings.fitMode = kthoom.Key.W; - updateScale(); + updateScale(false); break; case kthoom.Key.H: settings.fitMode = kthoom.Key.H; - updateScale(); + updateScale(false); break; case kthoom.Key.B: settings.fitMode = kthoom.Key.B; - updateScale(); + updateScale(false); break; case kthoom.Key.N: settings.fitMode = kthoom.Key.N; - updateScale(); + updateScale(false); break; default: //console.log('KeyCode = ' + code); @@ -528,26 +536,22 @@ function keyHandler(evt) { } } -function ImageLoadCallback(event) { - var jso=this.response; +function ImageLoadCallback() { + var jso = this.response; // Unable to decompress file, or no response from server if (jso === null) { setImage("error"); - } - else - { - if (jso.page !== jso.last) - { - // var secRequest = new XMLHttpRequest(); - this.open("GET", this.fileid + "/"+(jso.page+1)); - this.addEventListener("load",ImageLoadCallback); + } else { + if (jso.page !== jso.last) { + this.open("GET", this.fileid + "/" + (jso.page + 1)); + this.addEventListener("load", ImageLoadCallback); this.send(); } - else + /*else { var diff = ((new Date).getTime() - start) / 1000; console.log("Transfer done in " + diff + "s"); - } + }*/ loadFromArrayBuffer(jso); } } @@ -556,8 +560,8 @@ function init(fileid) { var request = new XMLHttpRequest(); request.open("GET", fileid); request.responseType = "json"; - request.fileid=fileid.substring(0,fileid.length - 2); - request.addEventListener("load",ImageLoadCallback); + request.fileid = fileid.substring(0, fileid.length - 2); + request.addEventListener("load", ImageLoadCallback); request.send(); kthoom.initProgressMeter(); document.body.className += /AppleWebKit/.test(navigator.userAgent) ? " webkit" : ""; @@ -566,23 +570,23 @@ function init(fileid) { $(document).keydown(keyHandler); $(window).resize(function() { - updateScale(); + updateScale(false); }); // Open TOC menu - $("#slider").click(function(evt) { + $("#slider").click(function() { $("#sidebar").toggleClass("open"); $("#main").toggleClass("closed"); $(this).toggleClass("icon-menu icon-right"); }); // Open Settings modal - $("#setting").click(function(evt) { + $("#setting").click(function() { $("#settings-modal").toggleClass("md-show"); }); // On Settings input change - $("#settings input").on("change", function(evt) { + $("#settings input").on("change", function() { // Get either the checked boolean or the assigned value var value = this.type === "checkbox" ? this.checked : this.value; @@ -591,32 +595,32 @@ function init(fileid) { settings[this.name] = value; updatePage(); - updateScale(); + updateScale(false); }); // Close modal - $(".closer, .overlay").click(function(evt) { + $(".closer, .overlay").click(function() { $(".md-show").removeClass("md-show"); }); // TOC thumbnail pagination - $("#thumbnails").on("click", "a", function(evt) { + $("#thumbnails").on("click", "a", function() { currentImage = $(this).data("page") - 1; updatePage(); }); // Fullscreen mode if (typeof screenfull !== "undefined") { - $('#fullscreen').click(function(evt) { - screenfull.toggle($("#container")[0]) + $("#fullscreen").click(function(evt) { + screenfull.toggle($("#container")[0]); }); if (screenfull.raw) { var $button = $("#fullscreen"); - document.addEventListener(screenfull.raw.fullscreenchange,function() { + document.addEventListener(screenfull.raw.fullscreenchange, function() { screenfull.isFullscreen - ? $button.addClass("icon-resize-small").removeClass("icon-resize-full") - : $button.addClass("icon-resize-full").removeClass("icon-resize-small") + ? $button.addClass("icon-resize-small").removeClass("icon-resize-full") + : $button.addClass("icon-resize-full").removeClass("icon-resize-small"); }); } } @@ -630,8 +634,8 @@ function init(fileid) { var comicHeight = evt.target.clientHeight; var offsetX = (mainContentWidth - comicWidth) / 2; var offsetY = (mainContentHeight - comicHeight) / 2; - var clickX = evt.offsetX ? evt.offsetX : (evt.clientX - offsetX); - var clickY = evt.offsetY ? evt.offsetY : (evt.clientY - offsetY); + var clickX = !!evt.offsetX ? evt.offsetX : (evt.clientX - offsetX); + var clickY = !!evt.offsetY ? evt.offsetY : (evt.clientY - offsetY); // Determine if the user clicked/tapped the left side or the // right side of the page. From 03ab03f06ef1aa695037aa736e5a4ca849a00f73 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Wed, 22 Nov 2017 22:08:29 +0100 Subject: [PATCH 08/19] Code cosmetics --- cps/static/js/kthoom.js | 16 ++++++++-------- cps/web.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index 28d6ecd2..2f82503f 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -17,7 +17,7 @@ */ /* global screenfull */ -var start = 0; +// var start = 0; if (window.opera) { window.console.log = function(str) { @@ -271,7 +271,7 @@ kthoom.setProgressMeter = function(pct, optLabel) { //getElem('nav').className = ''; getElem("progress").className = ""; } -} +}; function loadFromArrayBuffer(ab) { var f = []; @@ -301,7 +301,7 @@ function loadFromArrayBuffer(ab) { if (imageFiles.length === currentImage + 1) { updatePage(); } -}; +} function updatePage() { @@ -449,7 +449,7 @@ function updateScale(clear) { var maxheight = innerHeight - 50; if (!clear) { - switch(settings.fitMode) { + switch (settings.fitMode) { case kthoom.Key.B: mainImageStyle.maxWidth = "100%"; mainImageStyle.maxHeight = maxheight + "px"; @@ -556,7 +556,7 @@ function ImageLoadCallback() { } } function init(fileid) { - start = (new Date).getTime(); + // start = (new Date).getTime(); var request = new XMLHttpRequest(); request.open("GET", fileid); request.responseType = "json"; @@ -611,7 +611,7 @@ function init(fileid) { // Fullscreen mode if (typeof screenfull !== "undefined") { - $("#fullscreen").click(function(evt) { + $("#fullscreen").click(function() { screenfull.toggle($("#container")[0]); }); @@ -634,8 +634,8 @@ function init(fileid) { var comicHeight = evt.target.clientHeight; var offsetX = (mainContentWidth - comicWidth) / 2; var offsetY = (mainContentHeight - comicHeight) / 2; - var clickX = !!evt.offsetX ? evt.offsetX : (evt.clientX - offsetX); - var clickY = !!evt.offsetY ? evt.offsetY : (evt.clientY - offsetY); + var clickX = evt.offsetX ? evt.offsetX : (evt.clientX - offsetX); + var clickY = evt.offsetY ? evt.offsetY : (evt.clientY - offsetY); // Determine if the user clicked/tapped the left side or the // right side of the page. diff --git a/cps/web.py b/cps/web.py index 4ae940b3..8c5fff61 100755 --- a/cps/web.py +++ b/cps/web.py @@ -838,7 +838,7 @@ def feed_series(book_id): off = request.args.get("offset") if not off: off = 0 - entries, random, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), + entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), db.Books, db.Books.series.any(db.Series.id == book_id),db.Books.series_index) xml = render_title_template('feed.xml', entries=entries, pagination=pagination) response = make_response(xml) From f890fc155332cc1911cae2f1e3efd9142a24e697 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Wed, 29 Nov 2017 15:54:17 +0100 Subject: [PATCH 09/19] New progress bar --- cps/helper.py | 3 +- cps/static/css/kthoom.css | 37 ++++++++ cps/static/js/kthoom.js | 189 +++++-------------------------------- cps/templates/readcbr.html | 16 ++-- cps/web.py | 2 +- 5 files changed, 70 insertions(+), 177 deletions(-) diff --git a/cps/helper.py b/cps/helper.py index 60c9e5fc..3209d4c7 100755 --- a/cps/helper.py +++ b/cps/helper.py @@ -281,7 +281,8 @@ def get_valid_filename(value, replace_whitespace=True): if replace_whitespace: #*+:\"/<>? are replaced by _ value = re.sub(r'[\*\+:\\\"/<>\?]+', u'_', value, flags=re.U) - + # pipe has to be replaced with comma + value = re.sub(r'[\|]+', u',', value, flags=re.U) value = value[:128] if not value: raise ValueError("Filename cannot be empty") diff --git a/cps/static/css/kthoom.css b/cps/static/css/kthoom.css index 061d171b..7e175de9 100644 --- a/cps/static/css/kthoom.css +++ b/cps/static/css/kthoom.css @@ -179,3 +179,40 @@ th { .dark-theme .overlay { background-color: rgba(0,0,0,0.8); } + +.view { + padding-top:0px; +} + +#Progress { + margin: auto; + width: 80%; + background-color: #ddd; + display: inline-block; + margin-top: 5px; +} + +#meter { + width: 0%; + height: 5px; + background-color: #1C5484; +} + +#meter2 { + width: 0%; + height: 15px; + background-color: #028138; +} + +#page { + text-align:left; + margin-top:-20px; + font-size:80%; + width: 0%; +} + +#progress_title { + text-align:right; + margin-top:-15px; + font-size:80%; +} diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index 2f82503f..63b12ca6 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -56,12 +56,10 @@ kthoom.Key = { }; // global variables -// var unarchiver = null; var currentImage = 0; var imageFiles = []; var imageFilenames = []; var totalImages = 0; -var lastCompletion = 0; var settings = { hflip: false, @@ -71,8 +69,6 @@ var settings = { theme: "light" }; -// var canKeyNext = true, canKeyPrev = true; - kthoom.saveSettings = function() { localStorage.kthoomSettings = JSON.stringify(settings); }; @@ -102,35 +98,6 @@ kthoom.setSettings = function() { }); }; -/* var createURLFromArray = function(array, mimeType) { - var offset = array.byteOffset, len = array.byteLength; - var url; - var blob; - - // TODO: Move all this browser support testing to a common place - // and do it just once. - - // Blob constructor, see http://dev.w3.org/2006/webapi/FileAPI/#dfn-Blob. - if (typeof Blob === "function") { - blob = new Blob([array], {type: mimeType}); - } else { - throw "Browser support for Blobs is missing."; - } - - if (blob.slice) { - blob = blob.slice(offset, offset + len, mimeType); - } else { - throw "Browser support for Blobs is missing."; - } - - if ((typeof URL !== "function" && typeof URL !== "object") || - typeof URL.createObjectURL !== "function") { - throw "Browser support for Object URLs is missing"; - } - - return URL.createObjectURL(blob); -};*/ - // Stores an image filename and its data: URI. kthoom.ImageFile = function(file) { @@ -141,136 +108,32 @@ kthoom.ImageFile = function(file) { kthoom.initProgressMeter = function() { - var svgns = "http://www.w3.org/2000/svg"; - var pdiv = $("#progress")[0]; - var svg = document.createElementNS(svgns, "svg"); - svg.style.width = "100%"; - svg.style.height = "100%"; - - var defs = document.createElementNS(svgns, "defs"); - - var patt = document.createElementNS(svgns, "pattern"); - patt.id = "progress_pattern"; - patt.setAttribute("width", "30"); - patt.setAttribute("height", "20"); - patt.setAttribute("patternUnits", "userSpaceOnUse"); - - var rect = document.createElementNS(svgns, "rect"); - rect.setAttribute("width", "100%"); - rect.setAttribute("height", "100%"); - rect.setAttribute("fill", "#cc2929"); - - var poly = document.createElementNS(svgns, "polygon"); - poly.setAttribute("fill", "yellow"); - poly.setAttribute("points", "15,0 30,0 15,20 0,20"); - - patt.appendChild(rect); - patt.appendChild(poly); - defs.appendChild(patt); - - svg.appendChild(defs); - - var g = document.createElementNS(svgns, "g"); - - var outline = document.createElementNS(svgns, "rect"); - outline.setAttribute("y", "1"); - outline.setAttribute("width", "100%"); - outline.setAttribute("height", "15"); - outline.setAttribute("fill", "#777"); - outline.setAttribute("stroke", "white"); - outline.setAttribute("rx", "5"); - outline.setAttribute("ry", "5"); - g.appendChild(outline); - - var title = document.createElementNS(svgns, "text"); - title.id = "progress_title"; - title.appendChild(document.createTextNode("0%")); - title.setAttribute("y", "13"); - title.setAttribute("x", "99.5%"); - title.setAttribute("fill", "white"); - title.setAttribute("font-size", "12px"); - title.setAttribute("text-anchor", "end"); - g.appendChild(title); - - var meter = document.createElementNS(svgns, "rect"); - meter.id = "meter"; - meter.setAttribute("width", "0%"); - meter.setAttribute("height", "17"); - meter.setAttribute("fill", "url(#progress_pattern)"); - meter.setAttribute("rx", "5"); - meter.setAttribute("ry", "5"); - - var meter2 = document.createElementNS(svgns, "rect"); - meter2.id = "meter2"; - meter2.setAttribute("width", "0%"); - meter2.setAttribute("height", "17"); - meter2.setAttribute("opacity", "0.8"); - meter2.setAttribute("fill", "#007fff"); - meter2.setAttribute("rx", "5"); - meter2.setAttribute("ry", "5"); - - g.appendChild(meter); - g.appendChild(meter2); - - var page = document.createElementNS(svgns, "text"); - page.id = "page"; - page.appendChild(document.createTextNode("0/0")); - page.setAttribute("y", "13"); - page.setAttribute("x", "0.5%"); - page.setAttribute("fill", "white"); - page.setAttribute("font-size", "12px"); - g.appendChild(page); - - - svg.appendChild(g); - pdiv.appendChild(svg); - svg.onclick = function(e) { - var l = 0; - for (var x = pdiv; x !== document.documentElement; x = x.parentNode) l += x.offsetLeft; - var page = Math.max(1, Math.ceil(((e.clientX - l) / pdiv.offsetWidth) * totalImages)) - 1; + $("#Progress").removeClass("hide"); + $("#Progress").click(function(e) { + var page = Math.max(1, Math.ceil((e.offsetX / $(this).width()) * totalImages)) - 1; currentImage = page; updatePage(); - }; + }); }; -kthoom.setProgressMeter = function(pct, optLabel) { - pct = (pct * 100); - var part = 1 / totalImages; - var remain = ((pct - lastCompletion) / 100) / part; - var fract = Math.min(1, remain); - var smartpct = ((imageFiles.length / totalImages) + (fract * part)) * 100; - if (totalImages === 0) smartpct = pct; - - // + Math.min((pct - lastCompletion), 100/totalImages * 0.9 + (pct - lastCompletion - 100/totalImages)/2, 100/totalImages); - var oldval = parseFloat(getElem("meter").getAttribute("width")); - if (isNaN(oldval)) oldval = 0; - var weight = 0.5; - smartpct = ((weight * smartpct) + ((1 - weight) * oldval)); - if (pct === 100) smartpct = 100; - - if (!isNaN(smartpct)) { - getElem("meter").setAttribute("width", smartpct + "%"); +kthoom.setProgressMeter = function(optLabel) { + var pct = imageFiles.length / totalImages * 100; + if (pct === 100) { + //smartpct = 100; + getElem("progress_title").innerHTML = "Complete"; + } else { + var labelText = pct.toFixed(2) + "% " + imageFiles.length + "/" + totalImages + ""; + if (optLabel) { + labelText = optLabel + " " + labelText; + } + getElem("progress_title").innerHTML=labelText; } - var title = getElem("progress_title"); - while (title.firstChild) title.removeChild(title.firstChild); - - var labelText = pct.toFixed(2) + "% " + imageFiles.length + "/" + totalImages + ""; - if (optLabel) { - labelText = optLabel + " " + labelText; + if (!isNaN(pct)) { + getElem("meter").style.width = pct + "%"; } - title.appendChild(document.createTextNode(labelText)); - - getElem("meter2").setAttribute("width", - 100 * (totalImages === 0 ? 0 : ((currentImage + 1) / totalImages)) + "%"); - - var titlePage = getElem("page"); - while (titlePage.firstChild) titlePage.removeChild(titlePage.firstChild); - titlePage.appendChild(document.createTextNode( (currentImage + 1) + "/" + totalImages )); - if (pct > 0) { - //getElem('nav').className = ''; - getElem("progress").className = ""; - } + getElem("meter2").style.width= 100 * (totalImages === 0 ? 0 : ((currentImage + 1) / totalImages)) + "%"; + getElem("page").innerHTML=(currentImage + 1) + "/" + totalImages ; }; function loadFromArrayBuffer(ab) { @@ -292,10 +155,10 @@ function loadFromArrayBuffer(ab) { "" ); } - var percentage = (ab.page + 1) / (ab.last + 1); + // var percentage = (ab.page + 1) / (ab.last + 1); totalImages = ab.last + 1; - kthoom.setProgressMeter(percentage, "Unzipping"); - lastCompletion = percentage * 100; + kthoom.setProgressMeter("Unzipping"); + // lastCompletion = percentage * 100; // display first page if we haven't yet if (imageFiles.length === currentImage + 1) { @@ -305,12 +168,8 @@ function loadFromArrayBuffer(ab) { function updatePage() { - var title = getElem("page"); - while (title.firstChild) title.removeChild(title.firstChild); - title.appendChild(document.createTextNode( (currentImage + 1 ) + "/" + totalImages )); - - getElem("meter2").setAttribute("width", - 100 * (totalImages === 0 ? 0 : ((currentImage + 1 ) / totalImages)) + "%"); + getElem("page").innerHTML=(currentImage + 1) + "/" + totalImages ; + getElem("meter2").style.width= 100 * (totalImages === 0 ? 0 : ((currentImage + 1) / totalImages)) + "%"; if (imageFiles[currentImage]) { setImage(imageFiles[currentImage].dataURI); } else { diff --git a/cps/templates/readcbr.html b/cps/templates/readcbr.html index 59296a7a..5eefa5df 100644 --- a/cps/templates/readcbr.html +++ b/cps/templates/readcbr.html @@ -25,10 +25,10 @@ +
    +
    +
    +
    - -
    -
    diff --git a/cps/web.py b/cps/web.py index 8c5fff61..f823ec30 100755 --- a/cps/web.py +++ b/cps/web.py @@ -608,7 +608,7 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session # if no element is found add it if new_element is None: if db_type == 'author': - new_element = db_object(add_element, add_element, "") + new_element = db_object(add_element, add_element.replace('|',','), "") elif db_type == 'series': new_element = db_object(add_element, add_element) elif db_type == 'custom': From 4e1127202351cef1518ab382eddbd3d02e84ac47 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Fri, 1 Dec 2017 10:10:42 +0100 Subject: [PATCH 10/19] Working on IE11 --- cps/static/js/kthoom.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index 63b12ca6..1754c460 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -138,6 +138,9 @@ kthoom.setProgressMeter = function(optLabel) { function loadFromArrayBuffer(ab) { var f = []; + if (typeof ab !== "object") { + ab = JSON.parse(ab); + } f.fileData = ab.content; f.filename = ab.name; // add any new pages based on the filename From 2fe03961bb676bd52170c2ee66628db536634141 Mon Sep 17 00:00:00 2001 From: Yury Tilis Date: Sun, 10 Dec 2017 02:43:51 -0500 Subject: [PATCH 11/19] -Added a more subtle progress bar which only shows the page number on hover -Added Current page indicator to the thumbnail list, as well as a hover state -Thumbnail list now scrolls to the current page on open or page change -Added @andy29485's request for controlling page with space/shift+space -Added the book title to the top of the page -Fixed IE11 failing on imagecallback --- cps/static/css/kthoom.css | 136 +++++++++++------- cps/static/js/kthoom.js | 154 +++++++++++--------- cps/templates/readcbr.html | 278 +++++++++++++++++++------------------ cps/web.py | 2 +- 4 files changed, 322 insertions(+), 248 deletions(-) diff --git a/cps/static/css/kthoom.css b/cps/static/css/kthoom.css index 7e175de9..770b94a2 100644 --- a/cps/static/css/kthoom.css +++ b/cps/static/css/kthoom.css @@ -1,17 +1,21 @@ body { - background: #444; + background: #444; overflow-x: hidden; overflow-y: auto; - color: white; - font-family: sans-serif; - margin: 0px; + color: white; + font-family: sans-serif; + margin: 0px; } #main { - text-align: center; + text-align: center; z-index: 2; } +.view { + padding-top:0px; +} + #sidebar a, #sidebar ul, #sidebar li, @@ -20,10 +24,28 @@ body { text-align: center; } +#sidebar ul { + position: relative; +} + #sidebar a { display: inline-block; position: relative; cursor: pointer; + padding: 4px; + + transition: all .2s ease; +} + +#sidebar a:hover, +#sidebar a:focus { + outline: none; + box-shadow: 0px 2px 8px 1px black; +} + +#sidebar a.active, +#sidebar a.active img + span { + background-color: #45B29D; } #sidebar li img { @@ -47,21 +69,65 @@ body { } #progress { - position: absolute; - display: inline; - left: 20%; - right: 20%; - height: 20px; - margin-top: 4px; - text-align: right; + position: absolute; + display: inline; + top: 0; + left: 0; + right: 0; + min-height: 4px; + font-family: sans-serif; + font-size: 10px; + line-height: 10px; + text-align: right; + + transition: min-height 150ms ease-in-out; +} + +#progress .bar-load, +#progress .bar-read { + display: flex; + align-items: flex-end; + justify-content: flex-end; + position: absolute; + top: 0; + left: 0; + bottom: 0; + + transition: width 150ms ease-in-out; +} + +#progress .bar-load { + color: #000; + background-color: #CCC; +} + +#progress .bar-read { + color: #FFF; + background-color: #45B29D; +} + +#progress .text { + display: none; + padding: 0 5px; +} + +#progress.loading, +#titlebar:hover #progress { + min-height: 10px; +} + +#progress.loading .text, +#titlebar:hover #progress .text { + display: inline-block; } .hide { - display: none !important; + display: none !important; } #mainContent { overflow: auto; + outline: none; } #mainText { @@ -82,6 +148,10 @@ body { height: auto; } +#metainfo { + max-width: 70%; +} + #prev { left: 40px; } @@ -176,43 +246,11 @@ th { color: #FFF; } -.dark-theme .overlay { - background-color: rgba(0,0,0,0.8); +.dark-theme #progress .bar-read { + background-color: red; } -.view { - padding-top:0px; -} - -#Progress { - margin: auto; - width: 80%; - background-color: #ddd; - display: inline-block; - margin-top: 5px; -} - -#meter { - width: 0%; - height: 5px; - background-color: #1C5484; -} -#meter2 { - width: 0%; - height: 15px; - background-color: #028138; -} - -#page { - text-align:left; - margin-top:-20px; - font-size:80%; - width: 0%; -} - -#progress_title { - text-align:right; - margin-top:-15px; - font-size:80%; +.dark-theme .overlay { + background-color: rgba(0,0,0,0.8); } diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index 1754c460..70958c27 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -17,8 +17,6 @@ */ /* global screenfull */ -// var start = 0; - if (window.opera) { window.console.log = function(str) { opera.postError(str); @@ -44,11 +42,12 @@ if (typeof window.kthoom === "undefined" ) { // key codes kthoom.Key = { ESCAPE: 27, + SPACE: 32, LEFT: 37, UP: 38, RIGHT: 39, - DOWN: 40, - A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, + DOWN: 40, + A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90, QUESTION_MARK: 191, LEFT_SQUARE_BRACKET: 219, @@ -62,8 +61,8 @@ var imageFilenames = []; var totalImages = 0; var settings = { - hflip: false, - vflip: false, + hflip: false, + vflip: false, rotateTimes: 0, fitMode: kthoom.Key.B, theme: "light" @@ -106,48 +105,16 @@ kthoom.ImageFile = function(file) { this.data = file; }; - -kthoom.initProgressMeter = function() { - $("#Progress").removeClass("hide"); - $("#Progress").click(function(e) { - var page = Math.max(1, Math.ceil((e.offsetX / $(this).width()) * totalImages)) - 1; - currentImage = page; - updatePage(); - }); -}; - -kthoom.setProgressMeter = function(optLabel) { - var pct = imageFiles.length / totalImages * 100; - if (pct === 100) { - //smartpct = 100; - getElem("progress_title").innerHTML = "Complete"; - } else { - var labelText = pct.toFixed(2) + "% " + imageFiles.length + "/" + totalImages + ""; - if (optLabel) { - labelText = optLabel + " " + labelText; - } - getElem("progress_title").innerHTML=labelText; - } - if (!isNaN(pct)) { - getElem("meter").style.width = pct + "%"; - } - - getElem("meter2").style.width= 100 * (totalImages === 0 ? 0 : ((currentImage + 1) / totalImages)) + "%"; - getElem("page").innerHTML=(currentImage + 1) + "/" + totalImages ; -}; - function loadFromArrayBuffer(ab) { var f = []; - if (typeof ab !== "object") { - ab = JSON.parse(ab); - } + f.fileData = ab.content; f.filename = ab.name; // add any new pages based on the filename if (imageFilenames.indexOf(f.filename) === -1) { imageFilenames.push(f.filename); imageFiles.push(new kthoom.ImageFile(f)); - + // add thumbnails to the TOC list $("#thumbnails").append( "
  • " + @@ -158,10 +125,10 @@ function loadFromArrayBuffer(ab) { "
  • " ); } - // var percentage = (ab.page + 1) / (ab.last + 1); + var percentage = ((ab.page + 1) / (ab.last + 1)) * 100; + updateProgress(percentage); + totalImages = ab.last + 1; - kthoom.setProgressMeter("Unzipping"); - // lastCompletion = percentage * 100; // display first page if we haven't yet if (imageFiles.length === currentImage + 1) { @@ -169,10 +136,28 @@ function loadFromArrayBuffer(ab) { } } +function scrollTocToActive() { + // Scroll to the thumbnail in the TOC on page change + $('#tocView').stop().animate({ + scrollTop: $('#tocView a.active').position().top + }, 200); +} function updatePage() { - getElem("page").innerHTML=(currentImage + 1) + "/" + totalImages ; - getElem("meter2").style.width= 100 * (totalImages === 0 ? 0 : ((currentImage + 1) / totalImages)) + "%"; + $('.page').text((currentImage + 1 ) + "/" + totalImages); + + // Mark the current page in the TOC + $('#tocView a[data-page]') + // Remove the currently active thumbnail + .removeClass('active') + // Find the new one + .filter('[data-page='+ (currentImage + 1) +']') + // Set it to active + .addClass('active'); + + scrollTocToActive(); + updateProgress(); + if (imageFiles[currentImage]) { setImage(imageFiles[currentImage].dataURI); } else { @@ -185,6 +170,22 @@ function updatePage() { kthoom.saveSettings(); } +function updateProgress(loadPercentage) { + // Set the load/unzip progress if it's passed in + if (loadPercentage) { + $("#progress .bar-load").css({ width: loadPercentage + "%" }); + + if (loadPercentage === 100) { + $("#progress") + .removeClass('loading') + .find(".load").text(''); + } + } + + // Set page progress bar + $("#progress .bar-read").css({ width: totalImages === 0 ? 0 : Math.round((currentImage + 1) / totalImages * 100) + "%"}); +} + function setImage(url) { var canvas = $("#mainImage")[0]; var x = $("#mainImage")[0].getContext("2d"); @@ -332,23 +333,18 @@ function updateScale(clear) { } function keyHandler(evt) { - var code = evt.keyCode; - - if ($("#progress").css("display") === "none") { - return; - } - // canKeyNext = (($("body").css("offsetWidth") + $("body").css("scrollLeft")) / $("body").css("scrollWidth")) >= 1; - // canKeyPrev = (scrollX <= 0); - - if (evt.ctrlKey || evt.shiftKey || evt.metaKey) return; - switch (code) { + var hasModifier = evt.ctrlKey || evt.shiftKey || evt.metaKey; + switch (evt.keyCode) { case kthoom.Key.LEFT: + if (hasModifier) break; showPrevPage(); break; case kthoom.Key.RIGHT: + if (hasModifier) break; showNextPage(); break; case kthoom.Key.L: + if (hasModifier) break; settings.rotateTimes--; if (settings.rotateTimes < 0) { settings.rotateTimes = 3; @@ -356,6 +352,7 @@ function keyHandler(evt) { updatePage(); break; case kthoom.Key.R: + if (hasModifier) break; settings.rotateTimes++; if (settings.rotateTimes > 3) { settings.rotateTimes = 0; @@ -363,6 +360,7 @@ function keyHandler(evt) { updatePage(); break; case kthoom.Key.F: + if (hasModifier) break; if (!settings.hflip && !settings.vflip) { settings.hflip = true; } else if (settings.hflip === true && settings.vflip === true) { @@ -377,23 +375,43 @@ function keyHandler(evt) { updatePage(); break; case kthoom.Key.W: + if (hasModifier) break; settings.fitMode = kthoom.Key.W; updateScale(false); break; case kthoom.Key.H: + if (hasModifier) break; settings.fitMode = kthoom.Key.H; updateScale(false); break; case kthoom.Key.B: + if (hasModifier) break; settings.fitMode = kthoom.Key.B; updateScale(false); break; case kthoom.Key.N: + if (hasModifier) break; settings.fitMode = kthoom.Key.N; updateScale(false); break; + case kthoom.Key.SPACE: + var container = $('#mainContent'); + var atTop = container.scrollTop() === 0; + var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height(); + + if (evt.shiftKey && atTop) { + evt.preventDefault(); + // If it's Shift + Space and the container is at the top of the page + showPrevPage(); + } else if (!evt.shiftKey && atBottom) { + evt.preventDefault(); + // If you're at the bottom of the page and you only pressed space + showNextPage(); + container.scrollTop(0); + } + break; default: - //console.log('KeyCode = ' + code); + //console.log('KeyCode', evt.keyCode); break; } } @@ -404,31 +422,31 @@ function ImageLoadCallback() { if (jso === null) { setImage("error"); } else { + // IE 11 sometimes sees the response as a string + if (typeof jso !== "object") { + jso = JSON.parse(jso); + } + if (jso.page !== jso.last) { this.open("GET", this.fileid + "/" + (jso.page + 1)); this.addEventListener("load", ImageLoadCallback); this.send(); } - /*else - { - var diff = ((new Date).getTime() - start) / 1000; - console.log("Transfer done in " + diff + "s"); - }*/ + loadFromArrayBuffer(jso); } } function init(fileid) { - // start = (new Date).getTime(); var request = new XMLHttpRequest(); request.open("GET", fileid); request.responseType = "json"; request.fileid = fileid.substring(0, fileid.length - 2); request.addEventListener("load", ImageLoadCallback); request.send(); - kthoom.initProgressMeter(); document.body.className += /AppleWebKit/.test(navigator.userAgent) ? " webkit" : ""; kthoom.loadSettings(); updateScale(true); + $(document).keydown(keyHandler); $(window).resize(function() { @@ -440,6 +458,13 @@ function init(fileid) { $("#sidebar").toggleClass("open"); $("#main").toggleClass("closed"); $(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 ¯\_(ツ)_/¯ + setTimeout(function(){ + // Focus on the TOC or the main content area, depending on which is open + $('#main:not(.closed) #mainContent, #sidebar.open #tocView').focus(); + scrollTocToActive(); + }, 500); }); // Open Settings modal @@ -487,6 +512,9 @@ function init(fileid) { } } + // Focus the scrollable area so that keyboard scrolling work as expected + $('#mainContent').focus(); + $("#mainImage").click(function(evt) { // Firefox does not support offsetX/Y so we have to manually calculate // where the user clicked in the image. diff --git a/cps/templates/readcbr.html b/cps/templates/readcbr.html index 5eefa5df..de661d0e 100644 --- a/cps/templates/readcbr.html +++ b/cps/templates/readcbr.html @@ -1,148 +1,156 @@ - - - - Comic Reader - - - - - - - + + + + Comic Reader + + + - - - - - - -