Move back `Library::Impl` in `Library`.

As we now always use Library through a shared_ptr, we can make `Library`
not copiable and not movable.
So we can move back the data in `Library`, we don't care about move
constructor.
This commit is contained in:
Matthieu Gautier 2023-08-23 10:42:30 +02:00
parent f8e7c3d476
commit 5292f06fff
2 changed files with 84 additions and 106 deletions

View File

@ -34,6 +34,10 @@
#define KIWIX_LIBRARY_VERSION "20110515" #define KIWIX_LIBRARY_VERSION "20110515"
namespace Xapian {
class WritableDatabase;
};
namespace kiwix namespace kiwix
{ {
@ -173,6 +177,12 @@ class ZimSearcher : public zim::Searcher
std::mutex m_mutex; std::mutex m_mutex;
}; };
template<typename, typename>
class ConcurrentCache;
template<typename, typename>
class MultiKeyCache;
/** /**
* A Library store several books. * A Library store several books.
*/ */
@ -197,9 +207,9 @@ class Library: public std::enable_shared_from_this<Library>
* Library is not a copiable object. However it can be moved. * Library is not a copiable object. However it can be moved.
*/ */
Library(const Library& ) = delete; Library(const Library& ) = delete;
Library(Library&& ); Library(Library&& ) = delete;
void operator=(const Library& ) = delete; void operator=(const Library& ) = delete;
Library& operator=(Library&& ); Library& operator=(Library&& ) = delete;
/** /**
* Add a book to the library. * Add a book to the library.
@ -370,17 +380,29 @@ class Library: public std::enable_shared_from_this<Library>
private: // types private: // types
typedef const std::string& (Book::*BookStrPropMemFn)() const; typedef const std::string& (Book::*BookStrPropMemFn)() const;
struct Impl; struct Entry : Book
{
Library::Revision lastUpdatedRevision = 0;
};
private: // functions private: // functions
AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const; AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const;
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const; std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
BookIdCollection filterViaBookDB(const Filter& filter) const; BookIdCollection filterViaBookDB(const Filter& filter) const;
unsigned int getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const;
void updateBookDB(const Book& book); void updateBookDB(const Book& book);
void dropCache(const std::string& bookId); void dropCache(const std::string& bookId);
private: //data private: //data
std::unique_ptr<Impl> mp_impl; mutable std::mutex m_mutex;
Library::Revision m_revision;
std::map<std::string, Entry> m_books;
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
std::unique_ptr<ArchiveCache> mp_archiveCache;
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
std::unique_ptr<SearcherCache> mp_searcherCache;
std::vector<kiwix::Bookmark> m_bookmarks;
std::unique_ptr<Xapian::WritableDatabase> m_bookDB;
}; };
} }

View File

@ -39,6 +39,8 @@
namespace kiwix namespace kiwix
{ {
namespace namespace
{ {
@ -58,6 +60,8 @@ bool booksReferToTheSameArchive(const Book& book1, const Book& book2)
&& book1.getPath() == book2.getPath(); && book1.getPath() == book2.getPath();
} }
} // unnamed namespace
template<typename Key, typename Value> template<typename Key, typename Value>
class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value> class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
{ {
@ -79,47 +83,8 @@ class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
} }
}; };
} // unnamed namespace
struct Library::Impl
{
struct Entry : Book
{
Library::Revision lastUpdatedRevision = 0;
};
mutable std::mutex m_mutex;
Library::Revision m_revision;
std::map<std::string, Entry> m_books;
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
std::unique_ptr<ArchiveCache> mp_archiveCache;
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
std::unique_ptr<SearcherCache> mp_searcherCache;
std::vector<kiwix::Bookmark> m_bookmarks;
Xapian::WritableDatabase m_bookDB;
unsigned int getBookCount(const bool localBooks, const bool remoteBooks) const;
Impl();
~Impl();
Impl(Impl&& ) = delete;
Impl& operator=(Impl&& ) = delete;
};
Library::Impl::Impl()
: mp_archiveCache(new ArchiveCache(std::max(getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", 1), 1))),
mp_searcherCache(new SearcherCache(std::max(getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", 1), 1))),
m_bookDB("", Xapian::DB_BACKEND_INMEMORY)
{
}
Library::Impl::~Impl()
{
}
unsigned int unsigned int
Library::Impl::getBookCount(const bool localBooks, const bool remoteBooks) const Library::getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const
{ {
unsigned int result = 0; unsigned int result = 0;
for (auto& pair: m_books) { for (auto& pair: m_books) {
@ -134,50 +99,41 @@ Library::Impl::getBookCount(const bool localBooks, const bool remoteBooks) const
/* Constructor */ /* Constructor */
Library::Library() Library::Library()
: mp_impl(new Library::Impl) : mp_archiveCache(new ArchiveCache(std::max(getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", 1), 1))),
mp_searcherCache(new SearcherCache(std::max(getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", 1), 1))),
m_bookDB(new Xapian::WritableDatabase("", Xapian::DB_BACKEND_INMEMORY))
{ {
} }
Library::Library(Library&& other)
: mp_impl(std::move(other.mp_impl))
{
}
Library& Library::operator=(Library&& other)
{
mp_impl = std::move(other.mp_impl);
return *this;
}
/* Destructor */ /* Destructor */
Library::~Library() = default; Library::~Library() = default;
bool Library::addBook(const Book& book) bool Library::addBook(const Book& book)
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
++mp_impl->m_revision; ++m_revision;
/* Try to find it */ /* Try to find it */
updateBookDB(book); updateBookDB(book);
try { try {
auto& oldbook = mp_impl->m_books.at(book.getId()); auto& oldbook = m_books.at(book.getId());
if ( ! booksReferToTheSameArchive(oldbook, book) ) { if ( ! booksReferToTheSameArchive(oldbook, book) ) {
dropCache(book.getId()); dropCache(book.getId());
} }
oldbook.update(book); // XXX: This may have no effect if oldbook is readonly oldbook.update(book); // XXX: This may have no effect if oldbook is readonly
// XXX: Then m_bookDB will become out-of-sync with // XXX: Then m_bookDB will become out-of-sync with
// XXX: the real contents of the library. // XXX: the real contents of the library.
oldbook.lastUpdatedRevision = mp_impl->m_revision; oldbook.lastUpdatedRevision = m_revision;
return false; return false;
} catch (std::out_of_range&) { } catch (std::out_of_range&) {
auto& newEntry = mp_impl->m_books[book.getId()]; auto& newEntry = m_books[book.getId()];
static_cast<Book&>(newEntry) = book; static_cast<Book&>(newEntry) = book;
newEntry.lastUpdatedRevision = mp_impl->m_revision; newEntry.lastUpdatedRevision = m_revision;
size_t new_cache_size = static_cast<size_t>(std::ceil(mp_impl->getBookCount(true, true)*0.1)); size_t new_cache_size = static_cast<size_t>(std::ceil(getBookCount_not_protected(true, true)*0.1));
if (getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", -1) <= 0) { if (getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", -1) <= 0) {
mp_impl->mp_archiveCache->setMaxSize(new_cache_size); mp_archiveCache->setMaxSize(new_cache_size);
} }
if (getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", -1) <= 0) { if (getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", -1) <= 0) {
mp_impl->mp_searcherCache->setMaxSize(new_cache_size); mp_searcherCache->setMaxSize(new_cache_size);
} }
return true; return true;
} }
@ -185,16 +141,16 @@ bool Library::addBook(const Book& book)
void Library::addBookmark(const Bookmark& bookmark) void Library::addBookmark(const Bookmark& bookmark)
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
mp_impl->m_bookmarks.push_back(bookmark); m_bookmarks.push_back(bookmark);
} }
bool Library::removeBookmark(const std::string& zimId, const std::string& url) bool Library::removeBookmark(const std::string& zimId, const std::string& url)
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
for(auto it=mp_impl->m_bookmarks.begin(); it!=mp_impl->m_bookmarks.end(); it++) { for(auto it=m_bookmarks.begin(); it!=m_bookmarks.end(); it++) {
if (it->getBookId() == zimId && it->getUrl() == url) { if (it->getBookId() == zimId && it->getUrl() == url) {
mp_impl->m_bookmarks.erase(it); m_bookmarks.erase(it);
return true; return true;
} }
} }
@ -204,14 +160,14 @@ bool Library::removeBookmark(const std::string& zimId, const std::string& url)
void Library::dropCache(const std::string& id) void Library::dropCache(const std::string& id)
{ {
mp_impl->mp_archiveCache->drop(id); mp_archiveCache->drop(id);
mp_impl->mp_searcherCache->drop(id); mp_searcherCache->drop(id);
} }
bool Library::removeBookById(const std::string& id) bool Library::removeBookById(const std::string& id)
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
mp_impl->m_bookDB.delete_document("Q" + id); m_bookDB->delete_document("Q" + id);
dropCache(id); dropCache(id);
// We do not change the cache size here // We do not change the cache size here
// Most of the time, the book is remove in case of library refresh, it is // Most of the time, the book is remove in case of library refresh, it is
@ -219,25 +175,25 @@ bool Library::removeBookById(const std::string& id)
// Having a too big cache is not a problem here (or it would have been before) // Having a too big cache is not a problem here (or it would have been before)
// (And setMaxSize doesn't actually reduce the cache size, extra cached items // (And setMaxSize doesn't actually reduce the cache size, extra cached items
// will be removed in put or getOrPut). // will be removed in put or getOrPut).
const bool bookWasRemoved = mp_impl->m_books.erase(id) == 1; const bool bookWasRemoved = m_books.erase(id) == 1;
if ( bookWasRemoved ) { if ( bookWasRemoved ) {
++mp_impl->m_revision; ++m_revision;
} }
return bookWasRemoved; return bookWasRemoved;
} }
Library::Revision Library::getRevision() const Library::Revision Library::getRevision() const
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
return mp_impl->m_revision; return m_revision;
} }
uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision) uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
{ {
BookIdCollection booksToRemove; BookIdCollection booksToRemove;
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
for ( const auto& entry : mp_impl->m_books) { for ( const auto& entry : m_books) {
if ( entry.second.lastUpdatedRevision <= libraryRevision ) { if ( entry.second.lastUpdatedRevision <= libraryRevision ) {
booksToRemove.push_back(entry.first); booksToRemove.push_back(entry.first);
} }
@ -256,12 +212,12 @@ const Book& Library::getBookById(const std::string& id) const
{ {
// XXX: Doesn't make sense to lock this operation since it cannot // XXX: Doesn't make sense to lock this operation since it cannot
// XXX: guarantee thread-safety because of its return type // XXX: guarantee thread-safety because of its return type
return mp_impl->m_books.at(id); return m_books.at(id);
} }
Book Library::getBookByIdThreadSafe(const std::string& id) const Book Library::getBookByIdThreadSafe(const std::string& id) const
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
return getBookById(id); return getBookById(id);
} }
@ -269,7 +225,7 @@ const Book& Library::getBookByPath(const std::string& path) const
{ {
// XXX: Doesn't make sense to lock this operation since it cannot // XXX: Doesn't make sense to lock this operation since it cannot
// XXX: guarantee thread-safety because of its return type // XXX: guarantee thread-safety because of its return type
for(auto& it: mp_impl->m_books) { for(auto& it: m_books) {
auto& book = it.second; auto& book = it.second;
if (book.getPath() == path) if (book.getPath() == path)
return book; return book;
@ -282,7 +238,7 @@ const Book& Library::getBookByPath(const std::string& path) const
std::shared_ptr<zim::Archive> Library::getArchiveById(const std::string& id) std::shared_ptr<zim::Archive> Library::getArchiveById(const std::string& id)
{ {
try { try {
return mp_impl->mp_archiveCache->getOrPut(id, return mp_archiveCache->getOrPut(id,
[&](){ [&](){
auto book = getBookById(id); auto book = getBookById(id);
if (!book.isPathValid()) { if (!book.isPathValid()) {
@ -299,7 +255,7 @@ std::shared_ptr<ZimSearcher> Library::getSearcherByIds(const BookIdSet& ids)
{ {
assert(!ids.empty()); assert(!ids.empty());
try { try {
return mp_impl->mp_searcherCache->getOrPut(ids, return mp_searcherCache->getOrPut(ids,
[&](){ [&](){
std::vector<zim::Archive> archives; std::vector<zim::Archive> archives;
for(auto& id:ids) { for(auto& id:ids) {
@ -319,8 +275,8 @@ std::shared_ptr<ZimSearcher> Library::getSearcherByIds(const BookIdSet& ids)
unsigned int Library::getBookCount(const bool localBooks, unsigned int Library::getBookCount(const bool localBooks,
const bool remoteBooks) const const bool remoteBooks) const
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
return mp_impl->getBookCount(localBooks, remoteBooks); return getBookCount_not_protected(localBooks, remoteBooks);
} }
bool Library::writeToFile(const std::string& path) const bool Library::writeToFile(const std::string& path) const
@ -332,7 +288,7 @@ bool Library::writeToFile(const std::string& path) const
dumper.setBaseDir(baseDir); dumper.setBaseDir(baseDir);
std::string xml; std::string xml;
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
xml = dumper.dumpLibXMLContent(allBookIds); xml = dumper.dumpLibXMLContent(allBookIds);
}; };
return writeTextFile(path, xml); return writeTextFile(path, xml);
@ -348,10 +304,10 @@ bool Library::writeBookmarksToFile(const std::string& path) const
Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) const Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) const
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
AttributeCounts propValueCounts; AttributeCounts propValueCounts;
for (const auto& pair: mp_impl->m_books) { for (const auto& pair: m_books) {
const auto& book = pair.second; const auto& book = pair.second;
if (book.getOrigId().empty()) { if (book.getOrigId().empty()) {
propValueCounts[(book.*p)()] += 1; propValueCounts[(book.*p)()] += 1;
@ -380,10 +336,10 @@ std::vector<std::string> Library::getBooksLanguages() const
Library::AttributeCounts Library::getBooksLanguagesWithCounts() const Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
AttributeCounts langsWithCounts; AttributeCounts langsWithCounts;
for (const auto& pair: mp_impl->m_books) { for (const auto& pair: m_books) {
const auto& book = pair.second; const auto& book = pair.second;
if (book.getOrigId().empty()) { if (book.getOrigId().empty()) {
for ( const auto& lang : book.getLanguages() ) { for ( const auto& lang : book.getLanguages() ) {
@ -396,10 +352,10 @@ Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
std::vector<std::string> Library::getBooksCategories() const std::vector<std::string> Library::getBooksCategories() const
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
std::set<std::string> categories; std::set<std::string> categories;
for (const auto& pair: mp_impl->m_books) { for (const auto& pair: m_books) {
const auto& book = pair.second; const auto& book = pair.second;
const auto& c = book.getCategory(); const auto& c = book.getCategory();
if ( !c.empty() ) { if ( !c.empty() ) {
@ -423,12 +379,12 @@ std::vector<std::string> Library::getBooksPublishers() const
const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks) const const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks) const
{ {
if (!onlyValidBookmarks) { if (!onlyValidBookmarks) {
return mp_impl->m_bookmarks; return m_bookmarks;
} }
std::vector<kiwix::Bookmark> validBookmarks; std::vector<kiwix::Bookmark> validBookmarks;
auto booksId = getBooksIds(); auto booksId = getBooksIds();
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
for(auto& bookmark:mp_impl->m_bookmarks) { for(auto& bookmark:m_bookmarks) {
if (std::find(booksId.begin(), booksId.end(), bookmark.getBookId()) != booksId.end()) { if (std::find(booksId.begin(), booksId.end(), bookmark.getBookId()) != booksId.end()) {
validBookmarks.push_back(bookmark); validBookmarks.push_back(bookmark);
} }
@ -438,10 +394,10 @@ const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks
Library::BookIdCollection Library::getBooksIds() const Library::BookIdCollection Library::getBooksIds() const
{ {
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
BookIdCollection bookIds; BookIdCollection bookIds;
for (auto& pair: mp_impl->m_books) { for (auto& pair: m_books) {
bookIds.push_back(pair.first); bookIds.push_back(pair.first);
} }
@ -496,7 +452,7 @@ void Library::updateBookDB(const Book& book)
doc.set_data(book.getId()); doc.set_data(book.getId());
mp_impl->m_bookDB.replace_document(idterm, doc); m_bookDB->replace_document(idterm, doc);
} }
namespace namespace
@ -644,10 +600,10 @@ Library::BookIdCollection Library::filterViaBookDB(const Filter& filter) const
BookIdCollection bookIds; BookIdCollection bookIds;
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
Xapian::Enquire enquire(mp_impl->m_bookDB); Xapian::Enquire enquire(*m_bookDB);
enquire.set_query(query); enquire.set_query(query);
const auto results = enquire.get_mset(0, mp_impl->m_books.size()); const auto results = enquire.get_mset(0, m_books.size());
for ( auto it = results.begin(); it != results.end(); ++it ) { for ( auto it = results.begin(); it != results.end(); ++it ) {
bookIds.push_back(it.get_document().get_data()); bookIds.push_back(it.get_document().get_data());
} }
@ -659,9 +615,9 @@ Library::BookIdCollection Library::filter(const Filter& filter) const
{ {
BookIdCollection result; BookIdCollection result;
const auto preliminaryResult = filterViaBookDB(filter); const auto preliminaryResult = filterViaBookDB(filter);
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
for(auto id : preliminaryResult) { for(auto id : preliminaryResult) {
if(filter.accept(mp_impl->m_books.at(id))) { if(filter.accept(m_books.at(id))) {
result.push_back(id); result.push_back(id);
} }
} }
@ -733,7 +689,7 @@ void Library::sort(BookIdCollection& bookIds, supportedListSortBy sort, bool asc
// NOTE: for the entire duration of the sort. Will need to obtain (under a // NOTE: for the entire duration of the sort. Will need to obtain (under a
// NOTE: lock) the required atributes from the books once, and then the // NOTE: lock) the required atributes from the books once, and then the
// NOTE: sorting will run on a copy of data without locking. // NOTE: sorting will run on a copy of data without locking.
std::lock_guard<std::mutex> lock(mp_impl->m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
switch(sort) { switch(sort) {
case TITLE: case TITLE:
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this, ascending)); std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this, ascending));