1 /*
2 * Copyright (C) 2009-2010 Geometer Plus <contact@geometerplus.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 */
19
20 #include <ZLibrary.h>
21 #include <ZLFile.h>
22 #include <ZLDir.h>
23 #include <ZLLanguageUtil.h>
24
25 #include "BooksDB.h"
26 #include "BooksDBQuery.h"
27
28 #include "../../library/Book.h"
29 #include "../../library/Author.h"
30 #include "../../library/Tag.h"
31
32 #include "../sqldb/implsqlite/SQLiteFactory.h"
33
34 shared_ptr<BooksDB> BooksDB::ourInstance = 0;
35
36 const std::string BooksDB::DATABASE_NAME = "books.db";
37 const std::string BooksDB::STATE_DATABASE_NAME = "state.db";
38 const std::string BooksDB::NET_DATABASE_NAME = "network.db";
39
Instance()40 BooksDB &BooksDB::Instance() {
41 if (ourInstance.isNull()) {
42 ZLFile dir(databaseDirName());
43 dir.directory(true);
44 ZLFile file(databaseDirName() + ZLibrary::FileNameDelimiter + DATABASE_NAME);
45 ourInstance = new BooksDB(file.physicalFilePath());
46 ourInstance->initDatabase();
47 }
48 return *ourInstance;
49 }
50
BooksDB(const std::string & path)51 BooksDB::BooksDB(const std::string &path) : SQLiteDataBase(path), myInitialized(false) {
52 initCommands();
53 }
54
~BooksDB()55 BooksDB::~BooksDB() {
56 }
57
initDatabase()58 bool BooksDB::initDatabase() {
59 if (isInitialized()) {
60 return true;
61 }
62
63 if (!open()) {
64 return false;
65 }
66
67 myInitialized = true;
68
69 ZLFile stateFile(databaseDirName() + ZLibrary::FileNameDelimiter + STATE_DATABASE_NAME);
70 ZLFile netFile(databaseDirName() + ZLibrary::FileNameDelimiter + NET_DATABASE_NAME);
71 shared_ptr<DBCommand> cmd = SQLiteFactory::createCommand(BooksDBQuery::PREINIT_DATABASE, connection(), "@stateFile", DBValue::DBTEXT, "@netFile", DBValue::DBTEXT);
72 ((DBTextValue&)*cmd->parameter("@stateFile").value()) = stateFile.physicalFilePath();
73 ((DBTextValue&)*cmd->parameter("@netFile").value()) = netFile.physicalFilePath();
74 if (!cmd->execute()) {
75 myInitialized = false;
76 close();
77 return false;
78 }
79
80 shared_ptr<DBRunnable> runnable = new InitBooksDBRunnable(connection());
81 if (!executeAsTransaction(*runnable)) {
82 myInitialized = false;
83 close();
84 return false;
85 }
86
87 return true;
88 }
89
initCommands()90 void BooksDB::initCommands() {
91 myLoadBook = SQLiteFactory::createCommand(BooksDBQuery::LOAD_BOOK, connection(), "@file_id", DBValue::DBINT);
92 myGetFileSize = SQLiteFactory::createCommand(BooksDBQuery::GET_FILE_SIZE, connection(), "@file_id", DBValue::DBINT);
93 mySetFileSize = SQLiteFactory::createCommand(BooksDBQuery::SET_FILE_SIZE, connection(), "@file_id", DBValue::DBINT, "@size", DBValue::DBINT);
94 myFindFileName = SQLiteFactory::createCommand(BooksDBQuery::FIND_FILE_NAME, connection(), "@file_id", DBValue::DBINT);
95 myFindAuthorId = SQLiteFactory::createCommand(BooksDBQuery::FIND_AUTHOR_ID, connection(), "@name", DBValue::DBTEXT, "@sort_key", DBValue::DBTEXT);
96
97 myLoadBooks = SQLiteFactory::createCommand(BooksDBQuery::LOAD_BOOKS, connection());
98
99 myLoadBookStateStack = SQLiteFactory::createCommand(BooksDBQuery::LOAD_BOOK_STATE_STACK, connection(), "@book_id", DBValue::DBINT);
100
101 myGetPalmType = SQLiteFactory::createCommand(BooksDBQuery::GET_PALM_TYPE, connection(), "@file_id", DBValue::DBINT);
102 mySetPalmType = SQLiteFactory::createCommand(BooksDBQuery::SET_PALM_TYPE, connection(), "@file_id", DBValue::DBINT, "@type", DBValue::DBTEXT);
103
104 myLoadStackPos = SQLiteFactory::createCommand(BooksDBQuery::LOAD_STACK_POS, connection(), "@book_id", DBValue::DBINT);
105 mySetStackPos = SQLiteFactory::createCommand(BooksDBQuery::SET_STACK_POS, connection(), "@book_id", DBValue::DBINT, "@stack_pos", DBValue::DBINT);
106
107 myLoadBookState = SQLiteFactory::createCommand(BooksDBQuery::LOAD_BOOK_STATE, connection(), "@book_id", DBValue::DBINT);
108 mySetBookState = SQLiteFactory::createCommand(BooksDBQuery::SET_BOOK_STATE, connection(), "@book_id", DBValue::DBINT, "@paragraph", DBValue::DBINT, "@word", DBValue::DBINT, "@char", DBValue::DBINT);
109
110 myInsertBookList = SQLiteFactory::createCommand(BooksDBQuery::INSERT_BOOK_LIST, connection(), "@book_id", DBValue::DBINT);
111 myDeleteBookList = SQLiteFactory::createCommand(BooksDBQuery::DELETE_BOOK_LIST, connection(), "@book_id", DBValue::DBINT);
112 myCheckBookList = SQLiteFactory::createCommand(BooksDBQuery::CHECK_BOOK_LIST, connection(), "@book_id", DBValue::DBINT);
113
114 mySaveTableBook = new SaveTableBookRunnable(connection());
115 mySaveAuthors = new SaveAuthorsRunnable(connection());
116 mySaveSeries = new SaveSeriesRunnable(connection());
117 mySaveTags = new SaveTagsRunnable(connection());
118 mySaveBook = new SaveBookRunnable(*mySaveTableBook, *mySaveAuthors, *mySaveSeries, *mySaveTags);
119 mySaveFileEntries = new SaveFileEntriesRunnable(connection());
120
121 myFindFileId = new FindFileIdRunnable(connection());
122
123 myLoadFileEntries = new LoadFileEntriesRunnable(connection());
124
125 myLoadRecentBooks = new LoadRecentBooksRunnable(connection());
126 mySaveRecentBooks = new SaveRecentBooksRunnable(connection());
127
128 mySaveBookStateStack = new SaveBookStateStackRunnable(connection());
129
130 myDeleteBook = new DeleteBookRunnable(connection());
131 }
132
clearDatabase()133 bool BooksDB::clearDatabase() {
134 if (!isInitialized()) {
135 return false;
136 }
137 shared_ptr<DBRunnable> runnable = new ClearBooksDBRunnable(connection());
138 return executeAsTransaction(*runnable);
139 }
140
loadBook(const std::string & fileName)141 shared_ptr<Book> BooksDB::loadBook(const std::string &fileName) {
142 if (!isInitialized()) {
143 return 0;
144 }
145
146 myFindFileId->setFileName(fileName);
147 if (!myFindFileId->run()) {
148 return 0;
149 }
150 ((DBIntValue&)*myLoadBook->parameter("@file_id").value()) = myFindFileId->fileId();
151 shared_ptr<DBDataReader> reader = myLoadBook->executeReader();
152
153 if (reader.isNull() || !reader->next() ||
154 reader->type(0) != DBValue::DBINT /* book_id */) {
155 return 0;
156 }
157 const int bookId = reader->intValue(0);
158
159 shared_ptr<Book> book = Book::createBook(
160 ZLFile(fileName), bookId,
161 reader->textValue(1, Book::AutoEncoding),
162 reader->textValue(2, ZLLanguageUtil::OtherLanguageCode),
163 reader->textValue(3, std::string())
164 );
165
166 loadSeries(*book);
167 loadAuthors(*book);
168 loadTags(*book);
169
170 return book;
171 }
172
173
saveBook(const shared_ptr<Book> book)174 bool BooksDB::saveBook(const shared_ptr<Book> book) {
175 if (!isInitialized()) {
176 return false;
177 }
178 mySaveBook->setBook(book);
179 return executeAsTransaction(*mySaveBook);
180 }
181
saveAuthors(const shared_ptr<Book> book)182 bool BooksDB::saveAuthors(const shared_ptr<Book> book) {
183 if (!isInitialized()) {
184 return false;
185 }
186 mySaveAuthors->setBook(book);
187 return executeAsTransaction(*mySaveAuthors);
188 }
189
saveSeries(const shared_ptr<Book> book)190 bool BooksDB::saveSeries(const shared_ptr<Book> book) {
191 if (!isInitialized()) {
192 return false;
193 }
194 mySaveSeries->setBook(book);
195 return executeAsTransaction(*mySaveSeries);
196 }
197
saveTags(const shared_ptr<Book> book)198 bool BooksDB::saveTags(const shared_ptr<Book> book) {
199 if (!isInitialized()) {
200 return false;
201 }
202 mySaveTags->setBook(book);
203 return executeAsTransaction(*mySaveTags);
204 }
205
getFileSize(const std::string & fileName)206 int BooksDB::getFileSize(const std::string &fileName) {
207 if (!isInitialized()) {
208 return -1;
209 }
210 myFindFileId->setFileName(fileName);
211 if (!myFindFileId->run()) {
212 return 0;
213 }
214 ((DBIntValue&)*myGetFileSize->parameter("@file_id").value()) = myFindFileId->fileId();
215
216 shared_ptr<DBValue> fileSize = myGetFileSize->executeScalar();
217
218 if (fileSize.isNull()) {
219 return -1;
220 }
221 if (fileSize->type() == DBValue::DBNULL) {
222 return 0;
223 }
224 if (fileSize->type() != DBValue::DBINT) {
225 return -1;
226 }
227 return ((DBIntValue&)*fileSize).value();
228 }
229
setFileSize(const std::string & fileName,int size)230 bool BooksDB::setFileSize(const std::string &fileName, int size) {
231 if (!isInitialized()) {
232 return false;
233 }
234 myFindFileId->setFileName(fileName, true);
235 if (!executeAsTransaction(*myFindFileId)) {
236 return false;
237 }
238 ((DBIntValue&)*mySetFileSize->parameter("@file_id").value()) = myFindFileId->fileId();
239 ((DBIntValue&)*mySetFileSize->parameter("@size").value()) = size;
240 return mySetFileSize->execute();
241 }
242
setEncoding(const Book & book,const std::string & encoding)243 bool BooksDB::setEncoding(const Book &book, const std::string &encoding) {
244 if (!isInitialized()) {
245 return false;
246 }
247
248 shared_ptr<DBCommand> command = SQLiteFactory::createCommand(BooksDBQuery::SET_ENCODING, connection());
249
250 command->parameters().push_back(DBCommandParameter("@book_id", new DBIntValue(book.bookId())));
251 command->parameters().push_back(DBCommandParameter("@encoding", new DBTextValue(encoding)));
252
253 return command->execute();
254 }
255
loadFileEntries(const std::string & fileName,std::vector<std::string> & entries)256 bool BooksDB::loadFileEntries(const std::string &fileName, std::vector<std::string> &entries) {
257 myLoadFileEntries->setFileName(fileName);
258 if (!myLoadFileEntries->run()) {
259 return false;
260 }
261 myLoadFileEntries->collectEntries(entries);
262 return true;
263 }
264
saveFileEntries(const std::string & fileName,const std::vector<std::string> & entries)265 bool BooksDB::saveFileEntries(const std::string &fileName, const std::vector<std::string> &entries) {
266 if (!isInitialized()) {
267 return false;
268 }
269 mySaveFileEntries->setEntries(fileName, entries);
270 return executeAsTransaction(*mySaveFileEntries);
271 }
272
loadRecentBooks(std::vector<std::string> & fileNames)273 bool BooksDB::loadRecentBooks(std::vector<std::string> &fileNames) {
274 std::vector<int> fileIds;
275 if (!myLoadRecentBooks->run()) {
276 return false;
277 }
278 myLoadRecentBooks->collectFileIds(fileIds);
279 for (std::vector<int>::const_iterator it = fileIds.begin(); it != fileIds.end(); ++it) {
280 const int fileId = *it;
281 const std::string fileName = getFileName(fileId);
282 fileNames.push_back(fileName);
283 }
284 return true;
285 }
286
saveRecentBooks(const BookList & books)287 bool BooksDB::saveRecentBooks(const BookList &books) {
288 if (!isInitialized()) {
289 return false;
290 }
291 mySaveRecentBooks->setBooks(books);
292 return executeAsTransaction(*mySaveRecentBooks);
293 }
294
getFileName(int fileId)295 std::string BooksDB::getFileName(int fileId) {
296 std::string fileName;
297 DBIntValue &findFileId = (DBIntValue&)*myFindFileName->parameter("@file_id").value();
298 findFileId = fileId;
299 while (true) {
300 shared_ptr<DBDataReader> reader = myFindFileName->executeReader();
301 if (reader.isNull() || !reader->next()) {
302 return std::string();
303 }
304 const std::string namePart = reader->textValue(0, std::string());
305 switch (reader->type(1)) { /* parent_id */
306 default:
307 return std::string();
308 case DBValue::DBNULL:
309 return namePart + ZLibrary::FileNameDelimiter + fileName;
310 case DBValue::DBINT:
311 if (fileName.empty()) {
312 fileName = namePart;
313 } else {
314 fileName = namePart + BooksDBQuery::ArchiveEntryDelimiter + fileName;
315 }
316 findFileId = reader->intValue(1);
317 break;
318 }
319 }
320 }
321
loadBooks(BookList & books)322 bool BooksDB::loadBooks(BookList &books) {
323 shared_ptr<DBDataReader> reader = myLoadBooks->executeReader();
324
325 books.clear();
326 std::map<int,shared_ptr<Book> > bookMap;
327
328 while (reader->next()) {
329 if (reader->type(0) != DBValue::DBINT || /* book_id */
330 reader->type(4) != DBValue::DBINT) { /* file_id */
331 return false;
332 }
333 const int bookId = reader->intValue(0);
334 const int fileId = reader->intValue(4);
335 const std::string fileName = getFileName(fileId);
336
337 shared_ptr<Book> book = Book::createBook(
338 ZLFile(fileName),
339 bookId,
340 reader->textValue(1, Book::AutoEncoding),
341 reader->textValue(2, ZLLanguageUtil::OtherLanguageCode),
342 reader->textValue(3, std::string())
343 );
344 books.push_back(book);
345 bookMap[bookId] = book;
346 }
347
348 loadSeries(bookMap);
349 loadAuthors(bookMap);
350 loadTags(bookMap);
351
352 return true;
353 }
354
loadBookStateStack(const Book & book,std::deque<ReadingState> & stack)355 bool BooksDB::loadBookStateStack(const Book &book, std::deque<ReadingState> &stack) {
356 if (book.bookId() == 0) {
357 return false;
358 }
359 ((DBIntValue&)*myLoadBookStateStack->parameter("@book_id").value()) = book.bookId();
360 shared_ptr<DBDataReader> reader = myLoadBookStateStack->executeReader();
361 if (reader.isNull()) {
362 return false;
363 }
364 while (reader->next()) {
365 if (reader->type(0) != DBValue::DBINT /* paragraph */
366 || reader->type(1) != DBValue::DBINT /* word */
367 || reader->type(2) != DBValue::DBINT /* char */) {
368 return false;
369 }
370 const int paragraph = reader->intValue(0);
371 const int word = reader->intValue(1);
372 const int character = reader->intValue(2);
373 stack.push_back(ReadingState(paragraph, word, character));
374 }
375 return true;
376 }
377
saveBookStateStack(const Book & book,const std::deque<ReadingState> & stack)378 bool BooksDB::saveBookStateStack(const Book &book, const std::deque<ReadingState> &stack) {
379 if (!isInitialized() || book.bookId() == 0) {
380 return false;
381 }
382 mySaveBookStateStack->setState(book.bookId(), stack);
383 return executeAsTransaction(*mySaveBookStateStack);
384 }
385
386
removeBook(const Book & book)387 bool BooksDB::removeBook(const Book &book) {
388 if (!isInitialized() || book.bookId() == 0) {
389 return false;
390 }
391 myDeleteBook->setFileName(book.file().path());
392 return executeAsTransaction(*myDeleteBook);
393 }
394
getPalmType(const std::string & fileName)395 std::string BooksDB::getPalmType(const std::string &fileName) {
396 if (!isInitialized()) {
397 return "";
398 }
399 myFindFileId->setFileName(fileName);
400 if (!myFindFileId->run()) {
401 return "";
402 }
403 ((DBIntValue&)*myGetPalmType->parameter("@file_id").value()) = myFindFileId->fileId();
404 shared_ptr<DBValue> value = myGetPalmType->executeScalar();
405 if (value.isNull() || value->type() != DBValue::DBTEXT) {
406 return "";
407 }
408 return ((DBTextValue&)*value).value();
409 }
410
setPalmType(const std::string & fileName,const std::string & type)411 bool BooksDB::setPalmType(const std::string &fileName, const std::string &type) {
412 if (!isInitialized()) {
413 return false;
414 }
415 myFindFileId->setFileName(fileName, true);
416 if (!myFindFileId->run()) {
417 return "";
418 }
419 ((DBIntValue&)*mySetPalmType->parameter("@file_id").value()) = myFindFileId->fileId();
420 ((DBTextValue&)*mySetPalmType->parameter("@type").value()) = type;
421 return mySetPalmType->execute();
422 }
423
getNetFile(const std::string & url)424 std::string BooksDB::getNetFile(const std::string &url) {
425 static shared_ptr<DBCommand> command = SQLiteFactory::createCommand(
426 "SELECT file_id FROM NetFiles WHERE url = @url;",
427 connection(), "@url", DBValue::DBTEXT
428 );
429
430 ((DBTextValue&)*command->parameter("@url").value()) = url;
431 shared_ptr<DBValue> value = command->executeScalar();
432 if (value.isNull() || value->type() != DBValue::DBINT) {
433 return std::string();
434 }
435 return getFileName(((DBIntValue&)*value).value());
436 }
437
setNetFile(const std::string & url,const std::string & fileName)438 bool BooksDB::setNetFile(const std::string &url, const std::string &fileName) {
439 static shared_ptr<DBCommand> command = SQLiteFactory::createCommand(
440 "INSERT OR REPLACE INTO NetFiles (url, file_id) VALUES (@url, @file_id);",
441 connection(), "@file_id", DBValue::DBINT, "@url", DBValue::DBTEXT
442 );
443
444 myFindFileId->setFileName(fileName, true);
445 if (!myFindFileId->run()) {
446 return false;
447 }
448 ((DBIntValue&)*command->parameter("@file_id").value()) = myFindFileId->fileId();
449 ((DBTextValue&)*command->parameter("@url").value()) = url;
450 return command->execute();
451 }
452
unsetNetFile(const std::string & url)453 bool BooksDB::unsetNetFile(const std::string &url) {
454 static shared_ptr<DBCommand> command = SQLiteFactory::createCommand(
455 "SELECT file_id FROM NetFiles WHERE url = @url;",
456 connection(), "@url", DBValue::DBTEXT
457 );
458
459 ((DBTextValue&)*command->parameter("@url").value()) = url;
460 return command->execute();
461 }
462
loadBookState(const Book & book,ReadingState & state)463 bool BooksDB::loadBookState(const Book &book, ReadingState &state) {
464 state.Paragraph = state.Word = state.Character = 0;
465 if (book.bookId() == 0) {
466 return false;
467 }
468 ((DBIntValue&)*myLoadBookState->parameter("@book_id").value()) = book.bookId();
469 shared_ptr<DBDataReader> reader = myLoadBookState->executeReader();
470 if (reader.isNull()) {
471 return false;
472 }
473 if (!reader->next()
474 || reader->type(0) != DBValue::DBINT /* paragraph */
475 || reader->type(1) != DBValue::DBINT /* word */
476 || reader->type(2) != DBValue::DBINT /* char */) {
477 return false;
478 }
479 state.Paragraph = reader->intValue(0);
480 state.Word = reader->intValue(1);
481 state.Character = reader->intValue(2);
482 return true;
483 }
484
setBookState(const Book & book,const ReadingState & state)485 bool BooksDB::setBookState(const Book &book, const ReadingState &state) {
486 if (book.bookId() == 0) {
487 return false;
488 }
489 ((DBIntValue&)*mySetBookState->parameter("@book_id").value()) = book.bookId();
490 ((DBIntValue&)*mySetBookState->parameter("@paragraph").value()) = state.Paragraph;
491 ((DBIntValue&)*mySetBookState->parameter("@word").value()) = state.Word;
492 ((DBIntValue&)*mySetBookState->parameter("@char").value()) = state.Character;
493 return mySetBookState->execute();
494 }
495
loadStackPos(const Book & book)496 int BooksDB::loadStackPos(const Book &book) {
497 if (book.bookId() == 0) {
498 return 0;
499 }
500 ((DBIntValue&)*myLoadStackPos->parameter("@book_id").value()) = book.bookId();
501 shared_ptr<DBValue> stackPosValue = myLoadStackPos->executeScalar();
502 if (stackPosValue.isNull()
503 || stackPosValue->type() != DBValue::DBINT) {
504 return 0;
505 }
506 return ((DBIntValue&)*stackPosValue).value();
507 }
508
setStackPos(const Book & book,int stackPos)509 bool BooksDB::setStackPos(const Book &book, int stackPos) {
510 if (book.bookId() == 0) {
511 return false;
512 }
513 ((DBIntValue&)*mySetStackPos->parameter("@book_id").value()) = book.bookId();
514 ((DBIntValue&)*mySetStackPos->parameter("@stack_pos").value()) = stackPos;
515 return mySetStackPos->execute();
516 }
517
insertIntoBookList(const Book & book)518 bool BooksDB::insertIntoBookList(const Book &book) {
519 if (book.bookId() == 0) {
520 return false;
521 }
522 ((DBIntValue&)*myInsertBookList->parameter("@book_id").value()) = book.bookId();
523 return myInsertBookList->execute();
524 }
525
deleteFromBookList(const Book & book)526 bool BooksDB::deleteFromBookList(const Book &book) {
527 if (book.bookId() == 0) {
528 return false;
529 }
530 ((DBIntValue&)*myDeleteBookList->parameter("@book_id").value()) = book.bookId();
531 return myDeleteBookList->execute();
532 }
533
checkBookList(const Book & book)534 bool BooksDB::checkBookList(const Book &book) {
535 if (book.bookId() == 0) {
536 return false;
537 }
538 ((DBIntValue&)*myCheckBookList->parameter("@book_id").value()) = book.bookId();
539 shared_ptr<DBValue> res = myCheckBookList->executeScalar();
540 if (res.isNull() || res->type() != DBValue::DBINT) {
541 return false;
542 }
543 const int checkRes = ((DBIntValue&)*res).value();
544 return checkRes > 0;
545 }
546
547