diff --git a/include/book.h b/include/book.h index a9db6df2c..cdcbf51fe 100644 --- a/include/book.h +++ b/include/book.h @@ -21,6 +21,9 @@ #define KIWIX_BOOK_H #include +#include +#include +#include namespace pugi { class xml_node; @@ -41,7 +44,26 @@ class Reader; */ class Book { - public: + public: // types + class Illustration + { + friend class Book; + 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> Illustrations; + + public: // functions Book(); ~Book(); @@ -74,8 +96,11 @@ class Book const uint64_t& getMediaCount() const { return m_mediaCount; } const uint64_t& getSize() const { return m_size; } const std::string& getFavicon() const; - const std::string& getFaviconUrl() const { return m_faviconUrl; } - const std::string& getFaviconMimeType() const { return m_faviconMimeType; } + const std::string& getFaviconUrl() const; + const std::string& getFaviconMimeType() const; + + Illustrations getIllustrations() const; + const std::string& getDownloadId() const { return m_downloadId; } void setReadOnly(bool readOnly) { m_readOnly = readOnly; } @@ -96,14 +121,13 @@ class Book void setArticleCount(uint64_t articleCount) { m_articleCount = articleCount; } void setMediaCount(uint64_t mediaCount) { m_mediaCount = mediaCount; } 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; } - private: + private: // functions std::string getCategoryFromTags() const; + const Illustration& getDefaultIllustration() const; - protected: + protected: // data std::string m_id; std::string m_downloadId; std::string m_path; @@ -124,9 +148,11 @@ class Book uint64_t m_mediaCount = 0; bool m_readOnly = false; uint64_t m_size = 0; - mutable std::string m_favicon; - std::string m_faviconUrl; - std::string m_faviconMimeType; + Illustrations m_illustrations; + + // Used as the return value of getDefaultIllustration() when no default + // illustration is found in the book + static const Illustration missingDefaultIllustration; }; } diff --git a/src/book.cpp b/src/book.cpp index 020fa7f4c..63ebbec19 100644 --- a/src/book.cpp +++ b/src/book.cpp @@ -41,11 +41,17 @@ Book::Book() : m_readOnly(false) { } + /* Destructor */ Book::~Book() { } +Book::Illustrations Book::getIllustrations() const +{ + return m_illustrations; +} + bool Book::update(const kiwix::Book& other) { if (m_readOnly) @@ -54,30 +60,7 @@ bool Book::update(const kiwix::Book& other) if (m_id != other.m_id) return false; - m_readOnly = other.m_readOnly; - 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; - + *this = other; return true; } @@ -104,7 +87,16 @@ void Book::update(const zim::Archive& archive) { m_mediaCount = getArchiveMediaCount(archive); m_size = static_cast(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(); + 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() @@ -131,9 +123,11 @@ void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir) m_articleCount = strtoull(ATTR("articleCount"), 0, 0); m_mediaCount = strtoull(ATTR("mediaCount"), 0, 0); m_size = strtoull(ATTR("size"), 0, 0) << 10; - m_favicon = base64_decode(ATTR("favicon")); - m_faviconMimeType = ATTR("faviconMimeType"); - m_faviconUrl = ATTR("faviconUrl"); + const auto favicon = std::make_shared(); + favicon->data = base64_decode(ATTR("favicon")); + favicon->mimeType = ATTR("faviconMimeType"); + favicon->url = ATTR("faviconUrl"); + m_illustrations.assign(1, favicon); try { m_downloadId = ATTR("downloadId"); } 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); } if (rel == "http://opds-spec.org/image/thumbnail") { - m_faviconUrl = urlHost + linkNode.attribute("href").value(); - m_faviconMimeType = linkNode.attribute("type").value(); + const auto favicon = std::make_shared(); + 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; } -const std::string& Book::getFavicon() const { - if (m_favicon.empty() && !m_faviconUrl.empty()) { - try { - m_favicon = download(m_faviconUrl); - } catch(...) { - std::cerr << "Cannot download favicon from " << m_faviconUrl; +const Book::Illustration Book::missingDefaultIllustration; + +const Book::Illustration& Book::getDefaultIllustration() const +{ + for ( const auto& ilPtr : m_illustrations ) { + if (ilPtr->width == 48 && ilPtr->height == 48) { + return *ilPtr; } } - return m_favicon; + return missingDefaultIllustration; +} + +const std::string& Book::Illustration::getData() const +{ + if (data.empty() && !url.empty()) { + const std::lock_guard l(mutex); + if ( data.empty() ) { + try { + data = download(url); + } catch(...) { + std::cerr << "Cannot download favicon from " << url; + } + } + } + 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 { diff --git a/src/opds_dumper.cpp b/src/opds_dumper.cpp index 6897b3142..7d463f714 100644 --- a/src/opds_dumper.cpp +++ b/src/opds_dumper.cpp @@ -58,10 +58,10 @@ IllustrationInfo getBookIllustrationInfo(const Book& book) { kainjow::mustache::list illustrations; if ( book.isPathValid() ) { - for ( auto illustration_size : zim::Archive(book.getPath()).getIllustrationSizes() ) { + for ( const auto& illustration : book.getIllustrations() ) { illustrations.push_back(kainjow::mustache::object{ - {"icon_width", to_string(illustration_size)}, - {"icon_height", to_string(illustration_size)}, + {"icon_width", to_string(illustration->width)}, + {"icon_height", to_string(illustration->height)}, {"icon_scale", "1"}, }); } diff --git a/test/book.cpp b/test/book.cpp index 22eca5428..f477b6765 100644 --- a/test/book.cpp +++ b/test/book.cpp @@ -2,42 +2,6 @@ #include "../include/book.h" #include -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 { @@ -68,6 +32,9 @@ TEST(BookTest, updateFromXMLTest) articleCount="123456" mediaCount="234567" size="345678" + favicon="ZmFrZS1ib29rLWZhdmljb24tZGF0YQ==" + faviconMimeType="text/plain" + faviconUrl="http://who.org/zara.fav" > )"); @@ -85,6 +52,9 @@ TEST(BookTest, updateFromXMLTest) EXPECT_EQ(book.getArticleCount(), 123456U); EXPECT_EQ(book.getMediaCount(), 234567U); 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) @@ -166,3 +136,45 @@ TEST(BookTest, updateCopiesCategory) newBook.update(book); EXPECT_EQ(newBook.getCategory(), "ted"); } + +TEST(BookTest, updateTest) +{ + const XMLDoc xml(R"( + + + )"); + + 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()); +}