/* * Copyright (C) 2013 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.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import jedi.functional.Command; import jedi.functional.Command2; import jedi.option.None; import jedi.option.Option; import net.nightwhistler.pageturner.Configuration; import net.nightwhistler.pageturner.R; import net.nightwhistler.pageturner.scheduling.QueueableAsyncTask; import nl.siegmann.epublib.domain.Book; import nl.siegmann.epublib.epub.EpubReader; import nl.siegmann.epublib.service.MediatypeService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.util.*; public class ImportTask extends QueueableAsyncTask<File, Integer, Void> implements OnCancelListener { private Context context; private LibraryService libraryService; private ImportCallback callBack; private Configuration config; private boolean copyToLibrary; private List<String> errors = new ArrayList<>(); private static final Logger LOG = LoggerFactory.getLogger(ImportTask.class); private static final int UPDATE_FOLDER = 1; private static final int UPDATE_IMPORT = 2; private int foldersScanned = 0; private int booksImported = 0; private boolean emptyLibrary; private boolean silent; private String importFailed = null; public ImportTask( Context context, LibraryService libraryService, ImportCallback callBack, Configuration config, boolean copyToLibrary, boolean silent) { this.context = context; this.libraryService = libraryService; this.callBack = callBack; this.copyToLibrary = copyToLibrary; this.config = config; this.silent = silent; } @Override public void onCancel(DialogInterface dialog) { LOG.debug("User aborted import."); requestCancellation(); } public boolean isSilent() { return this.silent; } public void setCallBack( ImportCallback callBack ) { this.callBack = callBack; } @Override public Option<Void> doInBackground(File... params) { doInBackground( params[0] ); return new None(); } private void doInBackground(File parent) { LOG.debug( "Starting import of folder " + parent.getAbsolutePath() ); /* Hack: don't run automated import on an empty database, since we explicitly ask the user to import. */ if ( silent && libraryService.findAllByTitle(null).getSize() == 0 ) { return; } if ( ! parent.exists() ) { LOG.error("Bailing out because of missing parent folder: " + parent.getPath() ); importFailed = String.format( context.getString(R.string.no_such_folder), parent.getPath()); return; } this.emptyLibrary = this.libraryService.findAllByTitle(null).getSize() == 0; List<File> books = new ArrayList<>(); findEpubsInFolder(parent, books); int total = books.size(); int i = 0; while ( i < books.size() && ! isCancelled() ) { File book = books.get(i); LOG.info("Importing: " + book.getAbsolutePath() ); try { if ( importBook( book ) ) { booksImported++; } } catch (OutOfMemoryError oom ) { errors.add(book.getName() + ": Out of memory."); return; } i++; publishProgress(UPDATE_IMPORT, i, total); } } private void findEpubsInFolder( File folder, List<File> items) { if ( folder == null || ! folder.exists() ) { return; } //If we got a single file, just import that. if ( ! folder.isDirectory() ) { items.add( folder ); return; } Queue<File> dirs = new LinkedList<>(); dirs.add(folder); while ( !isCancelled() && !dirs.isEmpty() ) { File[] fileList = dirs.poll().listFiles(); if ( fileList != null ) { for (File f : fileList ) { if (f.isDirectory()) { foldersScanned++; publishProgress(UPDATE_FOLDER, foldersScanned); //Check if a recursive structure with symlinks if ( ! dirs.contains(f) ) { dirs.add(f); } } else if (f.isFile()) { processFile( f, items ); } } } } } private void processFile( File file, List<File> items ) { String fileName = file.getAbsolutePath(); //Scan items if ( fileName.endsWith(".epub") ) { items.add(file); } else { Command<File> add = f -> { if ( f.getName().indexOf(".") == -1 ) { //Older versions downloaded files without an extension items.add(f); } }; config.getLibraryFolder().forEach( libraryFolder -> { if ( fileName.startsWith(libraryFolder.getAbsolutePath() ) ) { add.execute( file ); } }); config.getDownloadsFolder().forEach( downloadsFolder -> { if ( fileName.startsWith( downloadsFolder.getAbsolutePath() )) { add.execute( file ); } }); } } private boolean importBook(File file) { if (! libraryService.hasBook(file.getName() ) ) { try { String fileName = file.getAbsolutePath(); // read epub file EpubReader epubReader = new EpubReader(); Book importedBook = epubReader.readEpubLazy(fileName, "UTF-8", Arrays.asList(MediatypeService.mediatypes)); libraryService.storeBook(fileName, importedBook, false, this.copyToLibrary); return true; } catch (Exception io ) { if ( ! isCancelled() ) { errors.add( file + ": " + io.getMessage() ); LOG.error("Error while reading book: " + file, io); } else { LOG.info("Ignoring error since we were cancelled", io ); } } } return false; } @Override public void doOnProgressUpdate(Integer... values) { String message; if ( values[0] == UPDATE_IMPORT ) { message = String.format(context.getString(R.string.importing), values[1], values[2]); } else { message = String.format(context.getString(R.string.scan_folders), values[1]); } callBack.importStatusUpdate(message, silent); } @Override public void doOnCancelled(Option<Void> none) { this.callBack.importCancelled( booksImported, errors, emptyLibrary, silent); } @Override public void doOnPostExecute(Option<Void> none) { LOG.debug("Import task completed, imported " + booksImported + " books."); if ( importFailed != null ) { callBack.importFailed(importFailed, silent); } else { this.callBack.importComplete(booksImported, errors, emptyLibrary, silent); } } }