/* * Copyright (C) 2007-2012 Geometer Plus * * 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 myAuthors; private volatile List myTags; private volatile SeriesInfo mySeriesInfo; private volatile boolean myIsSaved; private static final WeakReference NULL_IMAGE = new WeakReference(null); private WeakReference 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 authors() { return (myAuthors != null) ? Collections.unmodifiableList(myAuthors) : Collections.emptyList(); } void addAuthorWithNoCheck(Author author) { if (myAuthors == null) { myAuthors = new ArrayList(); } myAuthors.add(author); } private void addAuthor(Author author) { if (author == null) { return; } if (myAuthors == null) { myAuthors = new ArrayList(); 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 tags() { return (myTags != null) ? Collections.unmodifiableList(myTags) : Collections.emptyList(); } void addTagWithNoCheck(Tag tag) { if (myTags == null) { myTags = new ArrayList(); } myTags.add(tag); } public void addTag(Tag tag) { if (tag != null) { if (myTags == null) { myTags = new ArrayList(); } 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 myVisitedHyperlinks; private void initHyperlinkSet() { if (myVisitedHyperlinks == null) { myVisitedHyperlinks = new TreeSet(); 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(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); } }