mirror of https://github.com/kiwix/libkiwix.git
Merge pull request #630 from kiwix/multi_illustration_books
This commit is contained in:
commit
c7b88398bd
|
@ -21,6 +21,9 @@
|
||||||
#define KIWIX_BOOK_H
|
#define KIWIX_BOOK_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
namespace pugi {
|
namespace pugi {
|
||||||
class xml_node;
|
class xml_node;
|
||||||
|
@ -41,7 +44,26 @@ class Reader;
|
||||||
*/
|
*/
|
||||||
class Book
|
class Book
|
||||||
{
|
{
|
||||||
|
public: // types
|
||||||
|
class Illustration
|
||||||
|
{
|
||||||
|
friend class Book;
|
||||||
public:
|
public:
|
||||||
|
uint16_t width = 48;
|
||||||
|
uint16_t height = 48;
|
||||||
|
std::string mimeType;
|
||||||
|
std::string url;
|
||||||
|
|
||||||
|
const std::string& getData() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::string data;
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<std::shared_ptr<const Illustration>> Illustrations;
|
||||||
|
|
||||||
|
public: // functions
|
||||||
Book();
|
Book();
|
||||||
~Book();
|
~Book();
|
||||||
|
|
||||||
|
@ -74,8 +96,11 @@ class Book
|
||||||
const uint64_t& getMediaCount() const { return m_mediaCount; }
|
const uint64_t& getMediaCount() const { return m_mediaCount; }
|
||||||
const uint64_t& getSize() const { return m_size; }
|
const uint64_t& getSize() const { return m_size; }
|
||||||
const std::string& getFavicon() const;
|
const std::string& getFavicon() const;
|
||||||
const std::string& getFaviconUrl() const { return m_faviconUrl; }
|
const std::string& getFaviconUrl() const;
|
||||||
const std::string& getFaviconMimeType() const { return m_faviconMimeType; }
|
const std::string& getFaviconMimeType() const;
|
||||||
|
|
||||||
|
Illustrations getIllustrations() const;
|
||||||
|
|
||||||
const std::string& getDownloadId() const { return m_downloadId; }
|
const std::string& getDownloadId() const { return m_downloadId; }
|
||||||
|
|
||||||
void setReadOnly(bool readOnly) { m_readOnly = readOnly; }
|
void setReadOnly(bool readOnly) { m_readOnly = readOnly; }
|
||||||
|
@ -96,14 +121,13 @@ class Book
|
||||||
void setArticleCount(uint64_t articleCount) { m_articleCount = articleCount; }
|
void setArticleCount(uint64_t articleCount) { m_articleCount = articleCount; }
|
||||||
void setMediaCount(uint64_t mediaCount) { m_mediaCount = mediaCount; }
|
void setMediaCount(uint64_t mediaCount) { m_mediaCount = mediaCount; }
|
||||||
void setSize(uint64_t size) { m_size = size; }
|
void setSize(uint64_t size) { m_size = size; }
|
||||||
void setFavicon(const std::string& favicon) { m_favicon = favicon; }
|
|
||||||
void setFaviconMimeType(const std::string& faviconMimeType) { m_faviconMimeType = faviconMimeType; }
|
|
||||||
void setDownloadId(const std::string& downloadId) { m_downloadId = downloadId; }
|
void setDownloadId(const std::string& downloadId) { m_downloadId = downloadId; }
|
||||||
|
|
||||||
private:
|
private: // functions
|
||||||
std::string getCategoryFromTags() const;
|
std::string getCategoryFromTags() const;
|
||||||
|
const Illustration& getDefaultIllustration() const;
|
||||||
|
|
||||||
protected:
|
protected: // data
|
||||||
std::string m_id;
|
std::string m_id;
|
||||||
std::string m_downloadId;
|
std::string m_downloadId;
|
||||||
std::string m_path;
|
std::string m_path;
|
||||||
|
@ -124,9 +148,11 @@ class Book
|
||||||
uint64_t m_mediaCount = 0;
|
uint64_t m_mediaCount = 0;
|
||||||
bool m_readOnly = false;
|
bool m_readOnly = false;
|
||||||
uint64_t m_size = 0;
|
uint64_t m_size = 0;
|
||||||
mutable std::string m_favicon;
|
Illustrations m_illustrations;
|
||||||
std::string m_faviconUrl;
|
|
||||||
std::string m_faviconMimeType;
|
// Used as the return value of getDefaultIllustration() when no default
|
||||||
|
// illustration is found in the book
|
||||||
|
static const Illustration missingDefaultIllustration;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
97
src/book.cpp
97
src/book.cpp
|
@ -41,11 +41,17 @@ Book::Book() :
|
||||||
m_readOnly(false)
|
m_readOnly(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Destructor */
|
/* Destructor */
|
||||||
Book::~Book()
|
Book::~Book()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Book::Illustrations Book::getIllustrations() const
|
||||||
|
{
|
||||||
|
return m_illustrations;
|
||||||
|
}
|
||||||
|
|
||||||
bool Book::update(const kiwix::Book& other)
|
bool Book::update(const kiwix::Book& other)
|
||||||
{
|
{
|
||||||
if (m_readOnly)
|
if (m_readOnly)
|
||||||
|
@ -54,30 +60,7 @@ bool Book::update(const kiwix::Book& other)
|
||||||
if (m_id != other.m_id)
|
if (m_id != other.m_id)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
m_readOnly = other.m_readOnly;
|
*this = other;
|
||||||
m_path = other.m_path;
|
|
||||||
m_pathValid = other.m_pathValid;
|
|
||||||
m_title = other.m_title;
|
|
||||||
m_description = other.m_description;
|
|
||||||
m_language = other.m_language;
|
|
||||||
m_creator = other.m_creator;
|
|
||||||
m_publisher = other.m_publisher;
|
|
||||||
m_date = other.m_date;
|
|
||||||
m_url = other.m_url;
|
|
||||||
m_name = other.m_name;
|
|
||||||
m_flavour = other.m_flavour;
|
|
||||||
m_tags = other.m_tags;
|
|
||||||
m_category = other.m_category;
|
|
||||||
m_origId = other.m_origId;
|
|
||||||
m_articleCount = other.m_articleCount;
|
|
||||||
m_mediaCount = other.m_mediaCount;
|
|
||||||
m_size = other.m_size;
|
|
||||||
m_favicon = other.m_favicon;
|
|
||||||
m_faviconMimeType = other.m_faviconMimeType;
|
|
||||||
m_faviconUrl = other.m_faviconUrl;
|
|
||||||
|
|
||||||
m_downloadId = other.m_downloadId;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +87,16 @@ void Book::update(const zim::Archive& archive) {
|
||||||
m_mediaCount = getArchiveMediaCount(archive);
|
m_mediaCount = getArchiveMediaCount(archive);
|
||||||
m_size = static_cast<uint64_t>(getArchiveFileSize(archive)) << 10;
|
m_size = static_cast<uint64_t>(getArchiveFileSize(archive)) << 10;
|
||||||
|
|
||||||
getArchiveFavicon(archive, 48, m_favicon, m_faviconMimeType);
|
m_illustrations.clear();
|
||||||
|
for ( const auto illustrationSize : archive.getIllustrationSizes() ) {
|
||||||
|
const auto illustration = std::make_shared<Illustration>();
|
||||||
|
const zim::Item illustrationItem = archive.getIllustrationItem(illustrationSize);
|
||||||
|
illustration->width = illustration->height = illustrationSize;
|
||||||
|
illustration->mimeType = illustrationItem.getMimetype();
|
||||||
|
illustration->data = illustrationItem.getData();
|
||||||
|
// NOTE: illustration->url is left uninitialized
|
||||||
|
m_illustrations.push_back(illustration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ATTR(name) node.attribute(name).value()
|
#define ATTR(name) node.attribute(name).value()
|
||||||
|
@ -131,9 +123,11 @@ void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir)
|
||||||
m_articleCount = strtoull(ATTR("articleCount"), 0, 0);
|
m_articleCount = strtoull(ATTR("articleCount"), 0, 0);
|
||||||
m_mediaCount = strtoull(ATTR("mediaCount"), 0, 0);
|
m_mediaCount = strtoull(ATTR("mediaCount"), 0, 0);
|
||||||
m_size = strtoull(ATTR("size"), 0, 0) << 10;
|
m_size = strtoull(ATTR("size"), 0, 0) << 10;
|
||||||
m_favicon = base64_decode(ATTR("favicon"));
|
const auto favicon = std::make_shared<Illustration>();
|
||||||
m_faviconMimeType = ATTR("faviconMimeType");
|
favicon->data = base64_decode(ATTR("favicon"));
|
||||||
m_faviconUrl = ATTR("faviconUrl");
|
favicon->mimeType = ATTR("faviconMimeType");
|
||||||
|
favicon->url = ATTR("faviconUrl");
|
||||||
|
m_illustrations.assign(1, favicon);
|
||||||
try {
|
try {
|
||||||
m_downloadId = ATTR("downloadId");
|
m_downloadId = ATTR("downloadId");
|
||||||
} catch(...) {}
|
} catch(...) {}
|
||||||
|
@ -181,8 +175,11 @@ void Book::updateFromOpds(const pugi::xml_node& node, const std::string& urlHost
|
||||||
m_size = strtoull(linkNode.attribute("length").value(), 0, 0);
|
m_size = strtoull(linkNode.attribute("length").value(), 0, 0);
|
||||||
}
|
}
|
||||||
if (rel == "http://opds-spec.org/image/thumbnail") {
|
if (rel == "http://opds-spec.org/image/thumbnail") {
|
||||||
m_faviconUrl = urlHost + linkNode.attribute("href").value();
|
const auto favicon = std::make_shared<Illustration>();
|
||||||
m_faviconMimeType = linkNode.attribute("type").value();
|
favicon->data.clear();
|
||||||
|
favicon->url = urlHost + linkNode.attribute("href").value();
|
||||||
|
favicon->mimeType = linkNode.attribute("type").value();
|
||||||
|
m_illustrations.assign(1, favicon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,15 +212,45 @@ void Book::setPath(const std::string& path)
|
||||||
: path;
|
: path;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& Book::getFavicon() const {
|
const Book::Illustration Book::missingDefaultIllustration;
|
||||||
if (m_favicon.empty() && !m_faviconUrl.empty()) {
|
|
||||||
|
const Book::Illustration& Book::getDefaultIllustration() const
|
||||||
|
{
|
||||||
|
for ( const auto& ilPtr : m_illustrations ) {
|
||||||
|
if (ilPtr->width == 48 && ilPtr->height == 48) {
|
||||||
|
return *ilPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missingDefaultIllustration;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Book::Illustration::getData() const
|
||||||
|
{
|
||||||
|
if (data.empty() && !url.empty()) {
|
||||||
|
const std::lock_guard<std::mutex> l(mutex);
|
||||||
|
if ( data.empty() ) {
|
||||||
try {
|
try {
|
||||||
m_favicon = download(m_faviconUrl);
|
data = download(url);
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
std::cerr << "Cannot download favicon from " << m_faviconUrl;
|
std::cerr << "Cannot download favicon from " << url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m_favicon;
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Book::getFavicon() const {
|
||||||
|
return getDefaultIllustration().getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Book::getFaviconUrl() const
|
||||||
|
{
|
||||||
|
return getDefaultIllustration().url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Book::getFaviconMimeType() const
|
||||||
|
{
|
||||||
|
return getDefaultIllustration().mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Book::getTagStr(const std::string& tagName) const {
|
std::string Book::getTagStr(const std::string& tagName) const {
|
||||||
|
|
|
@ -58,10 +58,10 @@ IllustrationInfo getBookIllustrationInfo(const Book& book)
|
||||||
{
|
{
|
||||||
kainjow::mustache::list illustrations;
|
kainjow::mustache::list illustrations;
|
||||||
if ( book.isPathValid() ) {
|
if ( book.isPathValid() ) {
|
||||||
for ( auto illustration_size : zim::Archive(book.getPath()).getIllustrationSizes() ) {
|
for ( const auto& illustration : book.getIllustrations() ) {
|
||||||
illustrations.push_back(kainjow::mustache::object{
|
illustrations.push_back(kainjow::mustache::object{
|
||||||
{"icon_width", to_string(illustration_size)},
|
{"icon_width", to_string(illustration->width)},
|
||||||
{"icon_height", to_string(illustration_size)},
|
{"icon_height", to_string(illustration->height)},
|
||||||
{"icon_scale", "1"},
|
{"icon_scale", "1"},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,42 +2,6 @@
|
||||||
#include "../include/book.h"
|
#include "../include/book.h"
|
||||||
#include <pugixml.hpp>
|
#include <pugixml.hpp>
|
||||||
|
|
||||||
TEST(BookTest, updateTest)
|
|
||||||
{
|
|
||||||
kiwix::Book book;
|
|
||||||
|
|
||||||
book.setId("xyz");
|
|
||||||
book.setReadOnly(false);
|
|
||||||
book.setPath("/home/user/Downloads/skin-of-color-society_en_all_2019-11.zim");
|
|
||||||
book.setPathValid(true);
|
|
||||||
book.setUrl("book-url");
|
|
||||||
book.setTags("youtube;_videos:yes;_ftindex:yes;_ftindex:yes;_pictures:yes;_details:yes");
|
|
||||||
book.setName("skin-of-color-society_en_all");
|
|
||||||
book.setFavicon("book-favicon");
|
|
||||||
book.setFaviconMimeType("book-favicon-mimetype");
|
|
||||||
|
|
||||||
kiwix::Book newBook;
|
|
||||||
|
|
||||||
newBook.setReadOnly(true);
|
|
||||||
EXPECT_FALSE(newBook.update(book));
|
|
||||||
|
|
||||||
newBook.setReadOnly(false);
|
|
||||||
EXPECT_FALSE(newBook.update(book));
|
|
||||||
|
|
||||||
newBook.setId("xyz");
|
|
||||||
EXPECT_TRUE(newBook.update(book));
|
|
||||||
|
|
||||||
EXPECT_EQ(newBook.readOnly(), book.readOnly());
|
|
||||||
EXPECT_EQ(newBook.getPath(), book.getPath());
|
|
||||||
EXPECT_EQ(newBook.isPathValid(), book.isPathValid());
|
|
||||||
EXPECT_EQ(newBook.getUrl(), book.getUrl());
|
|
||||||
EXPECT_EQ(newBook.getTags(), book.getTags());
|
|
||||||
EXPECT_EQ(newBook.getCategory(), book.getCategory());
|
|
||||||
EXPECT_EQ(newBook.getName(), book.getName());
|
|
||||||
EXPECT_EQ(newBook.getFavicon(), book.getFavicon());
|
|
||||||
EXPECT_EQ(newBook.getFaviconMimeType(), book.getFaviconMimeType());
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -68,6 +32,9 @@ TEST(BookTest, updateFromXMLTest)
|
||||||
articleCount="123456"
|
articleCount="123456"
|
||||||
mediaCount="234567"
|
mediaCount="234567"
|
||||||
size="345678"
|
size="345678"
|
||||||
|
favicon="ZmFrZS1ib29rLWZhdmljb24tZGF0YQ=="
|
||||||
|
faviconMimeType="text/plain"
|
||||||
|
faviconUrl="http://who.org/zara.fav"
|
||||||
>
|
>
|
||||||
</book>
|
</book>
|
||||||
)");
|
)");
|
||||||
|
@ -85,6 +52,9 @@ TEST(BookTest, updateFromXMLTest)
|
||||||
EXPECT_EQ(book.getArticleCount(), 123456U);
|
EXPECT_EQ(book.getArticleCount(), 123456U);
|
||||||
EXPECT_EQ(book.getMediaCount(), 234567U);
|
EXPECT_EQ(book.getMediaCount(), 234567U);
|
||||||
EXPECT_EQ(book.getSize(), 345678U*1024U);
|
EXPECT_EQ(book.getSize(), 345678U*1024U);
|
||||||
|
EXPECT_EQ(book.getFavicon(), "fake-book-favicon-data");
|
||||||
|
EXPECT_EQ(book.getFaviconMimeType(), "text/plain");
|
||||||
|
EXPECT_EQ(book.getFaviconUrl(), "http://who.org/zara.fav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BookTest, updateFromXMLCategoryHandlingTest)
|
TEST(BookTest, updateFromXMLCategoryHandlingTest)
|
||||||
|
@ -166,3 +136,45 @@ TEST(BookTest, updateCopiesCategory)
|
||||||
newBook.update(book);
|
newBook.update(book);
|
||||||
EXPECT_EQ(newBook.getCategory(), "ted");
|
EXPECT_EQ(newBook.getCategory(), "ted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(BookTest, updateTest)
|
||||||
|
{
|
||||||
|
const XMLDoc xml(R"(
|
||||||
|
<book id="xyz"
|
||||||
|
path="/home/user/Downloads/skin-of-color-society_en_all_2019-11.zim"
|
||||||
|
url="book-url"
|
||||||
|
name="skin-of-color-society_en_all"
|
||||||
|
tags="youtube;_videos:yes;_ftindex:yes;_ftindex:yes;_pictures:yes;_details:yes"
|
||||||
|
favicon="Ym9vay1mYXZpY29u"
|
||||||
|
faviconMimeType="book-favicon-mimetype"
|
||||||
|
>
|
||||||
|
</book>
|
||||||
|
)");
|
||||||
|
|
||||||
|
kiwix::Book book;
|
||||||
|
book.updateFromXml(xml.child("book"), "/data/zim");
|
||||||
|
|
||||||
|
book.setReadOnly(false);
|
||||||
|
book.setPathValid(true);
|
||||||
|
|
||||||
|
kiwix::Book newBook;
|
||||||
|
|
||||||
|
newBook.setReadOnly(true);
|
||||||
|
EXPECT_FALSE(newBook.update(book));
|
||||||
|
|
||||||
|
newBook.setReadOnly(false);
|
||||||
|
EXPECT_FALSE(newBook.update(book));
|
||||||
|
|
||||||
|
newBook.setId("xyz");
|
||||||
|
EXPECT_TRUE(newBook.update(book));
|
||||||
|
|
||||||
|
EXPECT_EQ(newBook.readOnly(), book.readOnly());
|
||||||
|
EXPECT_EQ(newBook.getPath(), book.getPath());
|
||||||
|
EXPECT_EQ(newBook.isPathValid(), book.isPathValid());
|
||||||
|
EXPECT_EQ(newBook.getUrl(), book.getUrl());
|
||||||
|
EXPECT_EQ(newBook.getTags(), book.getTags());
|
||||||
|
EXPECT_EQ(newBook.getCategory(), book.getCategory());
|
||||||
|
EXPECT_EQ(newBook.getName(), book.getName());
|
||||||
|
EXPECT_EQ(newBook.getFavicon(), book.getFavicon());
|
||||||
|
EXPECT_EQ(newBook.getFaviconMimeType(), book.getFaviconMimeType());
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue