mirror of
https://github.com/geometer/FBReaderJ.git
synced 2025-10-04 10:19:33 +02:00
532 lines
13 KiB
Java
532 lines
13 KiB
Java
/*
|
|
* Copyright (C) 2007-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.
|
|
*/
|
|
|
|
package org.geometerplus.fbreader.library;
|
|
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.*;
|
|
import java.io.InputStream;
|
|
import java.io.IOException;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import org.geometerplus.zlibrary.core.util.ZLMiscUtil;
|
|
import org.geometerplus.zlibrary.core.filesystem.*;
|
|
import org.geometerplus.zlibrary.core.image.ZLImage;
|
|
|
|
import org.geometerplus.zlibrary.text.view.ZLTextPosition;
|
|
|
|
import org.geometerplus.fbreader.formats.*;
|
|
import org.geometerplus.fbreader.bookmodel.BookReadingException;
|
|
|
|
import org.geometerplus.fbreader.Paths;
|
|
|
|
public class Book {
|
|
public static Book getById(long bookId) {
|
|
final Book book = BooksDatabase.Instance().loadBook(bookId);
|
|
if (book == null) {
|
|
return null;
|
|
}
|
|
book.loadLists();
|
|
|
|
final ZLFile bookFile = book.File;
|
|
final ZLPhysicalFile physicalFile = bookFile.getPhysicalFile();
|
|
if (physicalFile == null) {
|
|
return book;
|
|
}
|
|
if (!physicalFile.exists()) {
|
|
return null;
|
|
}
|
|
|
|
FileInfoSet fileInfos = new FileInfoSet(physicalFile);
|
|
if (fileInfos.check(physicalFile, physicalFile != bookFile)) {
|
|
return book;
|
|
}
|
|
fileInfos.save();
|
|
|
|
try {
|
|
book.readMetaInfo();
|
|
return book;
|
|
} catch (BookReadingException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static Book getByFile(ZLFile bookFile) {
|
|
if (bookFile == null) {
|
|
return null;
|
|
}
|
|
|
|
final ZLPhysicalFile physicalFile = bookFile.getPhysicalFile();
|
|
if (physicalFile != null && !physicalFile.exists()) {
|
|
return null;
|
|
}
|
|
|
|
final FileInfoSet fileInfos = new FileInfoSet(bookFile);
|
|
|
|
Book book = BooksDatabase.Instance().loadBookByFile(fileInfos.getId(bookFile), bookFile);
|
|
if (book != null) {
|
|
book.loadLists();
|
|
}
|
|
|
|
if (book != null && fileInfos.check(physicalFile, physicalFile != bookFile)) {
|
|
return book;
|
|
}
|
|
fileInfos.save();
|
|
|
|
try {
|
|
if (book == null) {
|
|
book = new Book(bookFile);
|
|
} else {
|
|
book.readMetaInfo();
|
|
}
|
|
} catch (BookReadingException e) {
|
|
return null;
|
|
}
|
|
|
|
book.save();
|
|
return book;
|
|
}
|
|
|
|
public final ZLFile File;
|
|
|
|
private volatile long myId;
|
|
|
|
private volatile String myEncoding;
|
|
private volatile String myLanguage;
|
|
private volatile String myTitle;
|
|
private volatile List<Author> myAuthors;
|
|
private volatile List<Tag> myTags;
|
|
private volatile SeriesInfo mySeriesInfo;
|
|
|
|
private volatile boolean myIsSaved;
|
|
|
|
private static final WeakReference<ZLImage> NULL_IMAGE = new WeakReference<ZLImage>(null);
|
|
private WeakReference<ZLImage> myCover;
|
|
|
|
Book(long id, ZLFile file, String title, String encoding, String language) {
|
|
myId = id;
|
|
File = file;
|
|
myTitle = title;
|
|
myEncoding = encoding;
|
|
myLanguage = language;
|
|
myIsSaved = true;
|
|
}
|
|
|
|
Book(ZLFile file) throws BookReadingException {
|
|
myId = -1;
|
|
File = file;
|
|
readMetaInfo();
|
|
}
|
|
|
|
public void reloadInfoFromFile() {
|
|
try {
|
|
readMetaInfo();
|
|
save();
|
|
} catch (BookReadingException e) {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
public void reloadInfoFromDatabase() {
|
|
final BooksDatabase database = BooksDatabase.Instance();
|
|
database.reloadBook(this);
|
|
myAuthors = database.loadAuthors(myId);
|
|
myTags = database.loadTags(myId);
|
|
mySeriesInfo = database.loadSeriesInfo(myId);
|
|
myIsSaved = true;
|
|
}
|
|
|
|
public FormatPlugin getPlugin() throws BookReadingException {
|
|
final FormatPlugin plugin = PluginCollection.Instance().getPlugin(File);
|
|
if (plugin == null) {
|
|
throw new BookReadingException("pluginNotFound", File);
|
|
}
|
|
return plugin;
|
|
}
|
|
|
|
void readMetaInfo() throws BookReadingException {
|
|
readMetaInfo(getPlugin());
|
|
}
|
|
|
|
private void readMetaInfo(FormatPlugin plugin) throws BookReadingException {
|
|
myEncoding = null;
|
|
myLanguage = null;
|
|
myTitle = null;
|
|
myAuthors = null;
|
|
myTags = null;
|
|
mySeriesInfo = null;
|
|
|
|
myIsSaved = false;
|
|
|
|
plugin.readMetaInfo(this);
|
|
|
|
if (myTitle == null || myTitle.length() == 0) {
|
|
final String fileName = File.getShortName();
|
|
final int index = fileName.lastIndexOf('.');
|
|
setTitle(index > 0 ? fileName.substring(0, index) : fileName);
|
|
}
|
|
final String demoPathPrefix = Paths.BooksDirectoryOption().getValue() + java.io.File.separator + "Demos" + java.io.File.separator;
|
|
if (File.getPath().startsWith(demoPathPrefix)) {
|
|
final String demoTag = LibraryUtil.resource().getResource("demo").getValue();
|
|
setTitle(getTitle() + " (" + demoTag + ")");
|
|
addTag(demoTag);
|
|
}
|
|
}
|
|
|
|
private void loadLists() {
|
|
final BooksDatabase database = BooksDatabase.Instance();
|
|
myAuthors = database.loadAuthors(myId);
|
|
myTags = database.loadTags(myId);
|
|
mySeriesInfo = database.loadSeriesInfo(myId);
|
|
myIsSaved = true;
|
|
}
|
|
|
|
public List<Author> authors() {
|
|
return (myAuthors != null) ? Collections.unmodifiableList(myAuthors) : Collections.<Author>emptyList();
|
|
}
|
|
|
|
void addAuthorWithNoCheck(Author author) {
|
|
if (myAuthors == null) {
|
|
myAuthors = new ArrayList<Author>();
|
|
}
|
|
myAuthors.add(author);
|
|
}
|
|
|
|
private void addAuthor(Author author) {
|
|
if (author == null) {
|
|
return;
|
|
}
|
|
if (myAuthors == null) {
|
|
myAuthors = new ArrayList<Author>();
|
|
myAuthors.add(author);
|
|
myIsSaved = false;
|
|
} else if (!myAuthors.contains(author)) {
|
|
myAuthors.add(author);
|
|
myIsSaved = false;
|
|
}
|
|
}
|
|
|
|
public void addAuthor(String name) {
|
|
addAuthor(name, "");
|
|
}
|
|
|
|
public void addAuthor(String name, String sortKey) {
|
|
String strippedName = name;
|
|
strippedName.trim();
|
|
if (strippedName.length() == 0) {
|
|
return;
|
|
}
|
|
|
|
String strippedKey = sortKey;
|
|
strippedKey.trim();
|
|
if (strippedKey.length() == 0) {
|
|
int index = strippedName.lastIndexOf(' ');
|
|
if (index == -1) {
|
|
strippedKey = strippedName;
|
|
} else {
|
|
strippedKey = strippedName.substring(index + 1);
|
|
while ((index >= 0) && (strippedName.charAt(index) == ' ')) {
|
|
--index;
|
|
}
|
|
strippedName = strippedName.substring(0, index + 1) + ' ' + strippedKey;
|
|
}
|
|
}
|
|
|
|
addAuthor(new Author(strippedName, strippedKey));
|
|
}
|
|
|
|
public long getId() {
|
|
return myId;
|
|
}
|
|
|
|
public String getTitle() {
|
|
return myTitle;
|
|
}
|
|
|
|
public void setTitle(String title) {
|
|
if (!ZLMiscUtil.equals(myTitle, title)) {
|
|
myTitle = title;
|
|
myIsSaved = false;
|
|
}
|
|
}
|
|
|
|
public SeriesInfo getSeriesInfo() {
|
|
return mySeriesInfo;
|
|
}
|
|
|
|
void setSeriesInfoWithNoCheck(String name, float index) {
|
|
mySeriesInfo = new SeriesInfo(name, index);
|
|
}
|
|
|
|
public void setSeriesInfo(String name, float index) {
|
|
if (mySeriesInfo == null) {
|
|
if (name != null) {
|
|
mySeriesInfo = new SeriesInfo(name, index);
|
|
myIsSaved = false;
|
|
}
|
|
} else if (name == null) {
|
|
mySeriesInfo = null;
|
|
myIsSaved = false;
|
|
} else if (!name.equals(mySeriesInfo.Name) || mySeriesInfo.Index != index) {
|
|
mySeriesInfo = new SeriesInfo(name, index);
|
|
myIsSaved = false;
|
|
}
|
|
}
|
|
|
|
public String getLanguage() {
|
|
return myLanguage;
|
|
}
|
|
|
|
public void setLanguage(String language) {
|
|
if (!ZLMiscUtil.equals(myLanguage, language)) {
|
|
myLanguage = language;
|
|
myIsSaved = false;
|
|
}
|
|
}
|
|
|
|
public String getEncoding() {
|
|
if (myEncoding == null) {
|
|
try {
|
|
getPlugin().detectLanguageAndEncoding(this);
|
|
} catch (BookReadingException e) {
|
|
}
|
|
if (myEncoding == null) {
|
|
setEncoding("utf-8");
|
|
}
|
|
}
|
|
return myEncoding;
|
|
}
|
|
|
|
public String getEncodingNoDetection() {
|
|
return myEncoding;
|
|
}
|
|
|
|
public void setEncoding(String encoding) {
|
|
if (!ZLMiscUtil.equals(myEncoding, encoding)) {
|
|
myEncoding = encoding;
|
|
myIsSaved = false;
|
|
}
|
|
}
|
|
|
|
public List<Tag> tags() {
|
|
return (myTags != null) ? Collections.unmodifiableList(myTags) : Collections.<Tag>emptyList();
|
|
}
|
|
|
|
void addTagWithNoCheck(Tag tag) {
|
|
if (myTags == null) {
|
|
myTags = new ArrayList<Tag>();
|
|
}
|
|
myTags.add(tag);
|
|
}
|
|
|
|
public void addTag(Tag tag) {
|
|
if (tag != null) {
|
|
if (myTags == null) {
|
|
myTags = new ArrayList<Tag>();
|
|
}
|
|
if (!myTags.contains(tag)) {
|
|
myTags.add(tag);
|
|
myIsSaved = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void addTag(String tagName) {
|
|
addTag(Tag.getTag(null, tagName));
|
|
}
|
|
|
|
boolean matches(String pattern) {
|
|
if (myTitle != null && ZLMiscUtil.matchesIgnoreCase(myTitle, pattern)) {
|
|
return true;
|
|
}
|
|
if (mySeriesInfo != null && ZLMiscUtil.matchesIgnoreCase(mySeriesInfo.Name, pattern)) {
|
|
return true;
|
|
}
|
|
if (myAuthors != null) {
|
|
for (Author author : myAuthors) {
|
|
if (ZLMiscUtil.matchesIgnoreCase(author.DisplayName, pattern)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (myTags != null) {
|
|
for (Tag tag : myTags) {
|
|
if (ZLMiscUtil.matchesIgnoreCase(tag.Name, pattern)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (ZLMiscUtil.matchesIgnoreCase(File.getLongName(), pattern)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean save() {
|
|
if (myIsSaved) {
|
|
return false;
|
|
}
|
|
final BooksDatabase database = BooksDatabase.Instance();
|
|
database.executeAsATransaction(new Runnable() {
|
|
public void run() {
|
|
if (myId >= 0) {
|
|
final FileInfoSet fileInfos = new FileInfoSet(File);
|
|
database.updateBookInfo(myId, fileInfos.getId(File), myEncoding, myLanguage, myTitle);
|
|
} else {
|
|
myId = database.insertBookInfo(File, myEncoding, myLanguage, myTitle);
|
|
storeAllVisitedHyperinks();
|
|
}
|
|
|
|
long index = 0;
|
|
database.deleteAllBookAuthors(myId);
|
|
for (Author author : authors()) {
|
|
database.saveBookAuthorInfo(myId, index++, author);
|
|
}
|
|
database.deleteAllBookTags(myId);
|
|
for (Tag tag : tags()) {
|
|
database.saveBookTagInfo(myId, tag);
|
|
}
|
|
database.saveBookSeriesInfo(myId, mySeriesInfo);
|
|
}
|
|
});
|
|
|
|
myIsSaved = true;
|
|
return true;
|
|
}
|
|
|
|
public ZLTextPosition getStoredPosition() {
|
|
return BooksDatabase.Instance().getStoredPosition(myId);
|
|
}
|
|
|
|
public void storePosition(ZLTextPosition position) {
|
|
if (myId != -1) {
|
|
BooksDatabase.Instance().storePosition(myId, position);
|
|
}
|
|
}
|
|
|
|
private Set<String> myVisitedHyperlinks;
|
|
private void initHyperlinkSet() {
|
|
if (myVisitedHyperlinks == null) {
|
|
myVisitedHyperlinks = new TreeSet<String>();
|
|
if (myId != -1) {
|
|
myVisitedHyperlinks.addAll(BooksDatabase.Instance().loadVisitedHyperlinks(myId));
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean isHyperlinkVisited(String linkId) {
|
|
initHyperlinkSet();
|
|
return myVisitedHyperlinks.contains(linkId);
|
|
}
|
|
|
|
public void markHyperlinkAsVisited(String linkId) {
|
|
initHyperlinkSet();
|
|
if (!myVisitedHyperlinks.contains(linkId)) {
|
|
myVisitedHyperlinks.add(linkId);
|
|
if (myId != -1) {
|
|
BooksDatabase.Instance().addVisitedHyperlink(myId, linkId);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void storeAllVisitedHyperinks() {
|
|
if (myId != -1 && myVisitedHyperlinks != null) {
|
|
for (String linkId : myVisitedHyperlinks) {
|
|
BooksDatabase.Instance().addVisitedHyperlink(myId, linkId);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void insertIntoBookList() {
|
|
if (myId != -1) {
|
|
BooksDatabase.Instance().insertIntoBookList(myId);
|
|
}
|
|
}
|
|
|
|
public String getContentHashCode() {
|
|
InputStream stream = null;
|
|
|
|
try {
|
|
final MessageDigest hash = MessageDigest.getInstance("SHA-256");
|
|
stream = File.getInputStream();
|
|
|
|
final byte[] buffer = new byte[2048];
|
|
while (true) {
|
|
final int nread = stream.read(buffer);
|
|
if (nread == -1) {
|
|
break;
|
|
}
|
|
hash.update(buffer, 0, nread);
|
|
}
|
|
|
|
final Formatter f = new Formatter();
|
|
for (byte b : hash.digest()) {
|
|
f.format("%02X", b & 0xFF);
|
|
}
|
|
return f.toString();
|
|
} catch (IOException e) {
|
|
return null;
|
|
} catch (NoSuchAlgorithmException e) {
|
|
return null;
|
|
} finally {
|
|
if (stream != null) {
|
|
try {
|
|
stream.close();
|
|
} catch (IOException e) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
synchronized ZLImage getCover() {
|
|
if (myCover == NULL_IMAGE) {
|
|
return null;
|
|
} else if (myCover != null) {
|
|
final ZLImage image = myCover.get();
|
|
if (image != null) {
|
|
return image;
|
|
}
|
|
}
|
|
ZLImage image = null;
|
|
try {
|
|
image = getPlugin().readCover(File);
|
|
} catch (BookReadingException e) {
|
|
// ignore
|
|
}
|
|
myCover = image != null ? new WeakReference<ZLImage>(image) : NULL_IMAGE;
|
|
return image;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return (int)myId;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) {
|
|
return true;
|
|
}
|
|
if (!(o instanceof Book)) {
|
|
return false;
|
|
}
|
|
return File.equals(((Book)o).File);
|
|
}
|
|
}
|