gt: 		if (seriesIDs.isEmpty()) {
context: /*
   * Copyright (C) 2009-2014 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.
   */
  
  package org.geometerplus.android.fbreader.libraryService;
  
  import android.content.Context;
  import android.database.Cursor;
  import android.database.SQLException;
  import android.database.sqlite.SQLiteDatabase;
  import android.database.sqlite.SQLiteDoneException;
  import android.database.sqlite.SQLiteStatement;
  
  import org.geometerplus.android.util.SQLiteUtil;
  import org.geometerplus.fbreader.book.Author;
  import org.geometerplus.fbreader.book.Book;
  import org.geometerplus.fbreader.book.Bookmark;
  import org.geometerplus.fbreader.book.BookmarkQuery;
  import org.geometerplus.fbreader.book.BooksDatabase;
  import org.geometerplus.fbreader.book.FileInfo;
  import org.geometerplus.fbreader.book.FileInfoSet;
  import org.geometerplus.fbreader.book.HighlightingStyle;
  import org.geometerplus.fbreader.book.SeriesInfo;
  import org.geometerplus.fbreader.book.Tag;
  import org.geometerplus.fbreader.book.UID;
  import org.geometerplus.zlibrary.core.filesystem.ZLFile;
  import org.geometerplus.zlibrary.core.options.Config;
  import org.geometerplus.zlibrary.core.options.ZLIntegerOption;
  import org.geometerplus.zlibrary.core.options.ZLStringOption;
  import org.geometerplus.zlibrary.core.util.RationalNumber;
  import org.geometerplus.zlibrary.core.util.ZLColor;
  import org.geometerplus.zlibrary.text.view.ZLTextFixedPosition;
  import org.geometerplus.zlibrary.text.view.ZLTextPosition;
  
  import java.math.BigDecimal;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.TreeSet;
  
  final class SQLiteBooksDatabase extends BooksDatabase {
  	private final SQLiteDatabase myDatabase;
  	private final HashMap<String,SQLiteStatement> myStatements =
  		new HashMap<String,SQLiteStatement>();
  
  	SQLiteBooksDatabase(Context context) {
  		myDatabase = context.openOrCreateDatabase("books.db", Context.MODE_PRIVATE, null);
  		migrate();
  	}
  
  	@Override
  	public void finalize() {
  		myDatabase.close();
  	}
  
  	protected void executeAsTransaction(Runnable actions) {
  		boolean transactionStarted = false;
  		try {
  			myDatabase.beginTransaction();
  			transactionStarted = true;
  		} catch (Throwable t) {
  		}
  		try {
  			actions.run();
  			if (transactionStarted) {
  				myDatabase.setTransactionSuccessful();
  			}
  		} finally {
  			if (transactionStarted) {
  				myDatabase.endTransaction();
  			}
  		}
  	}
  
  	private void migrate() {
  		final int version = myDatabase.getVersion();
  		final int currentVersion = 28;
  		if (version >= currentVersion) {
  			return;
  		}
  
  		myDatabase.beginTransaction();
  
  		switch (version) {
  			case 0:
  				createTables();
  			case 1:
  				updateTables1();
  			case 2:
  				updateTables2();
  			case 3:
  				updateTables3();
  			case 4:
  				updateTables4();
  			case 5:
  				updateTables5();
  			case 6:
  				updateTables6();
  			case 7:
  				updateTables7();
  			case 8:
  				updateTables8();
  			case 9:
  				updateTables9();
  			case 10:
  				updateTables10();
  			case 11:
  				updateTables11();
  			case 12:
  				updateTables12();
  			case 13:
  				updateTables13();
  			case 14:
  				updateTables14();
  			case 15:
  				updateTables15();
  			case 16:
  				updateTables16();
  			case 17:
  				updateTables17();
  			case 18:
  				updateTables18();
  			case 19:
  				updateTables19();
  			case 20:
  				updateTables20();
  			case 21:
  				updateTables21();
  			case 22:
  				updateTables22();
  			case 23:
  				updateTables23();
  			case 24:
  				updateTables24();
  			case 25:
  				updateTables25();
  			case 26:
  				updateTables26();
  			case 27:
  				updateTables27();
  		}
  		myDatabase.setTransactionSuccessful();
  		myDatabase.setVersion(currentVersion);
  		myDatabase.endTransaction();
  
  		myDatabase.execSQL("VACUUM");
  	}
  
  	@Override
  	protected Book loadBook(long bookId) {
  		Book book = null;
  		final Cursor cursor = myDatabase.rawQuery("SELECT file_id,title,encoding,language FROM Books WHERE book_id = " + bookId, null);
  		if (cursor.moveToNext()) {
  			book = createBook(
  				bookId, cursor.getLong(0), cursor.getString(1), cursor.getString(2), cursor.getString(3)
  			);
  		}
  		cursor.close();
  		return book;
  	}
  
  	@Override
  	protected Book loadBookByFile(long fileId, ZLFile file) {
  		if (fileId == -1) {
  			return null;
  		}
  		Book book = null;
  		final Cursor cursor = myDatabase.rawQuery("SELECT book_id,title,encoding,language FROM Books WHERE file_id = " + fileId, null);
  		if (cursor.moveToNext()) {
  			book = createBook(
  				cursor.getLong(0), file, cursor.getString(1), cursor.getString(2), cursor.getString(3)
  			);
  		}
  		cursor.close();
  		return book;
  	}
  
  	private boolean myTagCacheIsInitialized;
  	private final HashMap<Tag,Long> myIdByTag = new HashMap<Tag,Long>();
  	private final HashMap<Long,Tag> myTagById = new HashMap<Long,Tag>();
  
  	private void initTagCache() {
  		if (myTagCacheIsInitialized) {
  			return;
  		}
  		myTagCacheIsInitialized = true;
  
  		Cursor cursor = myDatabase.rawQuery("SELECT tag_id,parent_id,name FROM Tags ORDER BY tag_id", null);
  		while (cursor.moveToNext()) {
  			long id = cursor.getLong(0);
  			if (myTagById.get(id) == null) {
  				final Tag tag = Tag.getTag(myTagById.get(cursor.getLong(1)), cursor.getString(2));
  				myIdByTag.put(tag, id);
  				myTagById.put(id, tag);
  			}
  		}
  		cursor.close();
  	}
  
  	@Override
  	protected Map<Long,Book> loadBooks(FileInfoSet infos, boolean existing) {
  		Cursor cursor = myDatabase.rawQuery(
  			"SELECT book_id,file_id,title,encoding,language FROM Books WHERE `exists` = " + (existing ? 1 : 0), null
  		);
  		final HashMap<Long,Book> booksById = new HashMap<Long,Book>();
  		final HashMap<Long,Book> booksByFileId = new HashMap<Long,Book>();
  		while (cursor.moveToNext()) {
  			final long id = cursor.getLong(0);
  			final long fileId = cursor.getLong(1);
  			final Book book = createBook(
  				id, infos.getFile(fileId), cursor.getString(2), cursor.getString(3), cursor.getString(4)
  			);
  			if (book != null) {
  				booksById.put(id, book);
  				booksByFileId.put(fileId, book);
  			}
  		}
  		cursor.close();
  
  		initTagCache();
  
  		cursor = myDatabase.rawQuery(
  			"SELECT author_id,name,sort_key FROM Authors", null
  		);
  		final HashMap<Long,Author> authorById = new HashMap<Long,Author>();
  		while (cursor.moveToNext()) {
  			authorById.put(cursor.getLong(0), new Author(cursor.getString(1), cursor.getString(2)));
  		}
  		cursor.close();
  
  		cursor = myDatabase.rawQuery(
  			"SELECT book_id,author_id FROM BookAuthor ORDER BY author_index", null
  		);
  		while (cursor.moveToNext()) {
  			final Book book = booksById.get(cursor.getLong(0));
  			if (book != null) {
  				Author author = authorById.get(cursor.getLong(1));
  				if (author != null) {
  					addAuthor(book, author);
  				}
  			}
  		}
  		cursor.close();
  
  		cursor = myDatabase.rawQuery("SELECT book_id,tag_id FROM BookTag", null);
  		while (cursor.moveToNext()) {
  			final Book book = booksById.get(cursor.getLong(0));
  			if (book != null) {
  				addTag(book, getTagById(cursor.getLong(1)));
  			}
  		}
  		cursor.close();
  
  		cursor = myDatabase.rawQuery(
  			"SELECT series_id,name FROM Series", null
  		);
  		final HashMap<Long,String> seriesById = new HashMap<Long,String>();
  		while (cursor.moveToNext()) {
  			seriesById.put(cursor.getLong(0), cursor.getString(1));
  		}
  		cursor.close();
  
  		cursor = myDatabase.rawQuery(
  			"SELECT book_id,series_id,book_index FROM BookSeries", null
  		);
  		while (cursor.moveToNext()) {
  			final Book book = booksById.get(cursor.getLong(0));
  			if (book != null) {
  				final String series = seriesById.get(cursor.getLong(1));
  				if (series != null) {
  					setSeriesInfo(book, series, cursor.getString(2));
  				}
  			}
  		}
  		cursor.close();
  
  		cursor = myDatabase.rawQuery(
  			"SELECT book_id,type,uid FROM BookUid", null
  		);
  		while (cursor.moveToNext()) {
  			final Book book = booksById.get(cursor.getLong(0));
  			if (book != null) {
  				book.addUid(cursor.getString(1), cursor.getString(2));
  			}
  		}
  		cursor.close();
  
  		cursor = myDatabase.rawQuery(
  			"SELECT BookLabel.book_id,Labels.name FROM Labels" +
  			" INNER JOIN BookLabel ON BookLabel.label_id=Labels.label_id",
  			null
  		);
  		while (cursor.moveToNext()) {
  			final Book book = booksById.get(cursor.getLong(0));
  			if (book != null) {
  				book.addLabel(cursor.getString(1));
  			}
  		}
  		cursor.close();
  
  		cursor = myDatabase.rawQuery(
  			"SELECT book_id,numerator,denominator FROM BookReadingProgress",
  			null
  		);
  		while (cursor.moveToNext()) {
  			final Book book = booksById.get(cursor.getLong(0));
  			if (book != null) {
  				book.setProgress(RationalNumber.create(cursor.getLong(1), cursor.getLong(2)));
  			}
  		}
  		cursor.close();
  
  		cursor = myDatabase.rawQuery(
  			"SELECT book_id FROM Bookmarks WHERE visible = 1 GROUP by book_id",
  			null
  		);
  		while (cursor.moveToNext()) {
  			final Book book = booksById.get(cursor.getLong(0));
  			if (book != null) {
  				book.HasBookmark = true;
  			}
  		}
  		cursor.close();
  
  		return booksByFileId;
  	}
  
  	@Override
  	protected void setExistingFlag(Collection<Book> books, boolean flag) {
  		if (books.isEmpty()) {
  			return;
  		}
  		final StringBuilder bookSet = new StringBuilder("(");
  		boolean first = true;
  		for (Book b : books) {
  			if (first) {
  				first = false;
  			} else {
  				bookSet.append(",");
  			}
  			bookSet.append(b.getId());
  		}
  		bookSet.append(")");
  		myDatabase.execSQL(
  			"UPDATE Books SET `exists` = " + (flag ? 1 : 0) + " WHERE book_id IN " + bookSet
  		);
  	}
  
  	@Override
  	protected void updateBookInfo(long bookId, long fileId, String encoding, String language, String title) {
  		final SQLiteStatement statement = get(
  			"UPDATE OR IGNORE Books SET file_id=?, encoding=?, language=?, title=? WHERE book_id=?"
  		);
  		statement.bindLong(1, fileId);
  		SQLiteUtil.bindString(statement, 2, encoding);
  		SQLiteUtil.bindString(statement, 3, language);
  		statement.bindString(4, title);
  		statement.bindLong(5, bookId);
  		statement.execute();
  	}
  
  	@Override
  	protected long insertBookInfo(ZLFile file, String encoding, String language, String title) {
  		final SQLiteStatement statement = get(
  			"INSERT OR IGNORE INTO Books (encoding,language,title,file_id) VALUES (?,?,?,?)"
  		);
  		SQLiteUtil.bindString(statement, 1, encoding);
  		SQLiteUtil.bindString(statement, 2, language);
  		statement.bindString(3, title);
  		final FileInfoSet infoSet = new FileInfoSet(this, file);
  		statement.bindLong(4, infoSet.getId(file));
  		return statement.executeInsert();
  	}
  
  	protected void deleteAllBookAuthors(long bookId) {
  		final SQLiteStatement statement = get("DELETE FROM BookAuthor WHERE book_id=?");
  		statement.bindLong(1, bookId);
  		statement.execute();
  	}
  
  	protected void saveBookAuthorInfo(long bookId, long index, Author author) {
  		final SQLiteStatement getAuthorIdStatement = get(
  			"SELECT author_id FROM Authors WHERE name=? AND sort_key=?"
  		);
  		final SQLiteStatement insertAuthorStatement = get(
  			"INSERT OR IGNORE INTO Authors (name,sort_key) VALUES (?,?)"
  		);
  		final SQLiteStatement insertBookAuthorStatement = get(
  			"INSERT OR REPLACE INTO BookAuthor (book_id,author_id,author_index) VALUES (?,?,?)"
  		);
  
  		long authorId;
  		try {
  			getAuthorIdStatement.bindString(1, author.DisplayName);
  			getAuthorIdStatement.bindString(2, author.SortKey);
  			authorId = getAuthorIdStatement.simpleQueryForLong();
  		} catch (SQLException e) {
  			insertAuthorStatement.bindString(1, author.DisplayName);
  			insertAuthorStatement.bindString(2, author.SortKey);
  			authorId = insertAuthorStatement.executeInsert();
  		}
  		insertBookAuthorStatement.bindLong(1, bookId);
  		insertBookAuthorStatement.bindLong(2, authorId);
  		insertBookAuthorStatement.bindLong(3, index);
  		insertBookAuthorStatement.execute();
  	}
  
  	protected List<Author> listAuthors(long bookId) {
  		final Cursor cursor = myDatabase.rawQuery("SELECT Authors.name,Authors.sort_key FROM BookAuthor INNER JOIN Authors ON Authors.author_id = BookAuthor.author_id WHERE BookAuthor.book_id = ?", new String[] { String.valueOf(bookId) });
  		if (!cursor.moveToNext()) {
  			cursor.close();
  			return null;
  		}
  		final ArrayList<Author> list = new ArrayList<Author>();
  		do {
  			list.add(new Author(cursor.getString(0), cursor.getString(1)));
  		} while (cursor.moveToNext());
  		cursor.close();
  		return list;
  	}
  
  	private long getTagId(Tag tag) {
  		final SQLiteStatement getTagIdStatement = get(
  			"SELECT tag_id FROM Tags WHERE parent_id=? AND name=?"
  		);
  		{
  			final Long id = myIdByTag.get(tag);
  			if (id != null) {
  				return id;
  			}
  		}
  		if (tag.Parent != null) {
  			getTagIdStatement.bindLong(1, getTagId(tag.Parent));
  		} else {
  			getTagIdStatement.bindNull(1);
  		}
  		getTagIdStatement.bindString(2, tag.Name);
  		long id;
  		try {
  			id = getTagIdStatement.simpleQueryForLong();
  		} catch (SQLException e) {
  			final SQLiteStatement createTagIdStatement = get(
  				"INSERT OR IGNORE INTO Tags (parent_id,name) VALUES (?,?)"
  			);
  			if (tag.Parent != null) {
  				createTagIdStatement.bindLong(1, getTagId(tag.Parent));
  			} else {
  				createTagIdStatement.bindNull(1);
  			}
  			createTagIdStatement.bindString(2, tag.Name);
  			id = createTagIdStatement.executeInsert();
  		}
  		myIdByTag.put(tag, id);
  		myTagById.put(id, tag);
  		return id;
  	}
  
  	protected void deleteAllBookTags(long bookId) {
  		final SQLiteStatement statement = get("DELETE FROM BookTag WHERE book_id=?");
  		statement.bindLong(1, bookId);
  		statement.execute();
  	}
  
  	protected void saveBookTagInfo(long bookId, Tag tag) {
  		final SQLiteStatement statement = get(
  			"INSERT OR IGNORE INTO BookTag (book_id,tag_id) VALUES (?,?)"
  		);
  		statement.bindLong(1, bookId);
  		statement.bindLong(2, getTagId(tag));
  		statement.execute();
  	}
  
  	private Tag getTagById(long id) {
  		Tag tag = myTagById.get(id);
  		if (tag == null) {
  			final Cursor cursor = myDatabase.rawQuery("SELECT parent_id,name FROM Tags WHERE tag_id = ?", new String[] { String.valueOf(id) });
  			if (cursor.moveToNext()) {
  				final Tag parent = cursor.isNull(0) ? null : getTagById(cursor.getLong(0));
  				tag = Tag.getTag(parent, cursor.getString(1));
  				myIdByTag.put(tag, id);
  				myTagById.put(id, tag);
  			}
  			cursor.close();
  		}
  		return tag;
  	}
  
  	protected List<Tag> listTags(long bookId) {
  		final Cursor cursor = myDatabase.rawQuery("SELECT Tags.tag_id FROM BookTag INNER JOIN Tags ON Tags.tag_id = BookTag.tag_id WHERE BookTag.book_id = ?", new String[] { String.valueOf(bookId) });
  		if (!cursor.moveToNext()) {
  			cursor.close();
  			return null;
  		}
  		final ArrayList<Tag> list = new ArrayList<Tag>();
  		do {
  			list.add(getTagById(cursor.getLong(0)));
  		} while (cursor.moveToNext());
  		cursor.close();
  		return list;
  	}
  
  	@Override
  	protected List<String> listLabels(long bookId) {
  		final Cursor cursor = myDatabase.rawQuery(
  			"SELECT Labels.name FROM Labels" +
  			" INNER JOIN BookLabel ON BookLabel.label_id=Labels.label_id" +
  			" WHERE BookLabel.book_id=?",
  			new String[] { String.valueOf(bookId) }
  		);
  		final LinkedList<String> names = new LinkedList<String>();
  		while (cursor.moveToNext()) {
  			names.add(cursor.getString(0));
  		}
  		cursor.close();
  		return names;
  	}
  
  	protected void deleteAllBookUids(long bookId) {
  		final SQLiteStatement statement = get("DELETE FROM BookUid WHERE book_id=?");
  		statement.bindLong(1, bookId);
  		statement.execute();
  	}
  
  	@Override
  	protected void saveBookUid(long bookId, UID uid) {
  		final SQLiteStatement statement = get(
  			"INSERT OR IGNORE INTO BookUid (book_id,type,uid) VALUES (?,?,?)"
  		);
  		synchronized (statement) {
  			statement.bindLong(1, bookId);
  			statement.bindString(2, uid.Type);
  			statement.bindString(3, uid.Id);
  			statement.execute();
  		}
  	}
  
  	@Override
  	protected List<UID> listUids(long bookId) {
  		final ArrayList<UID> list = new ArrayList<UID>();
  		final Cursor cursor = myDatabase.rawQuery("SELECT type,uid FROM BookUid WHERE book_id = ?", new String[] { String.valueOf(bookId) });
  		while (cursor.moveToNext()) {
  			list.add(new UID(cursor.getString(0), cursor.getString(1)));
  		}
  		cursor.close();
  		return list;
  	}
  
  	@Override
  	protected Long bookIdByUid(UID uid) {
  		Long bookId = null;
  		final Cursor cursor = myDatabase.rawQuery("SELECT book_id FROM BookUid WHERE type = ? AND uid = ? LIMIT 1", new String[] { uid.Type, uid.Id });
  		if (cursor.moveToNext()) {
  			bookId = cursor.getLong(0);
  		}
  		cursor.close();
  		return bookId;
  	}
  
  	protected void saveBookSeriesInfo(long bookId, SeriesInfo seriesInfo) {
  		if (seriesInfo == null) {
  			final SQLiteStatement statement = get("DELETE FROM BookSeries WHERE book_id=?");
  			statement.bindLong(1, bookId);
  			statement.execute();
  		} else {
  			long seriesId;
  			try {
  				final SQLiteStatement getSeriesIdStatement = get(
  					"SELECT series_id FROM Series WHERE name = ?"
  				);
  				getSeriesIdStatement.bindString(1, seriesInfo.Series.getTitle());
  				seriesId = getSeriesIdStatement.simpleQueryForLong();
  			} catch (SQLException e) {
  				final SQLiteStatement insertSeriesStatement = get(
  					"INSERT OR IGNORE INTO Series (name) VALUES (?)"
  				);
  				insertSeriesStatement.bindString(1, seriesInfo.Series.getTitle());
  				seriesId = insertSeriesStatement.executeInsert();
  			}
  			final SQLiteStatement insertBookSeriesStatement = get(
  				"INSERT OR REPLACE INTO BookSeries (book_id,series_id,book_index) VALUES (?,?,?)"
  			);
  			insertBookSeriesStatement.bindLong(1, bookId);
  			insertBookSeriesStatement.bindLong(2, seriesId);
  			SQLiteUtil.bindString(
  				insertBookSeriesStatement, 3,
  				seriesInfo.Index != null ? seriesInfo.Index.toPlainString() : null
  			);
  			insertBookSeriesStatement.execute();
  		}
  	}
  
  	protected SeriesInfo getSeriesInfo(long bookId) {
  		final Cursor cursor = myDatabase.rawQuery("SELECT Series.name,BookSeries.book_index FROM BookSeries INNER JOIN Series ON Series.series_id = BookSeries.series_id WHERE BookSeries.book_id = ?", new String[] { String.valueOf(bookId) });
  		SeriesInfo info = null;
  		if (cursor.moveToNext()) {
  			info = SeriesInfo.createSeriesInfo(cursor.getString(0), cursor.getString(1));
  		}
  		cursor.close();
  		return info;
  	}
  
  	protected void removeFileInfo(long fileId) {
  		if (fileId == -1) {
  			return;
  		}
  		final SQLiteStatement statement = get("DELETE FROM Files WHERE file_id=?");
  		statement.bindLong(1, fileId);
  		statement.execute();
  	}
  
  	protected void saveFileInfo(FileInfo fileInfo) {
  		final long id = fileInfo.Id;
  		SQLiteStatement statement;
  		if (id == -1) {
  			statement = get(
  				"INSERT OR IGNORE INTO Files (name,parent_id,size) VALUES (?,?,?)"
  			);
  		} else {
  			statement = get(
  				"UPDATE Files SET name=?, parent_id=?, size=? WHERE file_id=?"
  			);
  		}
  		statement.bindString(1, fileInfo.Name);
  		final FileInfo parent = fileInfo.Parent;
  		if (parent != null) {
  			statement.bindLong(2, parent.Id);
  		} else {
  			statement.bindNull(2);
  		}
  		final long size = fileInfo.FileSize;
  		if (size != -1) {
  			statement.bindLong(3, size);
  		} else {
  			statement.bindNull(3);
  		}
  		if (id == -1) {
  			fileInfo.Id = statement.executeInsert();
  		} else {
  			statement.bindLong(4, id);
  			statement.execute();
  		}
  	}
  
  	protected Collection<FileInfo> loadFileInfos() {
  		Cursor cursor = myDatabase.rawQuery(
  			"SELECT file_id,name,parent_id,size FROM Files", null
  		);
  		HashMap<Long,FileInfo> infosById = new HashMap<Long,FileInfo>();
  		while (cursor.moveToNext()) {
  			final long id = cursor.getLong(0);
  			final FileInfo info = createFileInfo(id,
  				cursor.getString(1),
  				cursor.isNull(2) ? null : infosById.get(cursor.getLong(2))
  			);
  			if (!cursor.isNull(3)) {
  				info.FileSize = cursor.getLong(3);
  			}
  			infosById.put(id, info);
  		}
  		cursor.close();
  		return infosById.values();
  	}
  
  	protected Collection<FileInfo> loadFileInfos(ZLFile file) {
  		final LinkedList<ZLFile> fileStack = new LinkedList<ZLFile>();
  		for (; file != null; file = file.getParent()) {
  			fileStack.addFirst(file);
  		}
  
  		final ArrayList<FileInfo> infos = new ArrayList<FileInfo>(fileStack.size());
  		final String[] parameters = { null };
  		FileInfo current = null;
  		for (ZLFile f : fileStack) {
  			parameters[0] = f.getLongName();
  			final Cursor cursor = myDatabase.rawQuery(
  				(current == null) ?
  					"SELECT file_id,size FROM Files WHERE name = ?" :
  					"SELECT file_id,size FROM Files WHERE parent_id = " + current.Id + " AND name = ?",
  				parameters
  			);
  			if (cursor.moveToNext()) {
  				current = createFileInfo(cursor.getLong(0), parameters[0], current);
  				if (!cursor.isNull(1)) {
  					current.FileSize = cursor.getLong(1);
  				}
  				infos.add(current);
  				cursor.close();
  			} else {
  				cursor.close();
  				break;
  			}
  		}
  
  		return infos;
  	}
  
  	protected Collection<FileInfo> loadFileInfos(long fileId) {
  		final ArrayList<FileInfo> infos = new ArrayList<FileInfo>();
  		while (fileId != -1) {
  			final Cursor cursor = myDatabase.rawQuery(
  				"SELECT name,size,parent_id FROM Files WHERE file_id = " + fileId, null
  			);
  			if (cursor.moveToNext()) {
  				FileInfo info = createFileInfo(fileId, cursor.getString(0), null);
  				if (!cursor.isNull(1)) {
  					info.FileSize = cursor.getLong(1);
  				}
  				infos.add(0, info);
  				fileId = cursor.isNull(2) ? -1 : cursor.getLong(2);
  			} else {
  				fileId = -1;
  			}
  			cursor.close();
  		}
  		for (int i = 1; i < infos.size(); ++i) {
  			final FileInfo oldInfo = infos.get(i);
  			final FileInfo newInfo = createFileInfo(oldInfo.Id, oldInfo.Name, infos.get(i - 1));
  			newInfo.FileSize = oldInfo.FileSize;
  			infos.set(i, newInfo);
  		}
  		return infos;
  	}
  
  	protected void saveRecentBookIds(final List<Long> ids) {
  		final SQLiteStatement statement = get(
  			"INSERT OR IGNORE INTO RecentBooks (book_id) VALUES (?)"
  		);
  		executeAsTransaction(new Runnable() {
  			public void run() {
  				myDatabase.delete("RecentBooks", null, null);
  				for (long id : ids) {
  					statement.bindLong(1, id);
  					statement.execute();
  				}
  			}
  		});
  	}
  
  	@Override
  	protected List<Long> loadRecentBookIds() {
  		final Cursor cursor = myDatabase.rawQuery(
  			"SELECT book_id FROM RecentBooks ORDER BY book_index", null
  		);
  		final LinkedList<Long> ids = new LinkedList<Long>();
  		while (cursor.moveToNext()) {
  			ids.add(cursor.getLong(0));
  		}
  		cursor.close();
  		return ids;
  	}
  
  	@Override
  	protected void setLabel(long bookId, String label) {
  		myDatabase.execSQL("INSERT OR IGNORE INTO Labels (name) VALUES (?)", new Object[] { label });
  		final SQLiteStatement statement = get(
  			"INSERT OR IGNORE INTO BookLabel(label_id,book_id)" +
  			" SELECT label_id,? FROM Labels WHERE name=?"
  		);
  		statement.bindLong(1, bookId);
  		statement.bindString(2, label);
  		statement.execute();
  	}
  
  	@Override
  	protected void removeLabel(long bookId, String label) {
  		final SQLiteStatement statement = get(
  			"DELETE FROM BookLabel WHERE book_id=? AND label_id IN" +
  			" (SELECT label_id FROM Labels WHERE name=?)"
  		);
  		statement.bindLong(1, bookId);
  		statement.bindString(2, label);
  		statement.execute();
  	}
  
  	@Override
  	protected boolean hasVisibleBookmark(long bookId) {
  		final Cursor cursor = myDatabase.rawQuery(
  			"SELECT bookmark_id FROM Bookmarks WHERE book_id = " + bookId +
  			" AND visible = 1 LIMIT 1", null
  		);
  		final boolean result = cursor.moveToNext();
  		cursor.close();
  		return result;
  	}
  
  	@Override
  	protected List<Bookmark> loadBookmarks(BookmarkQuery query) {
  		final LinkedList<Bookmark> list = new LinkedList<Bookmark>();
  		final StringBuilder sql = new StringBuilder("SELECT")
  			.append(" bm.bookmark_id,bm.book_id,b.title,bm.bookmark_text,")
  			.append("bm.creation_time,bm.modification_time,bm.access_time,bm.access_counter,")
  			.append("bm.model_id,bm.paragraph,bm.word,bm.char,")
  			.append("bm.end_paragraph,bm.end_word,bm.end_character,")
  			.append("bm.style_id")
  			.append(" FROM Bookmarks AS bm")
  			.append(" INNER JOIN Books AS b ON b.book_id = bm.book_id")
  			.append(" WHERE");
  		if (query.Book != null) {
  			sql.append(" b.book_id = " + query.Book.getId() +" AND");
  		}
  		sql
  			.append(" bm.visible = " + (query.Visible ? 1 : 0))
  			.append(" ORDER BY bm.bookmark_id")
  			.append(" LIMIT " + query.Limit * query.Page + "," + query.Limit);
  		Cursor cursor = myDatabase.rawQuery(sql.toString(), null);
  		while (cursor.moveToNext()) {
  			list.add(createBookmark(
  				cursor.getLong(0),
  				cursor.getLong(1),
  				cursor.getString(2),
  				cursor.getString(3),
  				SQLiteUtil.getDate(cursor, 4),
  				SQLiteUtil.getDate(cursor, 5),
  				SQLiteUtil.getDate(cursor, 6),
  				(int)cursor.getLong(7),
  				cursor.getString(8),
  				(int)cursor.getLong(9),
  				(int)cursor.getLong(10),
  				(int)cursor.getLong(11),
  				(int)cursor.getLong(12),
  				cursor.isNull(13) ? -1 : (int)cursor.getLong(13),
  				cursor.isNull(14) ? -1 : (int)cursor.getLong(14),
  				query.Visible,
  				(int)cursor.getLong(15)
  			));
  		}
  		cursor.close();
  		return list;
  	}
  
  	@Override
  	protected List<HighlightingStyle> loadStyles() {
  		final LinkedList<HighlightingStyle> list = new LinkedList<HighlightingStyle>();
  		final String sql = "SELECT style_id,name,bg_color FROM HighlightingStyle";
  		final Cursor cursor = myDatabase.rawQuery(sql, null);
  		while (cursor.moveToNext()) {
  			list.add(createStyle(
  				(int)cursor.getLong(0),
  				cursor.getString(1),
  				(int)cursor.getLong(2)
  			));
  		}
  		cursor.close();
  		return list;
  	}
  
  	protected void saveStyle(HighlightingStyle style) {
  		final SQLiteStatement statement = get(
  			"INSERT OR REPLACE INTO HighlightingStyle (style_id,name,bg_color) VALUES (?,?,?)"
  		);
  		statement.bindLong(1, style.Id);
  		final String name = style.getName();
  		statement.bindString(2, name != null ? name : "");
  		final ZLColor bgColor = style.getBackgroundColor();
  		statement.bindLong(3, bgColor != null ? bgColor.intValue() : -1);
  		statement.execute();
  	}
  
  	@Override
  	protected long saveBookmark(Bookmark bookmark) {
  		SQLiteStatement statement;
  		if (bookmark.getId() == -1) {
  			statement = get(
  				"INSERT OR IGNORE INTO Bookmarks (book_id,bookmark_text,creation_time,modification_time,access_time,access_counter,model_id,paragraph,word,char,end_paragraph,end_word,end_character,visible,style_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
  			);
  		} else {
  			statement = get(
  				"UPDATE Bookmarks SET book_id = ?, bookmark_text = ?, creation_time =?, modification_time = ?,access_time = ?, access_counter = ?, model_id = ?, paragraph = ?, word = ?, char = ?, end_paragraph = ?, end_word = ?, end_character = ?, visible = ?, style_id = ? WHERE bookmark_id = ?"
  			);
  		}
  
  		statement.bindLong(1, bookmark.getBookId());
  		statement.bindString(2, bookmark.getText());
  		SQLiteUtil.bindDate(statement, 3, bookmark.getDate(Bookmark.DateType.Creation));
  		SQLiteUtil.bindDate(statement, 4, bookmark.getDate(Bookmark.DateType.Modification));
  		SQLiteUtil.bindDate(statement, 5, bookmark.getDate(Bookmark.DateType.Access));
  		statement.bindLong(6, bookmark.getAccessCount());
  		SQLiteUtil.bindString(statement, 7, bookmark.ModelId);
  		statement.bindLong(8, bookmark.ParagraphIndex);
  		statement.bindLong(9, bookmark.ElementIndex);
  		statement.bindLong(10, bookmark.CharIndex);
  		final ZLTextPosition end = bookmark.getEnd();
  		if (end != null) {
  			statement.bindLong(11, end.getParagraphIndex());
  			statement.bindLong(12, end.getElementIndex());
  			statement.bindLong(13, end.getCharIndex());
  		} else {
  			statement.bindLong(11, bookmark.getLength());
  			statement.bindNull(12);
  			statement.bindNull(13);
  		}
  		statement.bindLong(14, bookmark.IsVisible ? 1 : 0);
  		statement.bindLong(15, bookmark.getStyleId());
  
  		if (bookmark.getId() == -1) {
  			return statement.executeInsert();
  		} else {
  			final long id = bookmark.getId();
  			statement.bindLong(16, id);
  			statement.execute();
  			return id;
  		}
  	}
  
  	@Override
  	protected void deleteBookmark(Bookmark bookmark) {
  		final SQLiteStatement statement = get("DELETE FROM Bookmarks WHERE bookmark_id=?");
  		statement.bindLong(1, bookmark.getId());
  		statement.execute();
  	}
  
  	protected ZLTextFixedPosition.WithTimestamp getStoredPosition(long bookId) {
  		ZLTextFixedPosition.WithTimestamp position = null;
  		Cursor cursor = myDatabase.rawQuery(
  			"SELECT paragraph,word,char,timestamp FROM BookState WHERE book_id = " + bookId, null
  		);
  		if (cursor.moveToNext()) {
  			position = new ZLTextFixedPosition.WithTimestamp(
  				(int)cursor.getLong(0),
  				(int)cursor.getLong(1),
  				(int)cursor.getLong(2),
  				cursor.getLong(3)
  			);
  		}
  		cursor.close();
  		return position;
  	}
  
  	protected void storePosition(long bookId, ZLTextPosition position) {
  		final SQLiteStatement statement = get(
  			"INSERT OR REPLACE INTO BookState (book_id,paragraph,word,char,timestamp) VALUES (?,?,?,?,?)"
  		);
  		statement.bindLong(1, bookId);
  		statement.bindLong(2, position.getParagraphIndex());
  		statement.bindLong(3, position.getElementIndex());
  		statement.bindLong(4, position.getCharIndex());
  
  		long timestamp = -1;
  		if (position instanceof ZLTextFixedPosition.WithTimestamp) {
  			timestamp = ((ZLTextFixedPosition.WithTimestamp)position).Timestamp;
  		}
  		if (timestamp == -1) {
  			timestamp = System.currentTimeMillis();
  		}
  		statement.bindLong(5, timestamp);
  
  		statement.execute();
  	}
  
  	private void deleteVisitedHyperlinks(long bookId) {
  		final SQLiteStatement statement = get("DELETE FROM VisitedHyperlinks WHERE book_id=?");
  		statement.bindLong(1, bookId);
  		statement.execute();
  	}
  
  	protected void addVisitedHyperlink(long bookId, String hyperlinkId) {
  		final SQLiteStatement statement = get(
  			"INSERT OR IGNORE INTO VisitedHyperlinks(book_id,hyperlink_id) VALUES (?,?)"
  		);
  		statement.bindLong(1, bookId);
  		statement.bindString(2, hyperlinkId);
  		statement.execute();
  	}
  
  	protected Collection<String> loadVisitedHyperlinks(long bookId) {
  		final TreeSet<String> links = new TreeSet<String>();
  		final Cursor cursor = myDatabase.rawQuery("SELECT hyperlink_id FROM VisitedHyperlinks WHERE book_id = ?", new String[] { String.valueOf(bookId) });
  		while (cursor.moveToNext()) {
  			links.add(cursor.getString(0));
  		}
  		cursor.close();
  		return links;
  	}
  
  	@Override
  	protected void saveBookProgress(long bookId, RationalNumber progress) {
  		final SQLiteStatement statement = get(
  			"INSERT OR REPLACE INTO BookReadingProgress (book_id,numerator,denominator) VALUES (?,?,?)"
  		);
  		statement.bindLong(1, bookId);
  		statement.bindLong(2, progress.Numerator);
  		statement.bindLong(3, progress.Denominator);
  		statement.execute();
  	}
  
  	@Override
  	protected RationalNumber getProgress(long bookId) {
  		final RationalNumber progress;
  		final Cursor cursor = myDatabase.rawQuery(
  			"SELECT numerator,denominator FROM BookReadingProgress WHERE book_id=" + bookId, null
  		);
  		if (cursor.moveToNext()) {
  			progress = RationalNumber.create(cursor.getLong(0), cursor.getLong(1));
  		} else {
  			progress = null;
  		}
  		cursor.close();
  		return progress;
  	}
  
  	@Override
  	protected String getHash(long bookId, long lastModified) throws NotAvailable {
  		try {
  			final SQLiteStatement statement = get(
  				"SELECT hash FROM BookHash WHERE book_id=? AND timestamp>?"
  			);
  			statement.bindLong(1, bookId);
  			statement.bindLong(2, lastModified);
  			try {
  				return statement.simpleQueryForString();
  			} catch (SQLiteDoneException e) {
  				return null;
  			}
  		} catch (Throwable t) {
  			throw new NotAvailable();
  		}
  	}
  
  	@Override
  	protected void setHash(long bookId, String hash) throws NotAvailable {
  		try {
  			final SQLiteStatement statement = get(
  				"INSERT OR REPLACE INTO BookHash (book_id,timestamp,hash) VALUES (?,?,?)"
  			);
  			statement.bindLong(1, bookId);
  			statement.bindLong(2, System.currentTimeMillis());
  			statement.bindString(3, hash);
  			statement.execute();
  		} catch (Throwable t) {
  			throw new NotAvailable();
  		}
  	}
  
  	@Override
  	protected List<Long> bookIdsByHash(String hash) {
  		final Cursor cursor = myDatabase.rawQuery(
  			"SELECT book_id FROM BookHash WHERE hash=?", new String[] { hash }
  		);
  		final List<Long> bookIds = new LinkedList<Long>();
  		while (cursor.moveToNext()) {
  			bookIds.add(cursor.getLong(0));
  		}
  		cursor.close();
  		return bookIds;
  	}
  
  	@Override
  	protected void deleteBook(long bookId) {
  		myDatabase.beginTransaction();
  		myDatabase.execSQL("DELETE FROM BookHash WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM BookAuthor WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM BookLabel WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM BookReadingProgress WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM BookSeries WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM BookState WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM BookTag WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM BookUid WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM Bookmarks WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM RecentBooks WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM VisitedHyperlinks WHERE book_id=" + bookId);
  		myDatabase.execSQL("DELETE FROM Books WHERE book_id=" + bookId);
  		myDatabase.setTransactionSuccessful();
  		myDatabase.endTransaction();
  	}
  
  	private void createTables() {
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS Books(" +
  				"book_id INTEGER PRIMARY KEY," +
  				"encoding TEXT," +
  				"language TEXT," +
  				"title TEXT NOT NULL," +
  				"file_name TEXT UNIQUE NOT NULL)");
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS Authors(" +
  				"author_id INTEGER PRIMARY KEY," +
  				"name TEXT NOT NULL," +
  				"sort_key TEXT NOT NULL," +
  				"CONSTRAINT Authors_Unique UNIQUE (name, sort_key))");
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS BookAuthor(" +
  				"author_id INTEGER NOT NULL REFERENCES Authors(author_id)," +
  				"book_id INTEGER NOT NULL REFERENCES Books(book_id)," +
  				"author_index INTEGER NOT NULL," +
  				"CONSTRAINT BookAuthor_Unique0 UNIQUE (author_id, book_id)," +
  				"CONSTRAINT BookAuthor_Unique1 UNIQUE (book_id, author_index))");
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS Series(" +
  				"series_id INTEGER PRIMARY KEY," +
  				"name TEXT UNIQUE NOT NULL)");
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS BookSeries(" +
  				"series_id INTEGER NOT NULL REFERENCES Series(series_id)," +
  				"book_id INTEGER NOT NULL UNIQUE REFERENCES Books(book_id)," +
  				"book_index INTEGER)");
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS Tags(" +
  				"tag_id INTEGER PRIMARY KEY," +
  				"name TEXT NOT NULL," +
  				"parent INTEGER REFERENCES Tags(tag_id)," +
  				"CONSTRAINT Tags_Unique UNIQUE (name, parent))");
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS BookTag(" +
  				"tag_id INTEGER REFERENCES Tags(tag_id)," +
  				"book_id INTEGER REFERENCES Books(book_id)," +
  				"CONSTRAINT BookTag_Unique UNIQUE (tag_id, book_id))");
  	}
  
  	private void updateTables1() {
  		myDatabase.execSQL("ALTER TABLE Tags RENAME TO Tags_Obsolete");
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS Tags(" +
  				"tag_id INTEGER PRIMARY KEY," +
  				"name TEXT NOT NULL," +
  				"parent_id INTEGER REFERENCES Tags(tag_id)," +
  				"CONSTRAINT Tags_Unique UNIQUE (name, parent_id))");
  		myDatabase.execSQL("INSERT INTO Tags (tag_id,name,parent_id) SELECT tag_id,name,parent FROM Tags_Obsolete");
  		myDatabase.execSQL("DROP TABLE IF EXISTS Tags_Obsolete");
  
  		myDatabase.execSQL("ALTER TABLE BookTag RENAME TO BookTag_Obsolete");
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS BookTag(" +
  				"tag_id INTEGER NOT NULL REFERENCES Tags(tag_id)," +
  				"book_id INTEGER NOT NULL REFERENCES Books(book_id)," +
  				"CONSTRAINT BookTag_Unique UNIQUE (tag_id, book_id))");
  		myDatabase.execSQL("INSERT INTO BookTag (tag_id,book_id) SELECT tag_id,book_id FROM BookTag_Obsolete");
  		myDatabase.execSQL("DROP TABLE IF EXISTS BookTag_Obsolete");
  	}
  
  	private void updateTables2() {
  		myDatabase.execSQL("CREATE INDEX BookAuthor_BookIndex ON BookAuthor (book_id)");
  		myDatabase.execSQL("CREATE INDEX BookTag_BookIndex ON BookTag (book_id)");
  		myDatabase.execSQL("CREATE INDEX BookSeries_BookIndex ON BookSeries (book_id)");
  	}
  
  	private void updateTables3() {
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS Files(" +
  				"file_id INTEGER PRIMARY KEY," +
  				"name TEXT NOT NULL," +
  				"parent_id INTEGER REFERENCES Files(file_id)," +
  				"size INTEGER," +
  				"CONSTRAINT Files_Unique UNIQUE (name, parent_id))");
  	}
  
  	private void updateTables4() {
  		final FileInfoSet fileInfos = new FileInfoSet(this);
  		final Cursor cursor = myDatabase.rawQuery(
  			"SELECT file_name FROM Books", null
  		);
  		while (cursor.moveToNext()) {
  			fileInfos.check(ZLFile.createFileByPath(cursor.getString(0)).getPhysicalFile(), false);
  		}
  		cursor.close();
  		fileInfos.save();
  
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS RecentBooks(" +
  				"book_index INTEGER PRIMARY KEY," +
  				"book_id INTEGER REFERENCES Books(book_id))");
  		final ArrayList<Long> ids = new ArrayList<Long>();
  
  		final SQLiteStatement statement = myDatabase.compileStatement(
  			"SELECT book_id FROM Books WHERE file_name = ?"
  		);
  
  		for (int i = 0; i < 20; ++i) {
  			final ZLStringOption option = new ZLStringOption("LastOpenedBooks", "Book" + i, "");
  			final String fileName = option.getValue();
  			option.setValue("");
  			try {
  				statement.bindString(1, fileName);
  				final long bookId = statement.simpleQueryForLong();
  				if (bookId != -1) {
  					ids.add(bookId);
  				}
  			} catch (SQLException e) {
  			}
  		}
  		saveRecentBookIds(ids);
  	}
  
  	private void updateTables5() {
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS Bookmarks(" +
  				"bookmark_id INTEGER PRIMARY KEY," +
  				"book_id INTEGER NOT NULL REFERENCES Books(book_id)," +
  				"bookmark_text TEXT NOT NULL," +
  				"creation_time INTEGER NOT NULL," +
  				"modification_time INTEGER," +
  				"access_time INTEGER," +
  				"access_counter INTEGER NOT NULL," +
  				"paragraph INTEGER NOT NULL," +
  				"word INTEGER NOT NULL," +
  				"char INTEGER NOT NULL)");
  
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS BookState(" +
  				"book_id INTEGER UNIQUE NOT NULL REFERENCES Books(book_id)," +
  				"paragraph INTEGER NOT NULL," +
  				"word INTEGER NOT NULL," +
  				"char INTEGER NOT NULL)");
  		Cursor cursor = myDatabase.rawQuery(
  			"SELECT book_id,file_name FROM Books", null
  		);
  		final SQLiteStatement statement = myDatabase.compileStatement("INSERT INTO BookState (book_id,paragraph,word,char) VALUES (?,?,?,?)");
  		while (cursor.moveToNext()) {
  			final long bookId = cursor.getLong(0);
  			final String fileName = cursor.getString(1);
  			final int position = new ZLIntegerOption(fileName, "PositionInBuffer", 0).getValue();
  			final int paragraph = new ZLIntegerOption(fileName, "Paragraph_" + position, 0).getValue();
  			final int word = new ZLIntegerOption(fileName, "Word_" + position, 0).getValue();
  			final int chr = new ZLIntegerOption(fileName, "Char_" + position, 0).getValue();
  			if ((paragraph != 0) || (word != 0) || (chr != 0)) {
  				statement.bindLong(1, bookId);
  				statement.bindLong(2, paragraph);
  				statement.bindLong(3, word);
  				statement.bindLong(4, chr);
  				statement.execute();
  			}
  			Config.Instance().removeGroup(fileName);
  		}
  		cursor.close();
  	}
  
  	private void updateTables6() {
  		myDatabase.execSQL(
  			"ALTER TABLE Bookmarks ADD COLUMN model_id TEXT"
  		);
  
  		myDatabase.execSQL(
  			"ALTER TABLE Books ADD COLUMN file_id INTEGER"
  		);
  
  		myDatabase.execSQL("DELETE FROM Files");
  		final FileInfoSet infoSet = new FileInfoSet(this);
  		Cursor cursor = myDatabase.rawQuery(
  			"SELECT file_name FROM Books", null
  		);
  		while (cursor.moveToNext()) {
  			infoSet.check(ZLFile.createFileByPath(cursor.getString(0)).getPhysicalFile(), false);
  		}
  		cursor.close();
  		infoSet.save();
  
  		cursor = myDatabase.rawQuery(
  			"SELECT book_id,file_name FROM Books", null
  		);
  		final SQLiteStatement deleteStatement = myDatabase.compileStatement("DELETE FROM Books WHERE book_id=?");
  		final SQLiteStatement updateStatement = myDatabase.compileStatement("UPDATE OR IGNORE Books SET file_id=? WHERE book_id=?");
  		while (cursor.moveToNext()) {
  			final long bookId = cursor.getLong(0);
  			final long fileId = infoSet.getId(ZLFile.createFileByPath(cursor.getString(1)));
  
  			if (fileId == -1) {
  				deleteStatement.bindLong(1, bookId);
  				deleteStatement.execute();
  			} else {
  				updateStatement.bindLong(1, fileId);
  				updateStatement.bindLong(2, bookId);
  				updateStatement.execute();
  			}
  		}
  		cursor.close();
  
  		myDatabase.execSQL("ALTER TABLE Books RENAME TO Books_Obsolete");
  		myDatabase.execSQL(
  			"CREATE TABLE IF NOT EXISTS Books(" +
  				"book_id INTEGER PRIMARY KEY," +
  				"encoding TEXT," +
  				"language TEXT," +
  				"title TEXT NOT NULL," +
  				"file_id INTEGER UNIQUE NOT NULL REFERENCES Files(file_id))");
  		myDatabase.execSQL("INSERT INTO Books (book_id,encoding,language,title,file_id) SELECT book_id,encoding,language,title,file_id FROM Books_Obsolete");
  		myDatabase.execSQL("DROP TABLE IF EXISTS Books_Obsolete");
  	}
  
  	private void updateTables7() {
  		final ArrayList<Long> seriesIDs = new ArrayList<Long>();
  		Cursor cursor = myDatabase.rawQuery(
  			"SELECT series_id,name FROM Series", null
  		);
  		while (cursor.moveToNext()) {
  			if (cursor.getString(1).length() > 200) {
  				seriesIDs.add(cursor.getLong(0));
  			}
  		}
  		cursor.close();
  