mirror of
https://github.com/geometer/FBReaderJ.git
synced 2025-10-05 02:39:23 +02:00
BookCollection.build()
This commit is contained in:
parent
cc335e3850
commit
2622d74733
3 changed files with 318 additions and 8 deletions
|
@ -34,10 +34,24 @@ public class LibraryService extends Service {
|
||||||
if (database == null) {
|
if (database == null) {
|
||||||
database = new SQLiteBooksDatabase(LibraryService.this, "LIBRARY SERVICE");
|
database = new SQLiteBooksDatabase(LibraryService.this, "LIBRARY SERVICE");
|
||||||
}
|
}
|
||||||
final Library collection = Library.Instance();
|
final BookCollection collection = new BookCollection(database);
|
||||||
collection.addChangeListener(new Library.ChangeListener() {
|
final long start = System.currentTimeMillis();
|
||||||
public void onLibraryChanged(final Code code) {
|
collection.addChangeListener(new BookCollection.ChangeListener() {
|
||||||
System.err.println("LibraryService.onLibraryChanged(" + code + ")");
|
public void onCollectionChanged(Code code, Book book) {
|
||||||
|
switch (code) {
|
||||||
|
case BookAdded:
|
||||||
|
System.err.println("Added " + book.getTitle());
|
||||||
|
break;
|
||||||
|
case BuildStarted:
|
||||||
|
System.err.println("Build started");
|
||||||
|
break;
|
||||||
|
case BuildSucceeded:
|
||||||
|
System.err.println("Build succeeded");
|
||||||
|
break;
|
||||||
|
case BuildCompleted:
|
||||||
|
System.err.println("Build completed with " + collection.size() + " books in " + (System.currentTimeMillis() - start) + " milliseconds");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
collection.startBuild();
|
collection.startBuild();
|
||||||
|
|
|
@ -19,19 +19,65 @@
|
||||||
|
|
||||||
package org.geometerplus.fbreader.library;
|
package org.geometerplus.fbreader.library;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
|
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
|
||||||
|
import org.geometerplus.zlibrary.core.filesystem.ZLPhysicalFile;
|
||||||
|
|
||||||
class BookCollection {
|
import org.geometerplus.fbreader.Paths;
|
||||||
|
import org.geometerplus.fbreader.bookmodel.BookReadingException;
|
||||||
|
|
||||||
|
public class BookCollection {
|
||||||
|
private final List<ChangeListener> myListeners = Collections.synchronizedList(new LinkedList<ChangeListener>());
|
||||||
|
|
||||||
|
public interface ChangeListener {
|
||||||
|
public enum Code {
|
||||||
|
BookAdded,
|
||||||
|
BookRemoved,
|
||||||
|
BuildStarted,
|
||||||
|
BuildNotStarted,
|
||||||
|
BuildSucceeded,
|
||||||
|
BuildCompleted
|
||||||
|
}
|
||||||
|
|
||||||
|
void onCollectionChanged(Code code, Book book);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChangeListener(ChangeListener listener) {
|
||||||
|
myListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeChangeListener(ChangeListener listener) {
|
||||||
|
myListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fireModelChangedEvent(ChangeListener.Code code, Book book) {
|
||||||
|
synchronized (myListeners) {
|
||||||
|
for (ChangeListener l : myListeners) {
|
||||||
|
l.onCollectionChanged(code, book);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private final BooksDatabase myDatabase;
|
||||||
private final Map<ZLFile,Book> myBooks =
|
private final Map<ZLFile,Book> myBooks =
|
||||||
Collections.synchronizedMap(new HashMap<ZLFile,Book>());
|
Collections.synchronizedMap(new HashMap<ZLFile,Book>());
|
||||||
|
private volatile boolean myBuildStarted = false;
|
||||||
|
|
||||||
|
public BookCollection(BooksDatabase db) {
|
||||||
|
myDatabase = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return myBooks.size();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean addBook(Book book) {
|
public boolean addBook(Book book) {
|
||||||
if (myBooks.containsKey(book.File)) {
|
if (book == null || myBooks.containsKey(book.File)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
myBooks.put(book.File, book);
|
myBooks.put(book.File, book);
|
||||||
|
fireModelChangedEvent(ChangeListener.Code.BookAdded, book);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,4 +90,238 @@ class BookCollection {
|
||||||
return new ArrayList<Book>(myBooks.values());
|
return new ArrayList<Book>(myBooks.values());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void startBuild() {
|
||||||
|
if (myBuildStarted) {
|
||||||
|
fireModelChangedEvent(ChangeListener.Code.BuildNotStarted, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
myBuildStarted = true;
|
||||||
|
|
||||||
|
final Thread builder = new Thread("Library.build") {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
fireModelChangedEvent(ChangeListener.Code.BuildStarted, null);
|
||||||
|
build();
|
||||||
|
fireModelChangedEvent(ChangeListener.Code.BuildSucceeded, null);
|
||||||
|
} finally {
|
||||||
|
fireModelChangedEvent(ChangeListener.Code.BuildCompleted, null);
|
||||||
|
myBuildStarted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
builder.setPriority((Thread.MIN_PRIORITY + Thread.NORM_PRIORITY) / 2);
|
||||||
|
builder.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void build() {
|
||||||
|
// Step 0: get database books marked as "existing"
|
||||||
|
final FileInfoSet fileInfos = new FileInfoSet();
|
||||||
|
final Map<Long,Book> savedBooksByFileId = myDatabase.loadBooks(fileInfos, true);
|
||||||
|
final Map<Long,Book> savedBooksByBookId = new HashMap<Long,Book>();
|
||||||
|
for (Book b : savedBooksByFileId.values()) {
|
||||||
|
savedBooksByBookId.put(b.getId(), b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: set myDoGroupTitlesByFirstLetter value,
|
||||||
|
// add "existing" books into recent and favorites lists
|
||||||
|
//if (savedBooksByFileId.size() > 10) {
|
||||||
|
// final HashSet<String> letterSet = new HashSet<String>();
|
||||||
|
// for (Book book : savedBooksByFileId.values()) {
|
||||||
|
// final String letter = TitleTree.firstTitleLetter(book);
|
||||||
|
// if (letter != null) {
|
||||||
|
// letterSet.add(letter);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// myDoGroupTitlesByFirstLetter = savedBooksByFileId.values().size() > letterSet.size() * 5 / 4;
|
||||||
|
//}
|
||||||
|
|
||||||
|
for (long id : myDatabase.loadRecentBookIds()) {
|
||||||
|
Book book = savedBooksByBookId.get(id);
|
||||||
|
if (book == null) {
|
||||||
|
book = Book.getById(id);
|
||||||
|
if (book != null && !book.File.exists()) {
|
||||||
|
book = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addBook(book);
|
||||||
|
//if (book != null) {
|
||||||
|
// new BookTree(getFirstLevelTree(ROOT_RECENT), book, true);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (long id : myDatabase.loadFavoritesIds()) {
|
||||||
|
Book book = savedBooksByBookId.get(id);
|
||||||
|
if (book == null) {
|
||||||
|
book = Book.getById(id);
|
||||||
|
if (book != null && !book.File.exists()) {
|
||||||
|
book = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addBook(book);
|
||||||
|
//if (book != null) {
|
||||||
|
// getFirstLevelTree(ROOT_FAVORITES).getBookSubTree(book, true);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: check if files corresponding to "existing" books really exists;
|
||||||
|
// add books to library if yes (and reload book info if needed);
|
||||||
|
// remove from recent/favorites list if no;
|
||||||
|
// collect newly "orphaned" books
|
||||||
|
final Set<Book> orphanedBooks = new HashSet<Book>();
|
||||||
|
final Set<ZLPhysicalFile> physicalFiles = new HashSet<ZLPhysicalFile>();
|
||||||
|
int count = 0;
|
||||||
|
for (Book book : savedBooksByFileId.values()) {
|
||||||
|
synchronized (this) {
|
||||||
|
final ZLPhysicalFile file = book.File.getPhysicalFile();
|
||||||
|
if (file != null) {
|
||||||
|
physicalFiles.add(file);
|
||||||
|
}
|
||||||
|
if (file != book.File && file != null && file.getPath().endsWith(".epub")) {
|
||||||
|
myDatabase.deleteFromBookList(book.getId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (book.File.exists()) {
|
||||||
|
boolean doAdd = true;
|
||||||
|
if (file == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!fileInfos.check(file, true)) {
|
||||||
|
try {
|
||||||
|
book.readMetaInfo();
|
||||||
|
book.save();
|
||||||
|
} catch (BookReadingException e) {
|
||||||
|
doAdd = false;
|
||||||
|
}
|
||||||
|
file.setCached(false);
|
||||||
|
}
|
||||||
|
if (doAdd) {
|
||||||
|
addBook(book);
|
||||||
|
//addBookToLibrary(book);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//myRootTree.removeBook(book, true);
|
||||||
|
//fireModelChangedEvent(ChangeListener.Code.BookRemoved);
|
||||||
|
orphanedBooks.add(book);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
myDatabase.setExistingFlag(orphanedBooks, false);
|
||||||
|
|
||||||
|
// Step 3: collect books from physical files; add new, update already added,
|
||||||
|
// unmark orphaned as existing again, collect newly added
|
||||||
|
final Map<Long,Book> orphanedBooksByFileId = myDatabase.loadBooks(fileInfos, false);
|
||||||
|
final Set<Book> newBooks = new HashSet<Book>();
|
||||||
|
|
||||||
|
final List<ZLPhysicalFile> physicalFilesList = collectPhysicalFiles();
|
||||||
|
for (ZLPhysicalFile file : physicalFilesList) {
|
||||||
|
if (physicalFiles.contains(file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
collectBooks(
|
||||||
|
file, fileInfos,
|
||||||
|
savedBooksByFileId, orphanedBooksByFileId,
|
||||||
|
newBooks,
|
||||||
|
!fileInfos.check(file, true)
|
||||||
|
);
|
||||||
|
file.setCached(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: add help file
|
||||||
|
try {
|
||||||
|
final ZLFile helpFile = Library.getHelpFile();
|
||||||
|
Book helpBook = savedBooksByFileId.get(fileInfos.getId(helpFile));
|
||||||
|
if (helpBook == null) {
|
||||||
|
helpBook = new Book(helpFile);
|
||||||
|
}
|
||||||
|
//addBookToLibrary(helpBook);
|
||||||
|
addBook(helpBook);
|
||||||
|
} catch (BookReadingException e) {
|
||||||
|
// that's impossible
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: save changes into database
|
||||||
|
fileInfos.save();
|
||||||
|
|
||||||
|
myDatabase.executeAsATransaction(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
for (Book book : newBooks) {
|
||||||
|
book.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
myDatabase.setExistingFlag(newBooks, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ZLPhysicalFile> collectPhysicalFiles() {
|
||||||
|
final Queue<ZLFile> dirQueue = new LinkedList<ZLFile>();
|
||||||
|
final HashSet<ZLFile> dirSet = new HashSet<ZLFile>();
|
||||||
|
final LinkedList<ZLPhysicalFile> fileList = new LinkedList<ZLPhysicalFile>();
|
||||||
|
|
||||||
|
dirQueue.offer(new ZLPhysicalFile(new File(Paths.BooksDirectoryOption().getValue())));
|
||||||
|
|
||||||
|
while (!dirQueue.isEmpty()) {
|
||||||
|
for (ZLFile file : dirQueue.poll().children()) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
if (!dirSet.contains(file)) {
|
||||||
|
dirQueue.add(file);
|
||||||
|
dirSet.add(file);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file.setCached(true);
|
||||||
|
fileList.add((ZLPhysicalFile)file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectBooks(
|
||||||
|
ZLFile file, FileInfoSet fileInfos,
|
||||||
|
Map<Long,Book> savedBooksByFileId, Map<Long,Book> orphanedBooksByFileId,
|
||||||
|
Set<Book> newBooks,
|
||||||
|
boolean doReadMetaInfo
|
||||||
|
) {
|
||||||
|
final long fileId = fileInfos.getId(file);
|
||||||
|
if (savedBooksByFileId.get(fileId) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Book book = orphanedBooksByFileId.get(fileId);
|
||||||
|
if (book != null) {
|
||||||
|
if (doReadMetaInfo) {
|
||||||
|
book.readMetaInfo();
|
||||||
|
}
|
||||||
|
//addBookToLibrary(book);
|
||||||
|
addBook(book);
|
||||||
|
newBooks.add(book);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (BookReadingException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Book book = new Book(file);
|
||||||
|
//addBookToLibrary(book);
|
||||||
|
addBook(book);
|
||||||
|
newBooks.add(book);
|
||||||
|
return;
|
||||||
|
} catch (BookReadingException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.isArchive()) {
|
||||||
|
for (ZLFile entry : fileInfos.archiveEntries(file)) {
|
||||||
|
collectBooks(
|
||||||
|
entry, fileInfos,
|
||||||
|
savedBooksByFileId, orphanedBooksByFileId,
|
||||||
|
newBooks,
|
||||||
|
doReadMetaInfo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,8 +82,7 @@ public final class Library {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final BooksDatabase myDatabase;
|
private final BooksDatabase myDatabase;
|
||||||
|
private final BookCollection myCollection;
|
||||||
private final BookCollection myCollection = new BookCollection();
|
|
||||||
|
|
||||||
private final RootTree myRootTree = new RootTree();
|
private final RootTree myRootTree = new RootTree();
|
||||||
private boolean myDoGroupTitlesByFirstLetter;
|
private boolean myDoGroupTitlesByFirstLetter;
|
||||||
|
@ -104,6 +103,23 @@ public final class Library {
|
||||||
|
|
||||||
public Library(BooksDatabase db) {
|
public Library(BooksDatabase db) {
|
||||||
myDatabase = db;
|
myDatabase = db;
|
||||||
|
myCollection = new BookCollection(db);
|
||||||
|
myCollection.addChangeListener(new BookCollection.ChangeListener() {
|
||||||
|
public void onCollectionChanged(Code code, Book book) {
|
||||||
|
switch (code) {
|
||||||
|
case BookAdded:
|
||||||
|
//System.err.println("Added a book");
|
||||||
|
break;
|
||||||
|
case BuildStarted:
|
||||||
|
Library.this.fireModelChangedEvent(ChangeListener.Code.StatusChanged);
|
||||||
|
setStatus(myStatusMask | STATUS_LOADING);
|
||||||
|
break;
|
||||||
|
case BuildCompleted:
|
||||||
|
setStatus(myStatusMask & ~STATUS_LOADING);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
new FavoritesTree(myRootTree, ROOT_FAVORITES);
|
new FavoritesTree(myRootTree, ROOT_FAVORITES);
|
||||||
new FirstLevelTree(myRootTree, ROOT_RECENT);
|
new FirstLevelTree(myRootTree, ROOT_RECENT);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue