Add bookmarks support.

The library now contains (simple) methods to handle bookmarks.
The bookmark are stored in a separate xml file.

Bookmark are mainly a couple (`zimId`, `articleUrl`).
However, in the xml we store a bit more data :
- The article's title (for display)
- The book's title, lang and date (for potential update of zim files)
This commit is contained in:
Matthieu Gautier 2018-12-02 15:42:21 +01:00
parent b5ce60a627
commit 12498e2cfe
10 changed files with 273 additions and 16 deletions

68
include/bookmark.h Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright 2018 Matthieu Gautier <mgautier@kymeria.fr>
*
* 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 <string>
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

View File

@ -24,12 +24,14 @@
#include <vector> #include <vector>
#include <map> #include <map>
#include "book.h"
#include "bookmark.h"
#define KIWIX_LIBRARY_VERSION "20110515" #define KIWIX_LIBRARY_VERSION "20110515"
namespace kiwix namespace kiwix
{ {
class Book;
class OPDSDumper; class OPDSDumper;
enum supportedListSortBy { UNSORTED, TITLE, SIZE, DATE, CREATOR, PUBLISHER }; enum supportedListSortBy { UNSORTED, TITLE, SIZE, DATE, CREATOR, PUBLISHER };
@ -47,7 +49,9 @@ enum supportedListMode {
*/ */
class Library class Library
{ {
std::map<std::string, kiwix::Book> books; std::map<std::string, kiwix::Book> m_books;
std::vector<kiwix::Bookmark> m_bookmarks;
public: public:
Library(); Library();
~Library(); ~Library();
@ -64,6 +68,22 @@ class Library
*/ */
bool addBook(const Book& book); 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); Book& getBookById(const std::string& id);
/** /**
@ -78,10 +98,18 @@ class Library
* Write the library to a file. * Write the library to a file.
* *
* @param path the path of the file to write to. * @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); 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. * Get the number of book in the library.
* *
@ -112,6 +140,13 @@ class Library
*/ */
std::vector<std::string> getBooksPublishers(); std::vector<std::string> getBooksPublishers();
/**
* Get all bookmarks.
*
* @return A list of bookmarks
*/
const std::vector<kiwix::Bookmark>& getBookmarks() { return m_bookmarks; }
/** /**
* Get all book ids of the books in the library. * Get all book ids of the books in the library.
* *

View File

@ -49,6 +49,14 @@ class LibXMLDumper
*/ */
std::string dumpLibXMLContent(const std::vector<std::string>& bookIds); std::string dumpLibXMLContent(const std::vector<std::string>& bookIds);
/**
* Dump the bookmark of the library.
*
* @return The bookmark.xml content.
*/
std::string dumpLibXMLBookmark();
/** /**
* Set the base directory used. * Set the base directory used.
* *
@ -68,6 +76,7 @@ class LibXMLDumper
std::string baseDir; std::string baseDir;
private: private:
void handleBook(Book book, pugi::xml_node root_node); void handleBook(Book book, pugi::xml_node root_node);
void handleBookmark(Bookmark bookmark, pugi::xml_node root_node);
}; };
} }

View File

@ -38,6 +38,7 @@ class LibraryManipulator {
public: public:
virtual ~LibraryManipulator() {} virtual ~LibraryManipulator() {}
virtual bool addBookToLibrary(Book book) = 0; virtual bool addBookToLibrary(Book book) = 0;
virtual void addBookmarkToLibrary(Bookmark bookmark) = 0;
}; };
class DefaultLibraryManipulator : public LibraryManipulator { class DefaultLibraryManipulator : public LibraryManipulator {
@ -48,6 +49,9 @@ class DefaultLibraryManipulator : public LibraryManipulator {
bool addBookToLibrary(Book book) { bool addBookToLibrary(Book book) {
return library->addBook(book); return library->addBook(book);
} }
void addBookmarkToLibrary(Bookmark bookmark) {
library->addBookmark(bookmark);
}
private: private:
kiwix::Library* library; kiwix::Library* library;
}; };
@ -113,6 +117,15 @@ class Manager
*/ */
bool readOpds(const std::string& content, const std::string& urlHost); 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. * Add a book to the library.
* *

View File

@ -1,5 +1,6 @@
headers = [ headers = [
'book.h', 'book.h',
'bookmark.h',
'common.h', 'common.h',
'library.h', 'library.h',
'manager.h', 'manager.h',

47
src/bookmark.cpp Normal file
View File

@ -0,0 +1,47 @@
/*
* Copyright 2018 Matthieu Gautier <mgautier@kymeria.fr>
*
* 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 <pugixml.hpp>
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();
}
}

View File

@ -44,31 +44,48 @@ bool Library::addBook(const Book& book)
{ {
/* Try to find it */ /* Try to find it */
try { try {
auto& oldbook = books.at(book.getId()); auto& oldbook = m_books.at(book.getId());
oldbook.update(book); oldbook.update(book);
return false; return false;
} catch (std::out_of_range&) { } catch (std::out_of_range&) {
books[book.getId()] = book; m_books[book.getId()] = book;
return true; 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) 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) Book& Library::getBookById(const std::string& id)
{ {
return books.at(id); return m_books.at(id);
} }
unsigned int Library::getBookCount(const bool localBooks, unsigned int Library::getBookCount(const bool localBooks,
const bool remoteBooks) const bool remoteBooks)
{ {
unsigned int result = 0; unsigned int result = 0;
for (auto& pair: books) { for (auto& pair: m_books) {
auto& book = pair.second; auto& book = pair.second;
if ((!book.getPath().empty() && localBooks) if ((!book.getPath().empty() && localBooks)
|| (book.getPath().empty() && remoteBooks)) { || (book.getPath().empty() && remoteBooks)) {
@ -85,12 +102,17 @@ bool Library::writeToFile(const std::string& path) {
return writeTextFile(path, dumper.dumpLibXMLContent(getBooksIds())); return writeTextFile(path, dumper.dumpLibXMLContent(getBooksIds()));
} }
bool Library::writeBookmarksToFile(const std::string& path) {
LibXMLDumper dumper(this);
return writeTextFile(path, dumper.dumpLibXMLBookmark());
}
std::vector<std::string> Library::getBooksLanguages() std::vector<std::string> Library::getBooksLanguages()
{ {
std::vector<std::string> booksLanguages; std::vector<std::string> booksLanguages;
std::map<std::string, bool> booksLanguagesMap; std::map<std::string, bool> booksLanguagesMap;
for (auto& pair: books) { for (auto& pair: m_books) {
auto& book = pair.second; auto& book = pair.second;
auto& language = book.getLanguage(); auto& language = book.getLanguage();
if (booksLanguagesMap.find(language) == booksLanguagesMap.end()) { if (booksLanguagesMap.find(language) == booksLanguagesMap.end()) {
@ -109,7 +131,7 @@ std::vector<std::string> Library::getBooksCreators()
std::vector<std::string> booksCreators; std::vector<std::string> booksCreators;
std::map<std::string, bool> booksCreatorsMap; std::map<std::string, bool> booksCreatorsMap;
for (auto& pair: books) { for (auto& pair: m_books) {
auto& book = pair.second; auto& book = pair.second;
auto& creator = book.getCreator(); auto& creator = book.getCreator();
if (booksCreatorsMap.find(creator) == booksCreatorsMap.end()) { if (booksCreatorsMap.find(creator) == booksCreatorsMap.end()) {
@ -128,7 +150,7 @@ std::vector<std::string> Library::getBooksPublishers()
std::vector<std::string> booksPublishers; std::vector<std::string> booksPublishers;
std::map<std::string, bool> booksPublishersMap; std::map<std::string, bool> booksPublishersMap;
for (auto& pair:books) { for (auto& pair:m_books) {
auto& book = pair.second; auto& book = pair.second;
auto& publisher = book.getPublisher(); auto& publisher = book.getPublisher();
if (booksPublishersMap.find(publisher) == booksPublishersMap.end()) { if (booksPublishersMap.find(publisher) == booksPublishersMap.end()) {
@ -146,7 +168,7 @@ std::vector<std::string> Library::getBooksIds()
{ {
std::vector<std::string> bookIds; std::vector<std::string> bookIds;
for (auto& pair: books) { for (auto& pair: m_books) {
bookIds.push_back(pair.first); bookIds.push_back(pair.first);
} }
@ -160,7 +182,7 @@ std::vector<std::string> Library::filter(const std::string& search)
} }
std::vector<std::string> bookIds; std::vector<std::string> bookIds;
for(auto& pair:books) { for(auto& pair:m_books) {
auto& book = pair.second; auto& book = pair.second;
if (matchRegex(book.getTitle(), "\\Q" + search + "\\E") if (matchRegex(book.getTitle(), "\\Q" + search + "\\E")
|| matchRegex(book.getDescription(), "\\Q" + search + "\\E")) { || matchRegex(book.getDescription(), "\\Q" + search + "\\E")) {
@ -231,7 +253,7 @@ std::vector<std::string> Library::listBooksIds(
size_t maxSize) { size_t maxSize) {
std::vector<std::string> bookIds; std::vector<std::string> bookIds;
for(auto& pair:books) { for(auto& pair:m_books) {
auto& book = pair.second; auto& book = pair.second;
auto local = !book.getPath().empty(); auto local = !book.getPath().empty();
if (mode & LOCAL && !local) if (mode & LOCAL && !local)

View File

@ -87,7 +87,31 @@ void LibXMLDumper::handleBook(Book book, pugi::xml_node root_node) {
ADD_ATTR_NOT_EMPTY(entry_node, "downloadId", book.getDownloadId()); ADD_ATTR_NOT_EMPTY(entry_node, "downloadId", book.getDownloadId());
} }
string LibXMLDumper::dumpLibXMLContent(const std::vector<std::string>& bookIds) #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<std::string>& bookIds)
{ {
pugi::xml_document doc; pugi::xml_document doc;
@ -101,8 +125,22 @@ string LibXMLDumper::dumpLibXMLContent(const std::vector<std::string>& bookIds)
handleBook(library->getBookById(bookId), libraryNode); handleBook(library->getBookById(bookId), libraryNode);
} }
} }
return nodeToString(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);
}
} }

View File

@ -221,4 +221,27 @@ bool Manager::readBookFromPath(const std::string& path, kiwix::Book* book)
return true; 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;
}
} }

View File

@ -1,5 +1,6 @@
kiwix_sources = [ kiwix_sources = [
'book.cpp', 'book.cpp',
'bookmark.cpp',
'library.cpp', 'library.cpp',
'manager.cpp', 'manager.cpp',
'libxml_dumper.cpp', 'libxml_dumper.cpp',