diff --git a/include/bookmark.h b/include/bookmark.h new file mode 100644 index 000000000..7861fee31 --- /dev/null +++ b/include/bookmark.h @@ -0,0 +1,68 @@ +/* + * Copyright 2018 Matthieu Gautier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef KIWIX_BOOKMARK_H +#define KIWIX_BOOKMARK_H + +#include + +namespace pugi { +class xml_node; +} + +namespace kiwix +{ + +/** + * A class to store information about a bookmark (an article in a book) + */ +class Bookmark +{ + public: + Bookmark(); + ~Bookmark(); + + void updateFromXml(const pugi::xml_node& node); + + const std::string& getBookId() const { return m_bookId; } + const std::string& getBookTitle() const { return m_bookTitle; } + const std::string& getUrl() const { return m_url; } + const std::string& getTitle() const { return m_title; } + const std::string& getLanguage() const { return m_language; } + const std::string& getDate() const { return m_date; } + + void setBookId(const std::string& bookId) { m_bookId = bookId; } + void setBookTitle(const std::string& bookTitle) { m_bookTitle = bookTitle; } + void setUrl(const std::string& url) { m_url = url; } + void setTitle(const std::string& title) { m_title = title; } + void setLanguage(const std::string& language) { m_language = language; } + void setDate(const std::string& date) { m_date = date; } + + protected: + std::string m_bookId; + std::string m_bookTitle; + std::string m_url; + std::string m_title; + std::string m_language; + std::string m_date; +}; + +} + +#endif diff --git a/include/library.h b/include/library.h index e24bf9d7e..24f8fe5cd 100644 --- a/include/library.h +++ b/include/library.h @@ -24,12 +24,14 @@ #include #include +#include "book.h" +#include "bookmark.h" + #define KIWIX_LIBRARY_VERSION "20110515" namespace kiwix { -class Book; class OPDSDumper; enum supportedListSortBy { UNSORTED, TITLE, SIZE, DATE, CREATOR, PUBLISHER }; @@ -47,12 +49,13 @@ enum supportedListMode { */ class Library { - std::map books; + std::map m_books; + std::vector m_bookmarks; + public: Library(); ~Library(); - std::string version; /** * Add a book to the library. * @@ -65,6 +68,22 @@ class Library */ bool addBook(const Book& book); + /** + * Add a bookmark to the library. + * + * @param bookmark the book to add. + */ + void addBookmark(const Bookmark& bookmark); + + /** + * Remove a bookmarkk + * + * @param zimId The zimId of the bookmark. + * @param url The url of the bookmark. + * @return True if the bookmark has been removed. + */ + bool removeBookmark(const std::string& zimId, const std::string& url); + Book& getBookById(const std::string& id); /** @@ -79,10 +98,18 @@ class Library * Write the library to a file. * * @param path the path of the file to write to. - * @return True if the library has been correctly save. + * @return True if the library has been correctly saved. */ bool writeToFile(const std::string& path); + /** + * Write the library bookmarks to a file. + * + * @param path the path of the file to write to. + * @return True if the library has been correctly saved. + */ + bool writeBookmarksToFile(const std::string& path); + /** * Get the number of book in the library. * @@ -113,6 +140,13 @@ class Library */ std::vector getBooksPublishers(); + /** + * Get all bookmarks. + * + * @return A list of bookmarks + */ + const std::vector& getBookmarks() { return m_bookmarks; } + /** * Get all book ids of the books in the library. * @@ -163,6 +197,7 @@ class Library size_t maxSize = 0); friend class OPDSDumper; + friend class libXMLDumper; }; } diff --git a/include/libxml_dumper.h b/include/libxml_dumper.h new file mode 100644 index 000000000..09cce2d5a --- /dev/null +++ b/include/libxml_dumper.h @@ -0,0 +1,83 @@ +/* + * Copyright 2018 Matthieu Gautier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef KIWIX_LIBXML_DUMPER_H +#define KIWIX_LIBXML_DUMPER_H + +#include +#include + +#include + +#include "library.h" + +namespace kiwix +{ + +/** + * A tool to dump a `Library` into a basic library.xml + * + */ +class LibXMLDumper +{ + public: + LibXMLDumper() = default; + LibXMLDumper(Library* library); + ~LibXMLDumper(); + + /** + * Dump the library.xml + * + * @param id The id of the library. + * @return The library.xml content. + */ + std::string dumpLibXMLContent(const std::vector& bookIds); + + + /** + * Dump the bookmark of the library. + * + * @return The bookmark.xml content. + */ + std::string dumpLibXMLBookmark(); + + /** + * Set the base directory used. + * + * @param baseDir the base directory to use. + */ + void setBaseDir(const std::string& baseDir) { this->baseDir = baseDir; } + + /** + * Set the library to dump. + * + * @param library The library to dump. + */ + void setLibrary(Library* library) { this->library = library; } + + protected: + kiwix::Library* library; + std::string baseDir; + private: + void handleBook(Book book, pugi::xml_node root_node); + void handleBookmark(Bookmark bookmark, pugi::xml_node root_node); +}; +} + +#endif // KIWIX_OPDS_DUMPER_H diff --git a/include/manager.h b/include/manager.h index 718bfb3a0..fae26ee6e 100644 --- a/include/manager.h +++ b/include/manager.h @@ -38,6 +38,7 @@ class LibraryManipulator { public: virtual ~LibraryManipulator() {} virtual bool addBookToLibrary(Book book) = 0; + virtual void addBookmarkToLibrary(Bookmark bookmark) = 0; }; class DefaultLibraryManipulator : public LibraryManipulator { @@ -48,6 +49,9 @@ class DefaultLibraryManipulator : public LibraryManipulator { bool addBookToLibrary(Book book) { return library->addBook(book); } + void addBookmarkToLibrary(Bookmark bookmark) { + library->addBookmark(bookmark); + } private: kiwix::Library* library; }; @@ -113,6 +117,15 @@ class Manager */ bool readOpds(const std::string& content, const std::string& urlHost); + + /** + * Load a bookmark file. + * + * @param path The path of the file to read. + * @return True if the content has been properly parsed. + */ + bool readBookmarkFile(const std::string& path); + /** * Add a book to the library. * diff --git a/include/meson.build b/include/meson.build index e7ab6eca8..a813dcf9c 100644 --- a/include/meson.build +++ b/include/meson.build @@ -1,8 +1,10 @@ headers = [ 'book.h', + 'bookmark.h', 'common.h', 'library.h', 'manager.h', + 'libxml_dumper.h', 'opds_dumper.h', 'downloader.h', 'reader.h', diff --git a/src/bookmark.cpp b/src/bookmark.cpp new file mode 100644 index 000000000..2ca1b623f --- /dev/null +++ b/src/bookmark.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Matthieu Gautier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include "bookmark.h" + +#include + +namespace kiwix +{ +/* Constructor */ +Bookmark::Bookmark() +{ +} + +/* Destructor */ +Bookmark::~Bookmark() +{ +} + +void Bookmark::updateFromXml(const pugi::xml_node& node) +{ + auto bookNode = node.child("book"); + m_bookId = bookNode.child("id").child_value(); + m_bookTitle = bookNode.child("title").child_value(); + m_language = bookNode.child("language").child_value(); + m_date = bookNode.child("date").child_value(); + m_title = node.child("title").child_value(); + m_url = node.child("url").child_value(); +} + +} diff --git a/src/library.cpp b/src/library.cpp index 34650c2f8..c4e9a9f5b 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -19,6 +19,7 @@ #include "library.h" #include "book.h" +#include "libxml_dumper.h" #include "common/base64.h" #include "common/regexTools.h" @@ -30,7 +31,7 @@ namespace kiwix { /* Constructor */ -Library::Library() : version(KIWIX_LIBRARY_VERSION) +Library::Library() { } /* Destructor */ @@ -43,31 +44,48 @@ bool Library::addBook(const Book& book) { /* Try to find it */ try { - auto& oldbook = books.at(book.getId()); + auto& oldbook = m_books.at(book.getId()); oldbook.update(book); return false; } catch (std::out_of_range&) { - books[book.getId()] = book; + m_books[book.getId()] = book; return true; } } +void Library::addBookmark(const Bookmark& bookmark) +{ + m_bookmarks.push_back(bookmark); +} + +bool Library::removeBookmark(const std::string& zimId, const std::string& url) +{ + for(auto it=m_bookmarks.begin(); it!=m_bookmarks.end(); it++) { + if (it->getBookId() == zimId && it->getUrl() == url) { + m_bookmarks.erase(it); + return true; + } + } + return false; +} + + bool Library::removeBookById(const std::string& id) { - return books.erase(id) == 1; + return m_books.erase(id) == 1; } Book& Library::getBookById(const std::string& id) { - return books.at(id); + return m_books.at(id); } unsigned int Library::getBookCount(const bool localBooks, const bool remoteBooks) { unsigned int result = 0; - for (auto& pair: books) { + for (auto& pair: m_books) { auto& book = pair.second; if ((!book.getPath().empty() && localBooks) || (book.getPath().empty() && remoteBooks)) { @@ -78,91 +96,15 @@ unsigned int Library::getBookCount(const bool localBooks, } bool Library::writeToFile(const std::string& path) { - pugi::xml_document doc; auto baseDir = removeLastPathElement(path, true, false); + LibXMLDumper dumper(this); + dumper.setBaseDir(baseDir); + return writeTextFile(path, dumper.dumpLibXMLContent(getBooksIds())); +} - /* Add the library node */ - pugi::xml_node libraryNode = doc.append_child("library"); - - if (!version.empty()) - libraryNode.append_attribute("version") = version.c_str(); - - /* Add each book */ - for (auto& pair: books) { - auto& book = pair.second; - if (!book.readOnly()) { - pugi::xml_node bookNode = libraryNode.append_child("book"); - bookNode.append_attribute("id") = book.getId().c_str(); - - if (!book.getPath().empty()) { - bookNode.append_attribute("path") = computeRelativePath( - baseDir, book.getPath()).c_str(); - } - - if (!book.getIndexPath().empty()) { - bookNode.append_attribute("indexPath") = computeRelativePath( - baseDir, book.getIndexPath()).c_str(); - bookNode.append_attribute("indexType") = "xapian"; - } - - if (book.getOrigId().empty()) { - if (!book.getTitle().empty()) - bookNode.append_attribute("title") = book.getTitle().c_str(); - - if (!book.getName().empty()) - bookNode.append_attribute("name") = book.getName().c_str(); - - if (!book.getTags().empty()) - bookNode.append_attribute("tags") = book.getTags().c_str(); - - if (!book.getDescription().empty()) - bookNode.append_attribute("description") = book.getDescription().c_str(); - - if (!book.getLanguage().empty()) - bookNode.append_attribute("language") = book.getLanguage().c_str(); - - if (!book.getCreator().empty()) - bookNode.append_attribute("creator") = book.getCreator().c_str(); - - if (!book.getPublisher().empty()) - bookNode.append_attribute("publisher") = book.getPublisher().c_str(); - - if (!book.getFavicon().empty()) - bookNode.append_attribute("favicon") = base64_encode(book.getFavicon()).c_str(); - - if (!book.getFaviconMimeType().empty()) - bookNode.append_attribute("faviconMimeType") - = book.getFaviconMimeType().c_str(); - } else { - bookNode.append_attribute("origId") = book.getOrigId().c_str(); - } - - if (!book.getDate().empty()) { - bookNode.append_attribute("date") = book.getDate().c_str(); - } - - if (!book.getUrl().empty()) { - bookNode.append_attribute("url") = book.getUrl().c_str(); - } - - if (book.getArticleCount()) - bookNode.append_attribute("articleCount") = to_string(book.getArticleCount()).c_str(); - - if (book.getMediaCount()) - bookNode.append_attribute("mediaCount") = to_string(book.getMediaCount()).c_str(); - - if (book.getSize()) { - bookNode.append_attribute("size") = to_string(book.getSize()>>10).c_str(); - } - - if (!book.getDownloadId().empty()) { - bookNode.append_attribute("downloadId") = book.getDownloadId().c_str(); - } - } - } - - /* saving file */ - return doc.save_file(path.c_str()); +bool Library::writeBookmarksToFile(const std::string& path) { + LibXMLDumper dumper(this); + return writeTextFile(path, dumper.dumpLibXMLBookmark()); } std::vector Library::getBooksLanguages() @@ -170,7 +112,7 @@ std::vector Library::getBooksLanguages() std::vector booksLanguages; std::map booksLanguagesMap; - for (auto& pair: books) { + for (auto& pair: m_books) { auto& book = pair.second; auto& language = book.getLanguage(); if (booksLanguagesMap.find(language) == booksLanguagesMap.end()) { @@ -189,7 +131,7 @@ std::vector Library::getBooksCreators() std::vector booksCreators; std::map booksCreatorsMap; - for (auto& pair: books) { + for (auto& pair: m_books) { auto& book = pair.second; auto& creator = book.getCreator(); if (booksCreatorsMap.find(creator) == booksCreatorsMap.end()) { @@ -208,7 +150,7 @@ std::vector Library::getBooksPublishers() std::vector booksPublishers; std::map booksPublishersMap; - for (auto& pair:books) { + for (auto& pair:m_books) { auto& book = pair.second; auto& publisher = book.getPublisher(); if (booksPublishersMap.find(publisher) == booksPublishersMap.end()) { @@ -226,7 +168,7 @@ std::vector Library::getBooksIds() { std::vector bookIds; - for (auto& pair: books) { + for (auto& pair: m_books) { bookIds.push_back(pair.first); } @@ -240,7 +182,7 @@ std::vector Library::filter(const std::string& search) } std::vector bookIds; - for(auto& pair:books) { + for(auto& pair:m_books) { auto& book = pair.second; if (matchRegex(book.getTitle(), "\\Q" + search + "\\E") || matchRegex(book.getDescription(), "\\Q" + search + "\\E")) { @@ -311,7 +253,7 @@ std::vector Library::listBooksIds( size_t maxSize) { std::vector bookIds; - for(auto& pair:books) { + for(auto& pair:m_books) { auto& book = pair.second; auto local = !book.getPath().empty(); if (mode & LOCAL && !local) diff --git a/src/libxml_dumper.cpp b/src/libxml_dumper.cpp new file mode 100644 index 000000000..11680c111 --- /dev/null +++ b/src/libxml_dumper.cpp @@ -0,0 +1,146 @@ +/* + * Copyright 2017 Matthieu Gautier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include "libxml_dumper.h" +#include "book.h" + +#include +#include +#include + +namespace kiwix +{ +/* Constructor */ +LibXMLDumper::LibXMLDumper(Library* library) + : library(library) +{ +} +/* Destructor */ +LibXMLDumper::~LibXMLDumper() +{ +} + +#define ADD_ATTRIBUTE(node, name, value) { (node).append_attribute((name)) = (value).c_str(); } +#define ADD_ATTR_NOT_EMPTY(node, name, value) { if (!(value).empty()) ADD_ATTRIBUTE(node, name, value); } + +void LibXMLDumper::handleBook(Book book, pugi::xml_node root_node) { + if (book.readOnly()) + return; + + auto entry_node = root_node.append_child("book"); + ADD_ATTRIBUTE(entry_node, "id", book.getId()); + + if (!book.getPath().empty()) { + ADD_ATTRIBUTE(entry_node, "path", computeRelativePath(baseDir, book.getPath())); + } + + if (!book.getIndexPath().empty()) { + ADD_ATTRIBUTE(entry_node, "indexPath", computeRelativePath(baseDir, book.getIndexPath())); + entry_node.append_attribute("indexType") = "xapian"; + } + + if (book.getOrigId().empty()) { + ADD_ATTR_NOT_EMPTY(entry_node, "title", book.getTitle()); + ADD_ATTR_NOT_EMPTY(entry_node, "name", book.getName()); + ADD_ATTR_NOT_EMPTY(entry_node, "tags", book.getTags()); + ADD_ATTR_NOT_EMPTY(entry_node, "description", book.getDescription()); + ADD_ATTR_NOT_EMPTY(entry_node, "language", book.getLanguage()); + ADD_ATTR_NOT_EMPTY(entry_node, "creator", book.getCreator()); + ADD_ATTR_NOT_EMPTY(entry_node, "publisher", book.getPublisher()); + ADD_ATTR_NOT_EMPTY(entry_node, "faviconMimeType", book.getFaviconMimeType()); + ADD_ATTR_NOT_EMPTY(entry_node, "language", book.getLanguage()); + ADD_ATTR_NOT_EMPTY(entry_node, "language", book.getLanguage()); + if (!book.getFavicon().empty()) + ADD_ATTRIBUTE(entry_node, "favicon", base64_encode(book.getFavicon())); + } else { + ADD_ATTRIBUTE(entry_node, "origId", book.getOrigId()); + } + + ADD_ATTR_NOT_EMPTY(entry_node, "date", book.getDate()); + ADD_ATTR_NOT_EMPTY(entry_node, "url", book.getUrl()); + + if (book.getArticleCount()) + ADD_ATTRIBUTE(entry_node, "articleCount", to_string(book.getArticleCount())); + + if (book.getMediaCount()) + ADD_ATTRIBUTE(entry_node, "mediaCount", to_string(book.getMediaCount())); + + if (book.getSize()) + ADD_ATTRIBUTE(entry_node, "size", to_string(book.getSize()>>10)); + + ADD_ATTR_NOT_EMPTY(entry_node, "downloadId", book.getDownloadId()); +} + +#define ADD_TEXT_ENTRY(node, child, value) (node).append_child((child)).append_child(pugi::node_pcdata).set_value((value).c_str()) + +void LibXMLDumper::handleBookmark(Bookmark bookmark, pugi::xml_node root_node) { + + auto entry_node = root_node.append_child("bookmark"); + auto book_node = entry_node.append_child("book"); + + try { + auto book = library->getBookById(bookmark.getBookId()); + ADD_TEXT_ENTRY(book_node, "id", book.getId()); + ADD_TEXT_ENTRY(book_node, "title", book.getTitle()); + ADD_TEXT_ENTRY(book_node, "language", book.getLanguage()); + ADD_TEXT_ENTRY(book_node, "date", book.getDate()); + } catch (...) { + ADD_TEXT_ENTRY(book_node, "id", bookmark.getBookId()); + ADD_TEXT_ENTRY(book_node, "title", bookmark.getBookTitle()); + ADD_TEXT_ENTRY(book_node, "language", bookmark.getLanguage()); + ADD_TEXT_ENTRY(book_node, "date", bookmark.getDate()); + } + ADD_TEXT_ENTRY(entry_node, "title", bookmark.getTitle()); + ADD_TEXT_ENTRY(entry_node, "url", bookmark.getUrl()); +} + + +std::string LibXMLDumper::dumpLibXMLContent(const std::vector& bookIds) +{ + pugi::xml_document doc; + + /* Add the library node */ + pugi::xml_node libraryNode = doc.append_child("library"); + + libraryNode.append_attribute("version") = KIWIX_LIBRARY_VERSION; + + if (library) { + for (auto& bookId: bookIds) { + handleBook(library->getBookById(bookId), libraryNode); + } + } + return nodeToString(libraryNode); +} + +std::string LibXMLDumper::dumpLibXMLBookmark() +{ + pugi::xml_document doc; + + /* Add the library node */ + pugi::xml_node bookmarksNode = doc.append_child("bookmarks"); + + if (library) { + for (auto& bookmark: library->getBookmarks()) { + handleBookmark(bookmark, bookmarksNode); + } + } + return nodeToString(bookmarksNode); +} + +} diff --git a/src/manager.cpp b/src/manager.cpp index 4fb0637e2..c7c45b7e0 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -221,4 +221,27 @@ bool Manager::readBookFromPath(const std::string& path, kiwix::Book* book) return true; } +bool Manager::readBookmarkFile(const std::string& path) +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(path.c_str()); + + if (!result) { + return false; + } + + pugi::xml_node libraryNode = doc.child("bookmarks"); + + for (pugi::xml_node node = libraryNode.child("bookmark"); node; + node = node.next_sibling("bookmark")) { + kiwix::Bookmark bookmark; + + bookmark.updateFromXml(node); + + manipulator->addBookmarkToLibrary(bookmark); + } + + return true; +} + } diff --git a/src/meson.build b/src/meson.build index deefdac8f..46f585b1c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,7 +1,9 @@ kiwix_sources = [ 'book.cpp', + 'bookmark.cpp', 'library.cpp', 'manager.cpp', + 'libxml_dumper.cpp', 'opds_dumper.cpp', 'downloader.cpp', 'reader.cpp',