Skip to content

Commit

Permalink
Preliminary support for CBZ files
Browse files Browse the repository at this point in the history
There is no metadata provided at the moment, since I haven't found where
it's stored (or if it even is).  Right now, it is assumed that all the
files in the archive are images and the order in the archive is the
order in which they are to be presented.

Refs #78.
  • Loading branch information
rschroll committed Apr 10, 2015
1 parent 5df04d4 commit b689dce
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 23 deletions.
1 change: 1 addition & 0 deletions epubreader/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ set(PLUGIN_DIR Epub)

set(epubreader_SRCS
epubreader.cpp
cbzreader.cpp
epubreaderplugin.cpp
quazip/JlCompress.cpp
quazip/qioapi.cpp
Expand Down
171 changes: 171 additions & 0 deletions epubreader/cbzreader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/* Copyright 2015 Robert Schroll
*
* This file is part of Beru and is distributed under the terms of
* the GPL. See the file COPYING for full details.
*/

#include "cbzreader.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QtGui/QImage>
#include <QBuffer>
#include <QDir>
#include <QCryptographicHash>
#include "quazip/quazip.h"
#include "quazip/quazipfile.h"
#include "../qhttpserver/qhttpresponse.h"

QString guessMimeType(const QString &filename);

CBZReader::CBZReader(QObject *parent) :
QObject(parent)
{
this->zip = NULL;
}

bool CBZReader::load(const QString &filename)
{
if (this->zip != NULL) {
delete this->zip;
this->zip = NULL;
}
this->_hash = "";
this->spine.clear();

this->zip = new QuaZip(filename);
if (!this->zip->open(QuaZip::mdUnzip)) {
delete this->zip;
this->zip = NULL;
return false;
}
if (!this->parse()) {
delete this->zip;
this->zip = NULL;
return false;
}
return true;
}

bool CBZReader::parse() {
QList<QuaZipFileInfo> fileList = this->zip->getFileInfoList();
foreach (const QuaZipFileInfo info, fileList) {
if (info.uncompressedSize > 0)
this->spine.append(info.name);
}
return true;
}

QString CBZReader::hash() {
if (this->_hash != "")
return this->_hash;

if (!this->zip || !this->zip->isOpen())
return this->_hash;

QByteArray CRCarray;
QDataStream CRCstream(&CRCarray, QIODevice::WriteOnly);
QList<QuaZipFileInfo> fileList = this->zip->getFileInfoList();
foreach (const QuaZipFileInfo info, fileList) {
CRCstream << info.crc;
}
this->_hash = QCryptographicHash::hash(CRCarray, QCryptographicHash::Md5).toHex();
return this->_hash;
}

QString CBZReader::title() {
return "";
}

void CBZReader::serveComponent(const QString &filename, QHttpResponse *response)
{
if (!this->zip || !this->zip->isOpen()) {
response->writeHead(500);
response->end("Epub file not open for reading");
return;
}

this->zip->setCurrentFile(filename);
QuaZipFile zfile(this->zip);
if (!zfile.open(QIODevice::ReadOnly)) {
response->writeHead(404);
response->end("Could not find \"" + filename + "\" in epub file");
return;
}

response->setHeader("Content-Type", guessMimeType(filename));
response->writeHead(200);
// Important -- use write instead of end, so binary data doesn't get messed up!
response->write(zfile.readAll());
response->end();
zfile.close();
}

QVariantList CBZReader::getContents()
{
QVariantList res;
for (int i=0; i<this->spine.length(); i++) {
QVariantMap entry;
entry["title"] = "%PAGE% " + QString::number(i + 1);
entry["src"] = this->spine[i];
res.append(entry);
}
emit contentsReady(res);
return res;
}

void CBZReader::serveBookData(QHttpResponse *response)
{
if (!this->zip || !this->zip->isOpen()) {
response->writeHead(500);
response->end("Epub file not open for reading");
return;
}

response->setHeader("Content-Type", guessMimeType("js"));
response->writeHead(200);
QJsonDocument spine(QJsonArray::fromStringList(this->spine));
QJsonDocument contents(QJsonArray::fromVariantList(this->getContents()));
QString res = "var bookData = {" \
"getComponents: function () { return %1; }, " \
"getContents: function () { return %2; }, " \
"getComponent: function (component) { return " \
"\"<img style='display: block; margin: auto; max-height: 100% !important' src='\"" \
"+ component.replace(/\"/g, \"&#34;\").replace(/'/g, \"&#39;\") + \"' />\"; }, " \
"getMetaData: function (key) { return ''; } }";
response->write(res.arg(QString(spine.toJson()), QString(contents.toJson())));
response->end();
}

QVariantMap CBZReader::getCoverInfo(int thumbsize, int fullsize)
{
QVariantMap res;
if (!this->zip || !this->zip->isOpen())
return res;

res["title"] = "ZZZnone";
res["author"] = "";
res["authorsort"] = "zzznone";
res["cover"] = "ZZZnone";

this->zip->setCurrentFile(this->spine[0]);
QuaZipFile zfile(this->zip);
if (!zfile.open(QIODevice::ReadOnly))
return res;

QImage coverimg;
if (!coverimg.loadFromData(zfile.readAll())) {
zfile.close();
return res;
}
zfile.close();
QByteArray byteArray;
QBuffer buffer(&byteArray);
coverimg.scaledToWidth(thumbsize, Qt::SmoothTransformation).save(&buffer, "PNG");
res["cover"] = "data:image/png;base64," + QString(byteArray.toBase64());
QByteArray byteArrayf;
QBuffer bufferf(&byteArrayf);
coverimg.scaledToWidth(fullsize, Qt::SmoothTransformation).save(&bufferf, "PNG");
res["fullcover"] = "data:image/png;base64," + QString(byteArrayf.toBase64());
return res;
}
43 changes: 43 additions & 0 deletions epubreader/cbzreader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* Copyright 2015 Robert Schroll
*
* This file is part of Beru and is distributed under the terms of
* the GPL. See the file COPYING for full details.
*/

#ifndef CBZREADER_H
#define CBZREADER_H

#include <QObject>
#include <QDomDocument>
#include <QVariant>
#include "quazip/quazip.h"
#include "../qhttpserver/qhttpresponse.h"

class CBZReader : public QObject
{
Q_OBJECT
Q_PROPERTY(QString hash READ hash)
Q_PROPERTY(QString title READ title)
public:
explicit CBZReader(QObject *parent = 0);
QString hash();
QString title();
Q_INVOKABLE bool load(const QString &filename);
Q_INVOKABLE void serveBookData(QHttpResponse *response);
Q_INVOKABLE void serveComponent(const QString &filename, QHttpResponse *response);
Q_INVOKABLE QVariantMap getCoverInfo(int thumbsize, int fullsize);

signals:
void contentsReady(QVariantList contents);

private:
bool parse();
QVariantList getContents();

QuaZip* zip;
QString _hash;
QStringList spine;

};

#endif // CBZREADER_H
2 changes: 2 additions & 0 deletions epubreader/epubreaderplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

#include "epubreaderplugin.h"
#include "epubreader.h"
#include "cbzreader.h"
#include <qqml.h>

void EpubReaderPlugin::registerTypes(const char *uri)
{
qmlRegisterType<EpubReader>(uri, 1, 0, "EpubReader");
qmlRegisterType<CBZReader>(uri, 1, 0, "CBZReader");
}
4 changes: 2 additions & 2 deletions ui/BookPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ PageWithBottomEdge {
model: contentsListModel
delegate: Standard {
text: (new Array(model.level + 1)).join(" ") +
model.title.replace(/(\n| )+/g, " ")
model.title.replace(/(\n| )+/g, " ").replace(/^%PAGE%/, i18n.tr("Page"))
selected: bookPage.currentChapter == model.src
onClicked: {
Messaging.sendMessage("NavigateChapter", model.src)
Expand Down Expand Up @@ -613,7 +613,7 @@ PageWithBottomEdge {
Messaging.registerHandler("PageChange", onPageChange)
Messaging.registerHandler("Styles", bookStyles.load)
Messaging.registerHandler("Ready", onReady)
server.epub.contentsReady.connect(parseContents)
server.reader.contentsReady.connect(parseContents)
onWidthChanged.connect(windowSizeChanged)
onHeightChanged.connect(windowSizeChanged)
}
Expand Down
1 change: 1 addition & 0 deletions ui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set(UI_SRC_FILES
main.qml
qmlmessaging.js
qmlmessaging-userscript.js
Reader.qml
Server.qml
components/FloatingButton.qml
components/OptionSelector.qml
Expand Down
7 changes: 3 additions & 4 deletions ui/LocalBooks.qml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import QtGraphicalEffects 1.0
import Ubuntu.Components 1.1
import Ubuntu.Components.ListItems 1.0
import Ubuntu.Components.Popups 1.0
import Epub 1.0

import "components"

Expand Down Expand Up @@ -52,7 +51,7 @@ Page {
}

function fileToTitle(filename) {
return filename.replace(/\.epub$/, "").replace(/_/g, " ")
return filename.replace(/\.epub$/, "").replace(/\.cbz$/, "").replace(/_/g, " ")
}

// New items are given a lastread time of now, since these are probably
Expand All @@ -74,7 +73,7 @@ Page {
function addBookDir() {
var db = openDatabase()
db.transaction(function (tx) {
var files = filesystem.listDir(bookdir, ["*.epub"])
var files = filesystem.listDir(bookdir, ["*.epub", "*.cbz"])
for (var i=0; i<files.length; i++) {
var fileName = files[i].split("/").pop()
tx.executeSql(addFileSQL, [files[i], fileToTitle(fileName)])
Expand Down Expand Up @@ -311,7 +310,7 @@ Page {
gridview.positionViewAtBeginning()
}

EpubReader {
Reader {
id: coverReader
}

Expand Down
56 changes: 56 additions & 0 deletions ui/Reader.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* Copyright 2015 Robert Schroll
*
* This file is part of Beru and is distributed under the terms of
* the GPL. See the file COPYING for full details.
*/

import QtQuick 2.0
import Epub 1.0


Item {
id: reader

signal contentsReady(var contents)

property var currentReader

EpubReader {
id: epub
onContentsReady: reader.contentsReady(contents)
}

CBZReader {
id: cbz
onContentsReady: reader.contentsReady(contents)
}

function isCBZ(filename) {
return (filename.slice(-4) == ".cbz")
}

function load(filename) {
currentReader = isCBZ(filename) ? cbz : epub
return currentReader.load(filename)
}

function hash() {
return currentReader.hash
}

function title() {
return currentReader.title
}

function serveBookData(response) {
currentReader.serveBookData(response)
}

function serveComponent(filename, response) {
currentReader.serveComponent(filename, response)
}

function getCoverInfo(thumbsize, fullsize) {
return currentReader.getCoverInfo(thumbsize, fullsize)
}
}
15 changes: 5 additions & 10 deletions ui/Server.qml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import QtQuick 2.0
import HttpServer 1.0
import Epub 1.0


HttpServer {
Expand All @@ -18,19 +17,15 @@ HttpServer {
while (!listen("127.0.0.1", port))
port += 1
}
property var epub: EpubReader {
id: epub

property var reader: Reader {
id: reader
}

property var fileserver: FileServer {
id: fileserver
}

function loadFile(filename) {
return epub.load(filename)
}

function static_file(path, response) {
// Need to strip off leading "file://"
fileserver.serve(Qt.resolvedUrl("../html/" + path).slice(7), response)
Expand All @@ -55,11 +50,11 @@ HttpServer {
if (request.path == "/")
return static_file("index.html", response)
if (request.path == "/.bookdata.js")
return epub.serveBookData(response)
return reader.serveBookData(response)
if (request.path == "/.defaults.js")
return defaults(response)
if (request.path[1] == ".")
return static_file(request.path.slice(2), response)
return epub.serveComponent(request.path.slice(1), response)
return reader.serveComponent(request.path.slice(1), response)
}
}
Loading

0 comments on commit b689dce

Please sign in to comment.