summaryrefslogtreecommitdiffstats
path: root/fbreader/src/library/Library.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'fbreader/src/library/Library.cpp')
-rw-r--r--fbreader/src/library/Library.cpp439
1 files changed, 439 insertions, 0 deletions
diff --git a/fbreader/src/library/Library.cpp b/fbreader/src/library/Library.cpp
new file mode 100644
index 0000000..8ee1f36
--- /dev/null
+++ b/fbreader/src/library/Library.cpp
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2004-2012 Geometer Plus <[email protected]>
+ *
+ * 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 2 of the License, or
+ * (at your option) 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 <queue>
+#include <algorithm>
+
+#include <ZLibrary.h>
+#include <ZLStringUtil.h>
+#include <ZLFile.h>
+#include <ZLDir.h>
+#include <ZLDialogManager.h>
+
+#include "Library.h"
+#include "Book.h"
+#include "Author.h"
+#include "Tag.h"
+
+#include "../formats/FormatPlugin.h"
+
+#include "../database/booksdb/BooksDBUtil.h"
+#include "../database/booksdb/BooksDB.h"
+
+shared_ptr<Library> Library::ourInstance;
+const std::size_t Library::MaxRecentListSize = 10;
+
+Library &Library::Instance() {
+ if (ourInstance.isNull()) {
+ ourInstance = new Library();
+ }
+ return *ourInstance;
+}
+
+static const std::string OPTIONS = "Options";
+
+Library::Library() :
+ PathOption(ZLCategoryKey::CONFIG, OPTIONS, "BookPath", ""),
+ ScanSubdirsOption(ZLCategoryKey::CONFIG, OPTIONS, "ScanSubdirs", true),
+ CollectAllBooksOption(ZLCategoryKey::CONFIG, OPTIONS, "CollectAllBooks", false),
+ myBuildMode(BUILD_ALL),
+ myRevision(0) {
+ BooksDBUtil::getRecentBooks(myRecentBooks);
+}
+
+void Library::collectBookFileNames(std::set<std::string> &bookFileNames) const {
+ std::set<std::string> dirs;
+ collectDirNames(dirs);
+
+ while (!dirs.empty()) {
+ std::string dirname = *dirs.begin();
+ dirs.erase(dirs.begin());
+
+ ZLFile dirfile(dirname);
+ std::vector<std::string> files;
+ bool inZip = false;
+
+ shared_ptr<ZLDir> dir = dirfile.directory();
+ if (dir.isNull()) {
+ continue;
+ }
+
+ if (dirfile.isArchive()) {
+ ZLFile phys(dirfile.physicalFilePath());
+ if (!BooksDBUtil::checkInfo(phys)) {
+ BooksDBUtil::resetZipInfo(phys);
+ BooksDBUtil::saveInfo(phys);
+ }
+ BooksDBUtil::listZipEntries(dirfile, files);
+ inZip = true;
+ } else {
+ dir->collectFiles(files, true);
+ }
+ if (!files.empty()) {
+ const bool collectBookWithoutMetaInfo = CollectAllBooksOption.value();
+ for (std::vector<std::string>::const_iterator jt = files.begin(); jt != files.end(); ++jt) {
+ const std::string fileName = (inZip) ? (*jt) : (dir->itemPath(*jt));
+ ZLFile file(fileName);
+ if (PluginCollection::Instance().plugin(file, !collectBookWithoutMetaInfo) != 0) {
+ bookFileNames.insert(fileName);
+ } else if (file.isArchive()) {
+ if (myScanSubdirs || !inZip) {
+ dirs.insert(fileName);
+ }
+ }
+ }
+ }
+ }
+}
+
+void Library::rebuildBookSet() const {
+ myBooks.clear();
+ myExternalBooks.clear();
+
+ std::map<std::string, shared_ptr<Book> > booksMap;
+ BooksDBUtil::getBooks(booksMap);
+
+ std::set<std::string> fileNamesSet;
+ collectBookFileNames(fileNamesSet);
+
+ // collect books from book path
+ for (std::set<std::string>::iterator it = fileNamesSet.begin(); it != fileNamesSet.end(); ++it) {
+ std::map<std::string, shared_ptr<Book> >::iterator jt = booksMap.find(*it);
+ if (jt == booksMap.end()) {
+ insertIntoBookSet(BooksDBUtil::getBook(*it));
+ } else {
+ insertIntoBookSet(jt->second);
+ booksMap.erase(jt);
+ }
+ }
+
+ // other books from our database
+ for (std::map<std::string, shared_ptr<Book> >::iterator jt = booksMap.begin(); jt != booksMap.end(); ++jt) {
+ shared_ptr<Book> book = jt->second;
+ if (!book.isNull()) {
+ if (BooksDB::Instance().checkBookList(*book)) {
+ insertIntoBookSet(book);
+ myExternalBooks.insert(book);
+ }
+ }
+ }
+}
+
+std::size_t Library::revision() const {
+ if (myBuildMode == BUILD_NOTHING &&
+ (myScanSubdirs != ScanSubdirsOption.value() ||
+ myPath != PathOption.value())) {
+ myPath = PathOption.value();
+ myScanSubdirs = ScanSubdirsOption.value();
+ myBuildMode = BUILD_ALL;
+ }
+
+ return (myBuildMode == BUILD_NOTHING) ? myRevision : myRevision + 1;
+}
+
+class LibrarySynchronizer : public DBRunnable {
+
+public:
+ LibrarySynchronizer(Library::BuildMode mode) : myBuildMode(mode) { }
+
+private:
+ bool run() {
+ Library &library = Library::Instance();
+
+ if (myBuildMode & Library::BUILD_COLLECT_FILES_INFO) {
+ library.rebuildBookSet();
+ }
+
+ if (myBuildMode & Library::BUILD_UPDATE_BOOKS_INFO) {
+ library.rebuildMaps();
+ }
+ return true;
+ }
+
+private:
+ const Library::BuildMode myBuildMode;
+};
+
+class LibrarySynchronizerWrapper : public ZLRunnable {
+
+public:
+ LibrarySynchronizerWrapper(Library::BuildMode mode) : myRunnable(mode) { }
+
+private:
+ void run() {
+ BooksDB::Instance().executeAsTransaction(myRunnable);
+ }
+
+private:
+ LibrarySynchronizer myRunnable;
+};
+
+void Library::synchronize() const {
+ if (myScanSubdirs != ScanSubdirsOption.value() ||
+ myPath != PathOption.value()) {
+ myPath = PathOption.value();
+ myScanSubdirs = ScanSubdirsOption.value();
+ myBuildMode = BUILD_ALL;
+ }
+
+ if (myBuildMode == BUILD_NOTHING) {
+ return;
+ }
+
+ LibrarySynchronizerWrapper synchronizer(myBuildMode);
+ myBuildMode = BUILD_NOTHING;
+ ZLDialogManager::Instance().wait(ZLResourceKey("loadingBookList"), synchronizer);
+
+ ++myRevision;
+}
+
+void Library::rebuildMaps() const {
+ myAuthors.clear();
+ myBooksByAuthor.clear();
+ myTags.clear();
+ myBooksByTag.clear();
+
+ for (BookSet::const_iterator it = myBooks.begin(); it != myBooks.end(); ++it) {
+ if ((*it).isNull()) {
+ continue;
+ }
+
+ const AuthorList &bookAuthors = (*it)->authors();
+ if (bookAuthors.empty()) {
+ myBooksByAuthor[0].push_back(*it);
+ } else {
+ for(AuthorList::const_iterator jt = bookAuthors.begin(); jt != bookAuthors.end(); ++jt) {
+ myBooksByAuthor[*jt].push_back(*it);
+ }
+ }
+
+ const TagList &bookTags = (*it)->tags();
+ if (bookTags.empty()) {
+ myBooksByTag[0].push_back(*it);
+ } else {
+ for(TagList::const_iterator kt = bookTags.begin(); kt != bookTags.end(); ++kt) {
+ myBooksByTag[*kt].push_back(*it);
+ }
+ }
+ }
+ for (BooksByAuthor::iterator mit = myBooksByAuthor.begin(); mit != myBooksByAuthor.end(); ++mit) {
+ myAuthors.push_back(mit->first);
+ std::sort(mit->second.begin(), mit->second.end(), BookComparator());
+ }
+ for (BooksByTag::iterator mjt = myBooksByTag.begin(); mjt != myBooksByTag.end(); ++mjt) {
+ myTags.push_back(mjt->first);
+ std::sort(mjt->second.begin(), mjt->second.end(), BookComparator());
+ }
+}
+
+void Library::collectDirNames(std::set<std::string> &nameSet) const {
+ std::queue<std::string> nameQueue;
+
+ std::string path = myPath;
+ int pos = path.find(ZLibrary::PathDelimiter);
+ while (pos != -1) {
+ nameQueue.push(path.substr(0, pos));
+ path.erase(0, pos + 1);
+ pos = path.find(ZLibrary::PathDelimiter);
+ }
+ if (!path.empty()) {
+ nameQueue.push(path);
+ }
+
+ std::set<std::string> resolvedNameSet;
+ while (!nameQueue.empty()) {
+ std::string name = nameQueue.front();
+ nameQueue.pop();
+ ZLFile f(name);
+ const std::string resolvedName = f.resolvedPath();
+ if (resolvedNameSet.find(resolvedName) == resolvedNameSet.end()) {
+ if (myScanSubdirs) {
+ shared_ptr<ZLDir> dir = f.directory();
+ if (!dir.isNull()) {
+ std::vector<std::string> subdirs;
+ dir->collectSubDirs(subdirs, true);
+ for (std::vector<std::string>::const_iterator it = subdirs.begin(); it != subdirs.end(); ++it) {
+ nameQueue.push(dir->itemPath(*it));
+ }
+ }
+ }
+ resolvedNameSet.insert(resolvedName);
+ nameSet.insert(name);
+ }
+ }
+}
+
+void Library::updateBook(shared_ptr<Book> book) {
+ BooksDB::Instance().saveBook(book);
+ myBuildMode = (BuildMode)(myBuildMode | BUILD_UPDATE_BOOKS_INFO);
+}
+
+void Library::addBook(shared_ptr<Book> book) {
+ if (!book.isNull()) {
+ BooksDB::Instance().saveBook(book);
+ insertIntoBookSet(book);
+ myBuildMode = (BuildMode)(myBuildMode | BUILD_UPDATE_BOOKS_INFO);
+ }
+}
+
+void Library::removeBook(shared_ptr<Book> book) {
+ if (!book.isNull()) {
+ BookSet::iterator it = myBooks.find(book);
+ if (it != myBooks.end()) {
+ myBooks.erase(it);
+ myBuildMode = (BuildMode)(myBuildMode | BUILD_UPDATE_BOOKS_INFO);
+ }
+ BooksDB::Instance().deleteFromBookList(*book);
+ bool recentListChanged = false;
+ for (BookList::iterator it = myRecentBooks.begin(); it != myRecentBooks.end();) {
+ if ((*it)->file() == book->file()) {
+ it = myRecentBooks.erase(it);
+ recentListChanged = true;
+ } else {
+ ++it;
+ }
+ }
+ if (recentListChanged) {
+ BooksDB::Instance().saveRecentBooks(myRecentBooks);
+ }
+ }
+}
+
+const AuthorList &Library::authors() const {
+ synchronize();
+ return myAuthors;
+}
+
+const TagList &Library::tags() const {
+ synchronize();
+ return myTags;
+}
+
+const BookList &Library::books(shared_ptr<Author> author) const {
+ synchronize();
+ return myBooksByAuthor[author];
+}
+
+const BookList &Library::books(shared_ptr<Tag> tag) const {
+ synchronize();
+ return myBooksByTag[tag];
+}
+
+void Library::collectSeriesTitles(shared_ptr<Author> author, std::set<std::string> &titles) const {
+ const BookList &bookList = books(author);
+ for (BookList::const_iterator it = bookList.begin(); it != bookList.end(); ++it) {
+ const std::string &current = (*it)->seriesTitle();
+ if (!current.empty()) {
+ titles.insert(current);
+ }
+ }
+}
+
+void Library::removeTag(shared_ptr<Tag> tag, bool includeSubTags) {
+ for (BookSet::const_iterator it = myBooks.begin(); it != myBooks.end(); ++it) {
+ if ((*it)->removeTag(tag, includeSubTags)) {
+ BooksDB::Instance().saveTags(*it);
+ }
+ }
+ myBuildMode = (BuildMode)(myBuildMode | BUILD_UPDATE_BOOKS_INFO);
+}
+
+void Library::renameTag(shared_ptr<Tag> from, shared_ptr<Tag> to, bool includeSubTags) {
+ if (to != from) {
+ synchronize();
+ for (BookSet::const_iterator it = myBooks.begin(); it != myBooks.end(); ++it) {
+ BooksDBUtil::renameTag(*it, from, to, includeSubTags);
+ }
+ }
+ myBuildMode = (BuildMode)(myBuildMode | BUILD_UPDATE_BOOKS_INFO);
+}
+
+void Library::cloneTag(shared_ptr<Tag> from, shared_ptr<Tag> to, bool includeSubTags) {
+ if (to != from) {
+ synchronize();
+ for (BookSet::const_iterator it = myBooks.begin(); it != myBooks.end(); ++it) {
+ BooksDBUtil::cloneTag(*it, from, to, includeSubTags);
+ }
+ }
+ myBuildMode = (BuildMode)(myBuildMode | BUILD_UPDATE_BOOKS_INFO);
+}
+
+bool Library::hasBooks(shared_ptr<Tag> tag) const {
+ synchronize();
+ const BooksByTag::const_iterator it = myBooksByTag.find(tag);
+ return it != myBooksByTag.end() && !it->second.empty();
+}
+
+bool Library::hasSubtags(shared_ptr<Tag> tag) const {
+ const TagList &tagList = tags();
+ const TagList::const_iterator it =
+ std::upper_bound(tagList.begin(), tagList.end(), tag, TagComparator());
+ return it != tagList.end() && tag->isAncestorOf(*it);
+}
+
+void Library::replaceAuthor(shared_ptr<Author> from, shared_ptr<Author> to) {
+ if (to != from) {
+ for (BookSet::const_iterator it = myBooks.begin(); it != myBooks.end(); ++it) {
+ if ((*it)->replaceAuthor(from, to)) {
+ BooksDB::Instance().saveAuthors(*it);
+ }
+ }
+ myBuildMode = (BuildMode)(myBuildMode | BUILD_UPDATE_BOOKS_INFO);
+ }
+}
+
+Library::RemoveType Library::canRemove(shared_ptr<Book> book) const {
+ synchronize();
+ return (RemoveType)(
+ (myExternalBooks.find(book) != myExternalBooks.end() ?
+ REMOVE_FROM_LIBRARY : REMOVE_DONT_REMOVE) |
+ (BooksDBUtil::canRemoveFile(book->file().path()) ?
+ REMOVE_FROM_DISK : REMOVE_DONT_REMOVE)
+ );
+}
+
+void Library::insertIntoBookSet(shared_ptr<Book> book) const {
+ if (!book.isNull()) {
+ myBooks.insert(book);
+ }
+}
+
+const BookList &Library::recentBooks() const {
+ return myRecentBooks;
+}
+
+void Library::addBookToRecentList(shared_ptr<Book> book) {
+ if (book.isNull()) {
+ return;
+ }
+ for (BookList::iterator it = myRecentBooks.begin(); it != myRecentBooks.end(); ++it) {
+ if ((*it)->file() == book->file()) {
+ if (it == myRecentBooks.begin()) {
+ return;
+ }
+ myRecentBooks.erase(it);
+ break;
+ }
+ }
+ myRecentBooks.insert(myRecentBooks.begin(), book);
+ if (myRecentBooks.size() > MaxRecentListSize) {
+ myRecentBooks.erase(myRecentBooks.begin() + MaxRecentListSize, myRecentBooks.end());
+ }
+ BooksDB::Instance().saveRecentBooks(myRecentBooks);
+}