/* * Copyright (C) 2011 Alex Kuiper * * This file is part of PageTurner * * PageTurner 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 3 of the License, or * (at your option) any later version. * * PageTurner 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 PageTurner. If not, see <http://www.gnu.org/licenses/>.* */ package net.nightwhistler.pageturner.library; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.Matrix; import com.google.inject.Inject; import jedi.option.Option; import net.nightwhistler.pageturner.Configuration; import net.nightwhistler.pageturner.library.LibraryDatabaseHelper.Order; import nl.siegmann.epublib.domain.Book; import nl.siegmann.epublib.domain.Metadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import roboguice.inject.ContextSingleton; import java.io.*; import java.nio.channels.FileChannel; import static jedi.functional.FunctionalPrimitives.isEmpty; import static jedi.option.Options.none; import static jedi.option.Options.option; import static jedi.option.Options.some; @ContextSingleton public class SqlLiteLibraryService implements LibraryService { private static final int THUMBNAIL_HEIGHT = 250; private static final long MAX_COVER_SIZE = 1024 * 1024; //Max 1Mb @Inject private LibraryDatabaseHelper helper; private static final Logger LOG = LoggerFactory.getLogger(SqlLiteLibraryService.class); @Inject private Configuration config; @Override public void updateReadingProgress(String fileName, int progress) { helper.updateLastRead(new File(fileName).getName(), progress); } @Override public void storeBook(String fileName, Book book, boolean updateLastRead, boolean copyFile) throws IOException { File bookFile = new File(fileName); boolean hasBook = hasBook(bookFile.getName()); if ( hasBook && !updateLastRead ) { return; } else if ( hasBook ) { helper.updateLastRead(bookFile.getName(), -1); return; } Metadata metaData = book.getMetadata(); String authorFirstName = "Unknown author"; String authorLastName = ""; if ( metaData.getAuthors().size() > 0 ) { authorFirstName = metaData.getAuthors().get(0).getFirstname(); authorLastName = metaData.getAuthors().get(0).getLastname(); } Option<byte[]> thumbNail = none(); try { if ( book.getCoverImage() != null && book.getCoverImage().getSize() < MAX_COVER_SIZE ) { thumbNail = resizeImage(book.getCoverImage().getData()); book.getCoverImage().close(); } } catch (IOException | OutOfMemoryError e) { //If the image resource is too big, just import without a cover. } String description = ""; if ( ! metaData.getDescriptions().isEmpty() ) { description = metaData.getDescriptions().get(0); } String title = book.getTitle(); if ( title.trim().length() == 0 ) { title = fileName.substring( fileName.lastIndexOf('/') + 1 ); } if ( copyFile ) { Option<File> copiedFile = copyToLibrary(fileName, authorLastName + ", " + authorFirstName, title ); if ( ! isEmpty(copiedFile) ) { bookFile = copiedFile.unsafeGet(); } } this.helper.storeNewBook(bookFile.getAbsolutePath(), authorFirstName, authorLastName, title, description, thumbNail.unsafeGet(), updateLastRead); } private String cleanUp(String input) { char[] illegalChars = { ':', '/', '\\', '?', '<', '>', '\"', '*', '&', '#', '(', ')' }; String output = input; for ( char c: illegalChars ) { output = output.replace(c, '_'); } return output.trim(); } private Option<File> copyToLibrary( String fileName, String author, String title) throws IOException { Option<File> libraryFolder = config.getLibraryFolder(); if ( isEmpty(libraryFolder) ) { return none(); } File baseFile = new File(fileName); File targetFolder = new File( libraryFolder.unsafeGet(), cleanUp(author) + "/" + cleanUp(title) ); targetFolder.mkdirs(); FileChannel source = null; FileChannel destination = null; File targetFile = new File(targetFolder, baseFile.getName()); if ( baseFile.equals(targetFile) ) { return some(baseFile); } LOG.debug("Copying to file: " + targetFile.getAbsolutePath() ); targetFile.createNewFile(); try { source = new FileInputStream(baseFile).getChannel(); destination = new FileOutputStream(targetFile).getChannel(); destination.transferFrom(source, 0, source.size()); } finally { if(source != null) { source.close(); } if(destination != null) { destination.close(); } } return some(targetFile); } @Override public QueryResult<LibraryBook> findUnread(String filter) { return helper.findByField( LibraryDatabaseHelper.Field.date_last_read, null, LibraryDatabaseHelper.Field.title, LibraryDatabaseHelper.Order.ASC, filter); } @Override public Option<LibraryBook> getBook(String fileName) { QueryResult<LibraryBook> booksByFile = helper.findByField(LibraryDatabaseHelper.Field.file_name, fileName, LibraryDatabaseHelper.Field.file_name, Order.ASC, null); switch ( booksByFile.getSize() ) { case 0: return none(); case 1: return option(booksByFile.getItemAt(0)); default: throw new IllegalStateException("Non unique file-name: " + fileName ); } } @Override public QueryResult<LibraryBook> findAllByLastRead(String filter) { return helper.findAllOrderedBy( LibraryDatabaseHelper.Field.date_last_read, LibraryDatabaseHelper.Order.DESC, filter ); } @Override public QueryResult<LibraryBook> findAllByAuthor(String filter) { return helper.findAllKeyedBy( LibraryDatabaseHelper.Field.a_last_name, LibraryDatabaseHelper.Order.ASC, filter ); } @Override public QueryResult<LibraryBook> findAllByLastAdded(String filter) { return helper.findAllOrderedBy( LibraryDatabaseHelper.Field.date_added, LibraryDatabaseHelper.Order.DESC, filter ); } @Override public KeyedQueryResult<LibraryBook> findAllByTitle(String filter) { return helper.findAllKeyedBy( LibraryDatabaseHelper.Field.title, LibraryDatabaseHelper.Order.ASC, filter ); } public void close() { helper.close(); } @Override public void deleteBook(String fileName) { this.helper.delete( fileName ); config.getLibraryFolder().forEach( libraryFolder -> { //Only delete files we manage if ( fileName.startsWith(libraryFolder.getAbsolutePath()) ) { File bookFile = new File(fileName); File parentFolder = bookFile.getParentFile(); bookFile.delete(); while (parentFolder.list() == null || parentFolder.list().length == 0 ) { parentFolder.delete(); parentFolder = parentFolder.getParentFile(); } } }); } @Override public boolean hasBook(String fileName) { return helper.hasBook(fileName); } private Option<byte[]> resizeImage( byte[] input ) { if ( input == null ) { return none(); } Bitmap bitmapOrg = BitmapFactory.decodeByteArray(input, 0, input.length); if ( bitmapOrg == null ) { return none(); } int height = bitmapOrg.getHeight(); int width = bitmapOrg.getWidth(); int newHeight = THUMBNAIL_HEIGHT; float scaleHeight = ((float) newHeight) / height; // create a matrix for the manipulation Matrix matrix = new Matrix(); // resize the bit map matrix.postScale(scaleHeight, scaleHeight); // recreate the new Bitmap Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0, width, height, matrix, true); bitmapOrg.recycle(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); resizedBitmap.compress(CompressFormat.PNG, 0 /*ignored for PNG*/, bos); resizedBitmap.recycle(); return some(bos.toByteArray()); } }