summaryrefslogtreecommitdiffstats
path: root/fbreader/src/migration/Migration_0_11_0.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'fbreader/src/migration/Migration_0_11_0.cpp')
-rw-r--r--fbreader/src/migration/Migration_0_11_0.cpp544
1 files changed, 544 insertions, 0 deletions
diff --git a/fbreader/src/migration/Migration_0_11_0.cpp b/fbreader/src/migration/Migration_0_11_0.cpp
new file mode 100644
index 0000000..144245e
--- /dev/null
+++ b/fbreader/src/migration/Migration_0_11_0.cpp
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2009-2012 Geometer Plus <contact@geometerplus.com>
+ *
+ * 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 <iostream>
+#include <iomanip>
+#include <ZLTime.h>
+
+#include <vector>
+#include <algorithm>
+
+#include <ZLFile.h>
+#include <ZLStringUtil.h>
+#include <ZLUnicodeUtil.h>
+
+#include "BookInfo.h"
+#include "Migration.h"
+#include "../options/FBCategoryKey.h"
+
+#include "../formats/FormatPlugin.h"
+
+#include "../database/booksdb/BooksDBUtil.h"
+#include "../database/booksdb/BooksDB.h"
+#include "../library/Book.h"
+#include "../library/Tag.h"
+
+static const std::string BOOK_LIST_GROUP = "BookList";
+static const std::string BOOK_LIST_SIZE = "Size";
+static const std::string BOOK_LIST_PREFIX = "Book";
+
+static const std::string CURRENT_STATE_GROUP = "State";
+
+static const std::string RECENT_BOOKS_GROUP = "LastOpenedBooks";
+static const std::string BOOK = "Book";
+static const std::size_t MaxXmlListSize = 10;
+
+static const std::string PARAGRAPH_OPTION_NAME = "Paragraph";
+static const std::string WORD_OPTION_NAME = "Word";
+static const std::string CHAR_OPTION_NAME = "Char";
+static const std::string POSITION_IN_BUFFER = "PositionInBuffer";
+static const std::string BUFFER_SIZE = "UndoBufferSize";
+static const char * const BUFFER_PARAGRAPH_PREFIX = "Paragraph_";
+static const char * const BUFFER_WORD_PREFIX = "Word_";
+static const int MaxXmlStackSize = 20;
+
+static const std::string SIZE = "Size";
+static const std::string ENTRIES_NUMBER = "EntriesNumber";
+static const std::string PALM_TYPE = "PalmType";
+
+static const std::string NET_FILES_GROUP = "Files";
+
+class Migration_0_11_0_Runnable : public DBRunnable {
+
+public:
+ bool run();
+
+private:
+ bool migrateBooks();
+ bool migrateBookList();
+ bool migrateState();
+ bool migrateNetwork();
+
+ bool migrateBook(const ZLFile &file);
+
+ std::string tags2string(const TagList &tags);
+
+ bool stringEquals(const std::string &tags1, const std::string &tags2);
+
+ bool migrateRecentBooks();
+
+ bool migrateBooksState();
+
+ bool migrateBookStateStack(const std::string &fileName, const Book &book);
+ bool migrateBookLastState(const std::string &fileName, const Book &book);
+
+ bool shouldReadDisk(const std::string &fileName);
+
+ bool clearBooksOptions();
+
+ static void moveOption(const ZLCategoryKey &category, const std::string &oldGroup, const std::string &newGroup, const std::string &name, const std::string &defaultValue);
+ static void moveOption(const ZLCategoryKey &category, const std::string &oldGroup, const std::string &newGroup, const std::string &name, int defaultValue);
+ static void movePercentGroups(std::vector<std::string> &optionGroups);
+ static void moveBookGroup(const std::string &oldgroup, const std::string &newgroup);
+
+private:
+ std::map<std::string, shared_ptr<Book> > myBooks;
+};
+
+
+inline bool Migration_0_11_0_Runnable::shouldReadDisk(const std::string &fileName) {
+ const std::string ext = ZLFile(fileName).extension();
+ return ext == "fb2" || ext == "epub" || ext == "mobi" || ext == "oebzip" || ext == "opf";
+ //return ext == "fb2";
+ //return true;
+}
+
+
+void Migration_0_11_0_Runnable::moveOption(const ZLCategoryKey &category, const std::string &oldGroup, const std::string &newGroup, const std::string &name, const std::string &defaultValue) {
+ ZLStringOption newOption(category, newGroup, name, defaultValue);
+ ZLStringOption oldOption(category, oldGroup, name, defaultValue);
+ newOption.setValue(oldOption.value());
+ oldOption.setValue(defaultValue);
+}
+
+void Migration_0_11_0_Runnable::moveOption(const ZLCategoryKey &category, const std::string &oldGroup, const std::string &newGroup, const std::string &name, int defaultValue) {
+ ZLIntegerOption newOption(category, newGroup, name, defaultValue);
+ ZLIntegerOption oldOption(category, oldGroup, name, defaultValue);
+ newOption.setValue(oldOption.value());
+ oldOption.setValue(defaultValue);
+}
+
+static bool percentPathPredicate(const std::string &path) {
+ static const std::string _start = "%APPLICATION_PATH%";
+ return ZLStringUtil::stringStartsWith(path, _start);
+}
+
+void Migration_0_11_0_Runnable::movePercentGroups(std::vector<std::string> &optionGroups) {
+ std::vector<std::string>::iterator it = optionGroups.begin();
+ while ((it = std::find_if(it, optionGroups.end(), percentPathPredicate)) != optionGroups.end()) {
+ const std::string oldgroup = *it;
+ const std::string newgroup = ZLFile(oldgroup).resolvedPath();
+ if (std::find(optionGroups.begin(), optionGroups.end(), newgroup) == optionGroups.end()) {
+ moveBookGroup(oldgroup, newgroup);
+ *it++ = newgroup;
+ } else {
+ if (BookInfo(newgroup).TitleOption.value().empty()) {
+ moveBookGroup(oldgroup, newgroup);
+ }
+ it = optionGroups.erase(it);
+ }
+ ZLOption::clearGroup(oldgroup);
+ }
+}
+
+void Migration_0_11_0_Runnable::moveBookGroup(const std::string &oldgroup, const std::string &newgroup) {
+ BookInfo oldbi(oldgroup);
+ BookInfo newbi(newgroup);
+ newbi = oldbi;
+ oldbi.reset();
+
+ moveOption(ZLCategoryKey::STATE, oldgroup, newgroup, PARAGRAPH_OPTION_NAME, 0);
+ moveOption(ZLCategoryKey::STATE, oldgroup, newgroup, WORD_OPTION_NAME, 0);
+ moveOption(ZLCategoryKey::STATE, oldgroup, newgroup, CHAR_OPTION_NAME, 0);
+ moveOption(ZLCategoryKey::STATE, oldgroup, newgroup, POSITION_IN_BUFFER, 0);
+
+ int stackSize = ZLIntegerOption(ZLCategoryKey::STATE, oldgroup, BUFFER_SIZE, 0).value();
+ for (int i = 0; i < stackSize; ++i) {
+ std::string bufferParagraph = BUFFER_PARAGRAPH_PREFIX;
+ std::string bufferWord = BUFFER_WORD_PREFIX;
+ ZLStringUtil::appendNumber(bufferParagraph, i);
+ ZLStringUtil::appendNumber(bufferWord, i);
+ moveOption(ZLCategoryKey::STATE, oldgroup, newgroup, bufferParagraph, -1);
+ moveOption(ZLCategoryKey::STATE, oldgroup, newgroup, bufferWord, -1);
+ }
+ moveOption(ZLCategoryKey::STATE, oldgroup, newgroup, BUFFER_SIZE, 0);
+}
+
+
+
+Migration_0_11_0::Migration_0_11_0() : Migration("0.11.0") {
+}
+
+void Migration_0_11_0::doMigrationInternal() {
+ Migration_0_11_0_Runnable r;
+ BooksDB::Instance().executeAsTransaction(r);
+ //r.run();
+}
+
+bool Migration_0_11_0_Runnable::run() {
+const ZLTime start;
+ if (!migrateBooks()) {
+ std::cerr << std::endl << "VERDICT: migrateBooks failed" << std::endl << std::endl;
+ }
+std::cerr << "migration total 0: " << ZLTime().millisecondsFrom(start) << "ms" << std::endl;
+ if (!migrateBookList()) {
+ std::cerr << std::endl << "VERDICT: migrateBookList failed" << std::endl << std::endl;
+ }
+std::cerr << "migration total 1: " << ZLTime().millisecondsFrom(start) << "ms" << std::endl;
+ if (!migrateState()) {
+ std::cerr << std::endl << "VERDICT: migrateState failed" << std::endl << std::endl;
+ }
+std::cerr << "migration total 2: " << ZLTime().millisecondsFrom(start) << "ms" << std::endl;
+ if (!migrateNetwork()) {
+ std::cerr << std::endl << "VERDICT: migrateNetwork failed" << std::endl << std::endl;
+ }
+std::cerr << "migration total 3: " << ZLTime().millisecondsFrom(start) << "ms" << std::endl;
+ if (!clearBooksOptions()) {
+ std::cerr << std::endl << "VERDICT: clearBooksOptions failed" << std::endl << std::endl;
+ }
+std::cerr << "migration total 4: " << ZLTime().millisecondsFrom(start) << "ms" << std::endl;
+ return true;
+}
+
+
+
+bool Migration_0_11_0_Runnable::migrateBooks() {
+ /*std::map<std::string, unsigned long> ext2time;
+ std::map<std::string, unsigned long> ext2num;
+ unsigned long totalTime = 0, totalNum = 0;*/
+
+ PluginCollection &collection = PluginCollection::Instance();
+ std::vector<std::string> optionGroups;
+ ZLOption::listOptionGroups(optionGroups);
+
+ movePercentGroups(optionGroups);
+
+ bool res = true;
+ for (std::vector<std::string>::const_iterator it = optionGroups.begin(); it != optionGroups.end(); ++it) {
+ const std::string &name = *it;
+ if (Migration::isLikeToFileName(name)) {
+ /* TODO: check correctness of migration order:
+ * 1) palmType
+ * 2) size
+ * 3) book (depends on palmType and size)
+ */
+ const std::string palmType = ZLStringOption(FBCategoryKey::BOOKS, name, PALM_TYPE, "").value();
+ if (!palmType.empty()) {
+ BooksDB::Instance().setPalmType(name, palmType);
+ }
+ ZLStringOption(FBCategoryKey::BOOKS, name, PALM_TYPE, "").setValue(""); // clean books.xml
+ ZLFile file(name);
+ if (file.physicalFilePath() == name) {
+ int size = ZLIntegerOption(FBCategoryKey::BOOKS, name, SIZE, -1).value();
+ if (size != -1) {
+ BooksDB::Instance().setFileSize(name, size);
+ }
+ }
+ if (collection.plugin(file, false) != 0) {
+ if (!BookInfo(name).TitleOption.value().empty()) {
+ //ZLTime start;
+ if (!migrateBook(ZLFile(name))) {
+ std::cerr << "ERROR(2): migrateBook failed" << std::endl;
+ res = false;
+ }
+ /*ZLTime end;
+ {
+ unsigned time = end.millisecondsFrom(start);
+ std::string ext = ZLFile(name).extension();
+ totalTime += time;
+ totalNum += 1;
+ ext2time[ext] += time;
+ ext2num[ext] += 1;
+ }*/
+ }
+ BookInfo(name).reset(); // clean books.xml
+ } else {
+ ZLOption::clearGroup(name); // clean books.xml
+ }
+ ZLIntegerOption(FBCategoryKey::BOOKS, name, SIZE, -1).setValue(-1); // clean books.xml
+ if (!ZLStringOption(FBCategoryKey::BOOKS, name, ENTRIES_NUMBER, "").value().empty()) {
+ ZLOption::clearGroup(name); // clean books.xml
+ }
+ }
+ }
+
+ /*std::cerr << " ext" << " time,ms" << " time/total,%" << " number of books" << " time for 1 book,ms" << std::endl;
+ std::cerr << "---------------------------------------------------------------------------" << std::endl;
+ for (std::map<std::string, unsigned long>::const_iterator it = ext2time.begin(); it != ext2time.end(); ++it) {
+ const std::string &ext = it->first;
+ unsigned long time = it->second;
+ unsigned long num = ext2num[ext];
+ std::cerr << std::setw(8) << ext << std::setw(10) << time << std::setw(15) << ((float) time) / totalTime * 100.0
+ << std::setw(18) << num << std::setw(22) << ((float) time) / num << std::endl;
+ }
+ std::cerr << "---------------------------------------------------------------------------" << std::endl;
+ std::cerr << "total:" << std::endl;
+ std::cerr << std::setw(8) << "" << std::setw(10) << totalTime << std::setw(15) << ""
+ << std::setw(20) << totalNum << std::setw(20) << "" << std::endl;*/
+
+ return res;
+}
+
+
+bool Migration_0_11_0_Runnable::migrateBook(const ZLFile &file) {
+ shared_ptr<Book> infoBook = Book::loadFromBookInfo(file);
+ if (infoBook.isNull()) {
+ std::cerr << "ERROR: loading book from BookInfo failed: " << file.path() << std::endl;
+ return false;
+ }
+ if (shouldReadDisk(file.path()) && BooksDBUtil::isBookFull(*infoBook)) {
+ shared_ptr<Book> fileBook = Book::loadFromFile(file);
+ //shared_ptr<Book> fileBook = infoBook;
+ //shared_ptr<Book> fileBook;
+ if (!fileBook.isNull()) {
+ std::string tagList1 = tags2string(infoBook->tags());
+ std::string tagList2 = tags2string(fileBook->tags());
+ if (stringEquals(tagList1, tagList2)) {
+ infoBook->removeAllTags();
+ const TagList &tList = fileBook->tags();
+ for (TagList::const_iterator it = tList.begin(); it != tList.end(); ++it) {
+ infoBook->addTag(*it);
+ }
+ }
+ }
+ }
+ myBooks.insert(std::make_pair(file.path(), infoBook));
+ const bool code = BooksDB::Instance().saveBook(infoBook);
+ if (!code) {
+ std::cerr << "ERROR: saving book to database failed: " << file.path() << std::endl;
+ }
+ return code;
+}
+
+std::string Migration_0_11_0_Runnable::tags2string(const TagList &tags) {
+ std::string tagList;
+ TagList::const_iterator it = tags.begin();
+ if (it != tags.end()) {
+ tagList += (*it++)->fullName();
+ while (it != tags.end()) {
+ tagList += ',';
+ tagList += (*it++)->fullName();
+ }
+ }
+ return tagList;
+}
+
+bool Migration_0_11_0_Runnable::stringEquals(const std::string &tags1, const std::string &tags2) {
+ std::size_t i1 = 0;
+ std::size_t i2 = 0;
+ while (i1 < tags1.size() && i2 < tags2.size()) {
+ if (std::isspace(tags1[i1])) {
+ ++i1;
+ continue;
+ }
+ if (std::isspace(tags2[i2])) {
+ ++i2;
+ continue;
+ }
+ if (tags1[i1++] != tags2[i2++]) {
+ return false;
+ }
+ }
+ if (i1 == tags1.size() && i2 < tags2.size()) {
+ while (i2 < tags2.size()) {
+ if (!std::isspace(tags2[i2++])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (i1 < tags1.size() && i2 == tags2.size()) {
+ while (i1 < tags1.size()) {
+ if (!std::isspace(tags1[i1++])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return true;
+}
+
+bool Migration_0_11_0_Runnable::migrateBookList() {
+ bool res = true;
+ int size = ZLIntegerOption(ZLCategoryKey::STATE, BOOK_LIST_GROUP, BOOK_LIST_SIZE, 0).value();
+ for (int i = 0; i < size; ++i) {
+ std::string optionName = BOOK_LIST_PREFIX;
+ ZLStringUtil::appendNumber(optionName, i);
+ const std::string &fileName = ZLStringOption(ZLCategoryKey::STATE, BOOK_LIST_GROUP, optionName, "").value();
+ if (!fileName.empty()) {
+ std::map<std::string, shared_ptr<Book> >::iterator it = myBooks.find(fileName);
+ if (it != myBooks.end()) {
+ shared_ptr<Book> book = it->second;
+ if (!book.isNull() && book->bookId() != 0) {
+ if (!BooksDB::Instance().insertIntoBookList(*book)) {
+ std::cerr << "ERROR: insert into BookList failed: " << fileName << std::endl;
+ res = false;
+ }
+ }
+ }
+ }
+ }
+ ZLOption::clearGroup(BOOK_LIST_GROUP); // clean state.xml
+ return res;
+}
+
+bool Migration_0_11_0_Runnable::migrateState() {
+ bool res = true;
+ if (!migrateRecentBooks()) {
+ std::cerr << "ERROR(2): migrateRecentBooks failed" << std::endl;
+ res = false;
+ }
+ if (!migrateBooksState()) {
+ std::cerr << "ERROR(2): migrateBooksState failed" << std::endl;
+ res = false;
+ }
+ ZLOption::clearGroup(RECENT_BOOKS_GROUP); // clean state.xml
+ ZLOption::clearGroup(CURRENT_STATE_GROUP); // clean state.xml
+ return res;
+}
+
+bool Migration_0_11_0_Runnable::migrateRecentBooks() {
+ BookList books;
+ for (std::size_t i = 0; i < MaxXmlListSize; ++i) {
+ std::string num = BOOK;
+ ZLStringUtil::appendNumber(num, i);
+ std::string name = ZLStringOption(ZLCategoryKey::STATE, RECENT_BOOKS_GROUP, num, "").value();
+ if (!name.empty()) {
+ //shared_ptr<Book> book = BooksDBUtil::getBook(name, false);
+ std::map<std::string, shared_ptr<Book> >::const_iterator it = myBooks.find(name);
+ if (it == myBooks.end()) {
+ if ((it = myBooks.find(ZLFile(name).resolvedPath())) == myBooks.end()) {
+ continue;
+ }
+ }
+ shared_ptr<Book> book = it->second;
+ if (!book.isNull() && book->bookId() != 0 && std::find(books.begin(), books.end(), book) == books.end()) {
+ books.push_back(book);
+ }
+ }
+ }
+ bool res = BooksDB::Instance().saveRecentBooks(books);
+ if (!res) {
+ std::cerr << "ERROR: saving recent books list failed (" << books.size() << " item[s])" << std::endl;
+ }
+ return res;
+}
+
+bool Migration_0_11_0_Runnable::migrateBooksState() {
+ bool res = true;
+
+ for (std::map<std::string, shared_ptr<Book> >::const_iterator it = myBooks.begin(); it != myBooks.end(); ++it) {
+ const std::string &fileName = it->first;
+ if (it->second.isNull()) {
+ std::cerr << "ERROR: book in map is null: " << fileName << std::endl;
+ res = false;
+ continue;
+ }
+ const Book &book = *it->second;
+ if (!migrateBookStateStack(fileName, book)) {
+ res = false;
+ }
+ if (!migrateBookLastState(fileName, book)) {
+ res = false;
+ }
+ }
+ return res;
+}
+
+
+bool Migration_0_11_0_Runnable::migrateBookStateStack(const std::string &fileName, const Book &book) {
+ std::deque<ReadingState> stack;
+ bool res = true;
+ int stackSize = ZLIntegerOption(ZLCategoryKey::STATE, fileName, BUFFER_SIZE, 0).value();
+ if (stackSize > 0) {
+ if (stackSize > MaxXmlStackSize) {
+ stackSize = MaxXmlStackSize;
+ }
+ for (int i = 0; i < stackSize; ++i) {
+ std::string bufferParagraph = BUFFER_PARAGRAPH_PREFIX;
+ std::string bufferWord = BUFFER_WORD_PREFIX;
+ ZLStringUtil::appendNumber(bufferParagraph, i);
+ ZLStringUtil::appendNumber(bufferWord, i);
+ ReadingState pos(
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, bufferParagraph, -1).value(),
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, bufferWord, -1).value(),
+ 0
+ );
+ stack.push_back(pos);
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, bufferParagraph, -1).setValue(-1); // clean state.xml
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, bufferWord, -1).setValue(-1); // clean state.xml
+ }
+ if (!BooksDB::Instance().saveBookStateStack(book, stack)) {
+ std::cerr << "ERROR: saving book state stack failed: " << fileName << std::endl;
+ res = false;
+ }
+ stack.clear();
+ }
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, BUFFER_SIZE, 0).setValue(0); // clean state.xml
+ return res;
+}
+
+bool Migration_0_11_0_Runnable::migrateBookLastState(const std::string &fileName, const Book &book) {
+ const ReadingState state(
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, PARAGRAPH_OPTION_NAME, 0).value(),
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, WORD_OPTION_NAME, 0).value(),
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, CHAR_OPTION_NAME, 0).value()
+ );
+ const int stackPos = ZLIntegerOption(ZLCategoryKey::STATE, fileName, POSITION_IN_BUFFER, 0).value();
+ if (state.Paragraph == 0 && state.Word == 0 && state.Character == 0 && stackPos == 0) {
+ return true;
+ }
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, PARAGRAPH_OPTION_NAME, 0).setValue(0);
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, WORD_OPTION_NAME, 0).setValue(0);
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, CHAR_OPTION_NAME, 0).setValue(0);
+ ZLIntegerOption(ZLCategoryKey::STATE, fileName, POSITION_IN_BUFFER, 0).setValue(0);
+ bool res1 = BooksDB::Instance().setBookState(book, state);
+ bool res2 = BooksDB::Instance().setStackPos(book, stackPos);
+ if (!res1) {
+ std::cerr << "ERROR: saving book last state failed: " << fileName << std::endl;
+ }
+ if (!res2) {
+ std::cerr << "ERROR: saving book state stack position failed: " << fileName << std::endl;
+ }
+ return res1 && res2;
+}
+
+bool Migration_0_11_0_Runnable::migrateNetwork() {
+ bool res = true;
+// FBReader desktop 0.99.1 deprecates NetFiles table, so don't fill it
+// std::vector<std::string> urls;
+// ZLOption::listOptionNames(NET_FILES_GROUP, urls);
+// for (std::vector<std::string>::const_iterator it = urls.begin(); it != urls.end(); ++it) {
+// const std::string &url = *it;
+// const std::string fileName = ZLStringOption(ZLCategoryKey::NETWORK, NET_FILES_GROUP, url, "").value();
+// if (!BooksDB::Instance().setNetFile(url, fileName)) {
+// std::cerr << "ERROR: saving file's URL failed: " << std::endl
+// << "\tURL = " << url << std::endl
+// << "\tfileName = " << fileName << std::endl;
+// res = false;
+// }
+// }
+ ZLOption::clearGroup(NET_FILES_GROUP); // clean state.xml
+ return res;
+}
+
+bool Migration_0_11_0_Runnable::clearBooksOptions() {
+ bool res = true;
+ for (std::map<std::string, shared_ptr<Book> >::const_iterator it = myBooks.begin(); it != myBooks.end(); ++it) {
+ const std::string &fileName = it->first;
+ if (it->second.isNull()) {
+ std::cerr << "ERROR: book in map is null in clearBooksOptions: " << fileName << std::endl;
+ res = false;
+ continue;
+ }
+ ZLOption::clearGroup(fileName); // clear books.xml & state.xml
+ }
+ return res;
+}
+